@sfmc-bds/db-server 0.2.0-beta.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +1 -1
  2. package/dist/domain/bridge.d.ts +1 -5
  3. package/dist/domain/bridge.d.ts.map +1 -1
  4. package/dist/domain/bridge.js +15 -14
  5. package/dist/domain/bridge.js.map +2 -2
  6. package/dist/domain/installed-content.d.ts +84 -0
  7. package/dist/domain/installed-content.d.ts.map +1 -0
  8. package/dist/domain/installed-content.js +149 -0
  9. package/dist/domain/installed-content.js.map +7 -0
  10. package/dist/domain/player-session.d.ts +8 -0
  11. package/dist/domain/player-session.d.ts.map +1 -0
  12. package/dist/domain/player-session.js +16 -0
  13. package/dist/domain/player-session.js.map +7 -0
  14. package/dist/domain/qq-account-profile.d.ts +51 -0
  15. package/dist/domain/qq-account-profile.d.ts.map +1 -0
  16. package/dist/domain/qq-account-profile.js +129 -0
  17. package/dist/domain/qq-account-profile.js.map +7 -0
  18. package/dist/domain/qq-events.d.ts +2 -2
  19. package/dist/domain/qq-events.d.ts.map +1 -1
  20. package/dist/domain/qq-events.js +6 -3
  21. package/dist/domain/qq-events.js.map +2 -2
  22. package/dist/domain/schema.d.ts.map +1 -1
  23. package/dist/domain/schema.js +48 -0
  24. package/dist/domain/schema.js.map +2 -2
  25. package/dist/domain/system-status.d.ts +3 -0
  26. package/dist/domain/system-status.d.ts.map +1 -1
  27. package/dist/domain/system-status.js +27 -12
  28. package/dist/domain/system-status.js.map +2 -2
  29. package/dist/env.d.ts +0 -2
  30. package/dist/env.d.ts.map +1 -1
  31. package/dist/env.js +12 -24
  32. package/dist/env.js.map +2 -2
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +108 -52
  36. package/dist/index.js.map +3 -3
  37. package/dist/routes/config.d.ts +1 -1
  38. package/dist/routes/config.d.ts.map +1 -1
  39. package/dist/routes/config.js +0 -4
  40. package/dist/routes/config.js.map +2 -2
  41. package/dist/routes/content.d.ts +23 -0
  42. package/dist/routes/content.d.ts.map +1 -0
  43. package/dist/routes/content.js +19 -0
  44. package/dist/routes/content.js.map +7 -0
  45. package/dist/routes/messages.d.ts +14 -6
  46. package/dist/routes/messages.d.ts.map +1 -1
  47. package/dist/routes/messages.js +22 -8
  48. package/dist/routes/messages.js.map +2 -2
  49. package/dist/routes/qq-bind.d.ts.map +1 -1
  50. package/dist/routes/qq-bind.js +29 -1
  51. package/dist/routes/qq-bind.js.map +2 -2
  52. package/dist/routes/qq-events.d.ts +6 -2
  53. package/dist/routes/qq-events.d.ts.map +1 -1
  54. package/dist/routes/qq-events.js +35 -1
  55. package/dist/routes/qq-events.js.map +3 -3
  56. package/dist/routes/qq-join.js +1 -1
  57. package/dist/routes/qq-join.js.map +2 -2
  58. package/dist/routes/status.d.ts +3 -11
  59. package/dist/routes/status.d.ts.map +1 -1
  60. package/dist/routes/status.js +39 -49
  61. package/dist/routes/status.js.map +2 -2
  62. package/dist/tx-runner.d.ts +11 -2
  63. package/dist/tx-runner.d.ts.map +1 -1
  64. package/dist/tx-runner.js +42 -7
  65. package/dist/tx-runner.js.map +2 -2
  66. package/package.json +4 -5
  67. package/dist/domain/bridge.test.d.ts +0 -2
  68. package/dist/domain/bridge.test.d.ts.map +0 -1
  69. package/dist/domain/bridge.test.js +0 -87
  70. package/dist/domain/bridge.test.js.map +0 -7
  71. package/dist/domain/messages-bridge-filter.test.d.ts +0 -5
  72. package/dist/domain/messages-bridge-filter.test.d.ts.map +0 -1
  73. package/dist/domain/messages-bridge-filter.test.js +0 -20
  74. package/dist/domain/messages-bridge-filter.test.js.map +0 -7
  75. package/dist/domain/qq-bind-status.test.d.ts +0 -2
  76. package/dist/domain/qq-bind-status.test.d.ts.map +0 -1
  77. package/dist/domain/qq-bind-status.test.js +0 -211
  78. package/dist/domain/qq-bind-status.test.js.map +0 -7
  79. package/dist/domain/qq-events.test.d.ts +0 -5
  80. package/dist/domain/qq-events.test.d.ts.map +0 -1
  81. package/dist/domain/qq-events.test.js +0 -170
  82. package/dist/domain/qq-events.test.js.map +0 -7
  83. package/dist/domain/qq-join.test.d.ts +0 -2
  84. package/dist/domain/qq-join.test.d.ts.map +0 -1
  85. package/dist/domain/qq-join.test.js +0 -353
  86. package/dist/domain/qq-join.test.js.map +0 -7
  87. package/dist/domain/system-status.test.d.ts +0 -5
  88. package/dist/domain/system-status.test.d.ts.map +0 -1
  89. package/dist/domain/system-status.test.js +0 -19
  90. package/dist/domain/system-status.test.js.map +0 -7
  91. package/dist/runtime.test.d.ts +0 -2
  92. package/dist/runtime.test.d.ts.map +0 -1
  93. package/dist/runtime.test.js +0 -402
  94. package/dist/runtime.test.js.map +0 -7
@@ -0,0 +1,129 @@
1
+ import { SQL } from "sql-template-strings";
2
+ const DEFAULT_ECONOMY_UNIT = "\u8282\u64CD";
3
+ const ONLINE_TIME_TZ_OFFSET_MS = 8 * 36e5;
4
+ function formatOnlineDuration(seconds) {
5
+ const s = Math.max(0, Math.floor(seconds));
6
+ const days = Math.floor(s / 86400);
7
+ const hours = Math.floor(s % 86400 / 3600);
8
+ const minutes = Math.floor(s % 3600 / 60);
9
+ const secs = s % 60;
10
+ const parts = [];
11
+ if (days > 0) parts.push(`${days}\u5929`);
12
+ if (hours > 0) parts.push(`${hours}\u65F6`);
13
+ if (minutes > 0) parts.push(`${minutes}\u5206`);
14
+ if (parts.length === 0 || secs > 0) parts.push(`${secs}\u79D2`);
15
+ return parts.join("") || "0\u79D2";
16
+ }
17
+ function dateKey(ms) {
18
+ const local = new Date(ms + ONLINE_TIME_TZ_OFFSET_MS);
19
+ const y = local.getUTCFullYear();
20
+ const m = String(local.getUTCMonth() + 1).padStart(2, "0");
21
+ const d = String(local.getUTCDate()).padStart(2, "0");
22
+ return `${y}-${m}-${d}`;
23
+ }
24
+ function rollPluginOnlineSeconds(row, now = Date.now()) {
25
+ const today = dateKey(now);
26
+ const month = today.slice(0, 7);
27
+ return {
28
+ today: String(row.last_date || "") === today ? Number(row.today_seconds) || 0 : 0,
29
+ month: String(row.last_month || "") === month ? Number(row.month_seconds) || 0 : 0,
30
+ total: Number(row.total_seconds) || 0
31
+ };
32
+ }
33
+ function safeRows(query, sql) {
34
+ try {
35
+ const rows = query(sql);
36
+ return Array.isArray(rows) ? rows : [];
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+ function pickEconomyBalance(rows, playerId, playerName) {
42
+ if (!rows.length) return 0;
43
+ const prefixed = `player:${playerId}`;
44
+ const order = [playerId, prefixed, playerName].filter((id) => id);
45
+ for (const id of order) {
46
+ const hit = rows.find((row) => String(row.account_id || "") === id);
47
+ if (!hit) continue;
48
+ if (id === playerName && hit.account_type && hit.account_type !== "player") continue;
49
+ const balance = Number(hit.balance);
50
+ return Number.isFinite(balance) ? balance : 0;
51
+ }
52
+ return 0;
53
+ }
54
+ function rollSnapshotSeconds(row, now) {
55
+ if (row.today_seconds == null && row.month_seconds == null && row.total_seconds == null) return null;
56
+ const updated = Number(row.updated_at) || 0;
57
+ const stampDay = updated > 0 ? dateKey(updated) : "";
58
+ const stampMonth = stampDay.slice(0, 7);
59
+ const today = dateKey(now);
60
+ const month = today.slice(0, 7);
61
+ return {
62
+ today: stampDay === today ? Number(row.today_seconds) || 0 : 0,
63
+ month: stampMonth === month ? Number(row.month_seconds) || 0 : 0,
64
+ total: Number(row.total_seconds) || 0
65
+ };
66
+ }
67
+ function loadQqAccountProfile(query, playerId, playerName, now = Date.now()) {
68
+ const id = playerId.trim();
69
+ const name = playerName.trim();
70
+ const economyRows = id ? safeRows(
71
+ query,
72
+ SQL`SELECT account_id, balance, account_type FROM sfmc_economy_accounts
73
+ WHERE (_deleted_at IS NULL)
74
+ AND (account_id = ${id} OR account_id = ${`player:${id}`} OR account_id = ${name})`
75
+ ) : [];
76
+ const onlineRows = id ? safeRows(
77
+ query,
78
+ SQL`SELECT today_seconds, month_seconds, total_seconds, last_date, last_month, updated_at
79
+ FROM sfmc_online_time
80
+ WHERE player_id = ${id} AND (_deleted_at IS NULL)
81
+ LIMIT 1`
82
+ ) : [];
83
+ const snapshots = id ? safeRows(
84
+ query,
85
+ SQL`SELECT balance, today_seconds, month_seconds, total_seconds, updated_at
86
+ FROM sfmc_qq_account_profile WHERE player_xuid = ${id} LIMIT 1`
87
+ ) : [];
88
+ const archiveBalance = economyRows == null ? null : pickEconomyBalance(economyRows, id, name);
89
+ const pluginOnline = onlineRows?.[0] ? rollPluginOnlineSeconds(onlineRows[0], now) : { today: 0, month: 0, total: 0 };
90
+ const snapshot = snapshots?.[0];
91
+ const snapshotOnline = snapshot ? rollSnapshotSeconds(snapshot, now) : null;
92
+ const pluginUpdated = Number(onlineRows?.[0]?.updated_at) || 0;
93
+ const snapshotUpdated = Number(snapshot?.updated_at) || 0;
94
+ const online = snapshotOnline && snapshotUpdated >= pluginUpdated ? snapshotOnline : pluginOnline;
95
+ const onlineKnown = onlineRows != null || snapshotOnline != null;
96
+ const snapshotBalance = snapshot && snapshot.balance != null ? Number(snapshot.balance) : null;
97
+ const balance = snapshotBalance != null && Number.isFinite(snapshotBalance) ? snapshotBalance : archiveBalance;
98
+ return {
99
+ unit: DEFAULT_ECONOMY_UNIT,
100
+ balance,
101
+ today_text: onlineKnown ? formatOnlineDuration(online.today) : "\u6682\u65F6\u8BFB\u4E0D\u5230",
102
+ month_text: onlineKnown ? formatOnlineDuration(online.month) : "\u6682\u65F6\u8BFB\u4E0D\u5230",
103
+ total_text: onlineKnown ? formatOnlineDuration(online.total) : "\u6682\u65F6\u8BFB\u4E0D\u5230"
104
+ };
105
+ }
106
+ function saveQqAccountProfile(query, input) {
107
+ const now = input.now ?? Date.now();
108
+ query(
109
+ SQL`INSERT INTO sfmc_qq_account_profile
110
+ (player_xuid, player_name, balance, today_seconds, month_seconds, total_seconds, updated_at)
111
+ VALUES
112
+ (${input.playerId}, ${input.playerName}, ${input.balance}, ${input.todaySeconds}, ${input.monthSeconds}, ${input.totalSeconds}, ${now})
113
+ ON CONFLICT(player_xuid) DO UPDATE SET
114
+ player_name = excluded.player_name,
115
+ balance = COALESCE(excluded.balance, sfmc_qq_account_profile.balance),
116
+ today_seconds = COALESCE(excluded.today_seconds, sfmc_qq_account_profile.today_seconds),
117
+ month_seconds = COALESCE(excluded.month_seconds, sfmc_qq_account_profile.month_seconds),
118
+ total_seconds = COALESCE(excluded.total_seconds, sfmc_qq_account_profile.total_seconds),
119
+ updated_at = excluded.updated_at`
120
+ );
121
+ }
122
+ export {
123
+ DEFAULT_ECONOMY_UNIT,
124
+ formatOnlineDuration,
125
+ loadQqAccountProfile,
126
+ rollPluginOnlineSeconds,
127
+ saveQqAccountProfile
128
+ };
129
+ //# sourceMappingURL=qq-account-profile.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/domain/qq-account-profile.ts"],
4
+ "sourcesContent": ["/**\n * domain/qq-account-profile.ts \u2014 QQ\u300C\u6211\u7684\u8D26\u53F7\u300D\u8981\u5C55\u793A\u7684\u7ECF\u6D4E\u4F59\u989D\u548C\u5728\u7EBF\u65F6\u957F\u3002\n *\n * \u4F59\u989D\u8BFB\u7ECF\u6D4E\u63D2\u4EF6\u5F52\u6863\u8868 sfmc_economy_accounts\uFF08\u8BA1\u5206\u677F\u5BF9\u8D26\u540E\u7684\u5FEB\u7167\uFF09\u3002\n * \u5728\u7EBF\u65F6\u957F\u8BFB online-time \u63D2\u4EF6\u8868 sfmc_online_time\uFF0C\u8DE8\u65E5\u3001\u8DE8\u6708\u7684\u6E05\u96F6\u89C4\u5219\u4E0E\u63D2\u4EF6\u79BB\u7EBF\u67E5\u8BE2\u4E00\u81F4\u3002\n * qq-link \u82E5\u5DF2\u628A\u4E24\u4E2A\u670D\u52A1\u7684\u5B9E\u65F6\u7ED3\u679C\u5199\u5165 sfmc_qq_account_profile\uFF0C\u4E14\u6BD4\u63D2\u4EF6\u843D\u5E93\u66F4\u65B0\uFF0C\u5219\u4F18\u5148\u7528\u90A3\u4EFD\u3002\n */\n\nimport { SQL } from \"sql-template-strings\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\n\n/** \u4E0E\u7ECF\u6D4E\u63D2\u4EF6\u9ED8\u8BA4 unitName \u4E00\u81F4\u3002\u914D\u7F6E\u6587\u4EF6\u7F3A\u5931\u65F6\u8D26\u53F7\u9875\u7528\u8FD9\u4E2A\u5355\u4F4D\u3002 */\nexport const DEFAULT_ECONOMY_UNIT = \"\u8282\u64CD\";\n\n/** \u4E0E online-time \u63D2\u4EF6\u9ED8\u8BA4\u65F6\u533A\u4E00\u81F4\uFF0C\u7528\u6765\u5224\u65AD\u300C\u4ECA\u65E5 / \u672C\u6708\u300D\u662F\u5426\u8981\u6E05\u96F6\u3002 */\nconst ONLINE_TIME_TZ_OFFSET_MS = 8 * 3600_000;\n\nexport type QqAccountProfile = {\n unit: string;\n /** \u6CA1\u6709\u8D26\u6237\u884C\u65F6\u6309\u63D2\u4EF6\u7EA6\u5B9A\u89C6\u4E3A 0\uFF1B\u67E5\u8BE2\u5931\u8D25\u65F6\u4E3A null\u3002 */\n balance: number | null;\n today_text: string;\n month_text: string;\n total_text: string;\n};\n\ntype EconomyRow = { account_id?: string; balance?: number; account_type?: string };\ntype OnlineRow = {\n today_seconds?: number;\n month_seconds?: number;\n total_seconds?: number;\n last_date?: string;\n last_month?: string;\n updated_at?: number;\n};\ntype SnapshotRow = {\n balance?: number | null;\n today_seconds?: number | null;\n month_seconds?: number | null;\n total_seconds?: number | null;\n updated_at?: number;\n};\n\n/** \u4E0E online-time \u63D2\u4EF6 formatDuration \u76F8\u540C\u7684\u5C55\u793A\u3002 */\nexport function formatOnlineDuration(seconds: number): string {\n const s = Math.max(0, Math.floor(seconds));\n const days = Math.floor(s / 86400);\n const hours = Math.floor((s % 86400) / 3600);\n const minutes = Math.floor((s % 3600) / 60);\n const secs = s % 60;\n const parts: string[] = [];\n if (days > 0) parts.push(`${days}\u5929`);\n if (hours > 0) parts.push(`${hours}\u65F6`);\n if (minutes > 0) parts.push(`${minutes}\u5206`);\n if (parts.length === 0 || secs > 0) parts.push(`${secs}\u79D2`);\n return parts.join(\"\") || \"0\u79D2\";\n}\n\nfunction dateKey(ms: number): string {\n const local = new Date(ms + ONLINE_TIME_TZ_OFFSET_MS);\n const y = local.getUTCFullYear();\n const m = String(local.getUTCMonth() + 1).padStart(2, \"0\");\n const d = String(local.getUTCDate()).padStart(2, \"0\");\n return `${y}-${m}-${d}`;\n}\n\n/**\n * \u63D2\u4EF6\u79BB\u7EBF\u67E5\u8BE2\u7684\u6E05\u96F6\uFF1Alast_date \u4E0D\u662F\u4ECA\u5929\u5219\u4ECA\u65E5\u4E3A 0\uFF0Clast_month \u4E0D\u662F\u672C\u6708\u5219\u672C\u6708\u4E3A 0\u3002\n * \u5F53\u524D\u7ED9\u8D26\u53F7\u9875\u4F7F\u7528\uFF0C\u907F\u514D\u628A\u6628\u5929\u7684\u65F6\u957F\u663E\u793A\u6210\u4ECA\u65E5\u3002\n */\nexport function rollPluginOnlineSeconds(\n row: { today_seconds?: number; month_seconds?: number; total_seconds?: number; last_date?: string; last_month?: string },\n now = Date.now()\n): { today: number; month: number; total: number } {\n const today = dateKey(now);\n const month = today.slice(0, 7);\n return {\n today: String(row.last_date || \"\") === today ? Number(row.today_seconds) || 0 : 0,\n month: String(row.last_month || \"\") === month ? Number(row.month_seconds) || 0 : 0,\n total: Number(row.total_seconds) || 0,\n };\n}\n\n/** \u67E5\u8BE2\u5931\u8D25\u8FD4\u56DE null\uFF0C\u548C\u300C\u8868\u5728\u4F46\u6CA1\u6709\u8FD9\u4E00\u884C\u300D\u533A\u5206\u5F00\u3002 */\nfunction safeRows<T>(query: QueryFn, sql: ReturnType<typeof SQL>): T[] | null {\n try {\n const rows = query(sql);\n return Array.isArray(rows) ? (rows as T[]) : [];\n } catch {\n return null;\n }\n}\n\nfunction pickEconomyBalance(rows: EconomyRow[], playerId: string, playerName: string): number | null {\n if (!rows.length) return 0;\n const prefixed = `player:${playerId}`;\n const order = [playerId, prefixed, playerName].filter((id) => id);\n for (const id of order) {\n const hit = rows.find((row) => String(row.account_id || \"\") === id);\n if (!hit) continue;\n if (id === playerName && hit.account_type && hit.account_type !== \"player\") continue;\n const balance = Number(hit.balance);\n return Number.isFinite(balance) ? balance : 0;\n }\n return 0;\n}\n\nfunction rollSnapshotSeconds(row: SnapshotRow, now: number): { today: number; month: number; total: number } | null {\n if (row.today_seconds == null && row.month_seconds == null && row.total_seconds == null) return null;\n const updated = Number(row.updated_at) || 0;\n const stampDay = updated > 0 ? dateKey(updated) : \"\";\n const stampMonth = stampDay.slice(0, 7);\n const today = dateKey(now);\n const month = today.slice(0, 7);\n return {\n today: stampDay === today ? Number(row.today_seconds) || 0 : 0,\n month: stampMonth === month ? Number(row.month_seconds) || 0 : 0,\n total: Number(row.total_seconds) || 0,\n };\n}\n\n/**\n * \u7EC4\u88C5\u4E00\u4E2A\u5DF2\u7ED1\u5B9A\u89D2\u8272\u7684\u8D26\u53F7\u9875\u6570\u5B57\u3002\n * \u6CA1\u6709\u8D26\u6237\u6216\u65F6\u957F\u8BB0\u5F55\u65F6\u6309\u63D2\u4EF6\u7EA6\u5B9A\u663E\u793A 0\uFF1B\u8868\u8BFB\u5931\u8D25\u65F6\u4F59\u989D\u4E3A null\u3001\u65F6\u957F\u6587\u6848\u4E3A\u300C\u6682\u65F6\u8BFB\u4E0D\u5230\u300D\u3002\n */\nexport function loadQqAccountProfile(\n query: QueryFn,\n playerId: string,\n playerName: string,\n now = Date.now()\n): QqAccountProfile {\n const id = playerId.trim();\n const name = playerName.trim();\n const economyRows = id\n ? safeRows<EconomyRow>(\n query,\n SQL`SELECT account_id, balance, account_type FROM sfmc_economy_accounts\n WHERE (_deleted_at IS NULL)\n AND (account_id = ${id} OR account_id = ${`player:${id}`} OR account_id = ${name})`\n )\n : [];\n const onlineRows = id\n ? safeRows<OnlineRow>(\n query,\n SQL`SELECT today_seconds, month_seconds, total_seconds, last_date, last_month, updated_at\n FROM sfmc_online_time\n WHERE player_id = ${id} AND (_deleted_at IS NULL)\n LIMIT 1`\n )\n : [];\n const snapshots = id\n ? safeRows<SnapshotRow>(\n query,\n SQL`SELECT balance, today_seconds, month_seconds, total_seconds, updated_at\n FROM sfmc_qq_account_profile WHERE player_xuid = ${id} LIMIT 1`\n )\n : [];\n\n const archiveBalance = economyRows == null ? null : pickEconomyBalance(economyRows, id, name);\n const pluginOnline = onlineRows?.[0] ? rollPluginOnlineSeconds(onlineRows[0], now) : { today: 0, month: 0, total: 0 };\n const snapshot = snapshots?.[0];\n const snapshotOnline = snapshot ? rollSnapshotSeconds(snapshot, now) : null;\n const pluginUpdated = Number(onlineRows?.[0]?.updated_at) || 0;\n const snapshotUpdated = Number(snapshot?.updated_at) || 0;\n const online = snapshotOnline && snapshotUpdated >= pluginUpdated ? snapshotOnline : pluginOnline;\n const onlineKnown = onlineRows != null || snapshotOnline != null;\n const snapshotBalance = snapshot && snapshot.balance != null ? Number(snapshot.balance) : null;\n const balance =\n snapshotBalance != null && Number.isFinite(snapshotBalance) ? snapshotBalance : archiveBalance;\n\n return {\n unit: DEFAULT_ECONOMY_UNIT,\n balance,\n today_text: onlineKnown ? formatOnlineDuration(online.today) : \"\u6682\u65F6\u8BFB\u4E0D\u5230\",\n month_text: onlineKnown ? formatOnlineDuration(online.month) : \"\u6682\u65F6\u8BFB\u4E0D\u5230\",\n total_text: onlineKnown ? formatOnlineDuration(online.total) : \"\u6682\u65F6\u8BFB\u4E0D\u5230\",\n };\n}\n\n/** \u5199\u5165 qq-link \u63A8\u6765\u7684\u5B9E\u65F6\u4F59\u989D\u548C\u5728\u7EBF\u65F6\u957F\u3002\u7F3A\u7684\u6570\u5B57\u4FDD\u7559\u4E0A\u4E00\u6B21\u3002 */\nexport function saveQqAccountProfile(\n query: QueryFn,\n input: {\n playerId: string;\n playerName: string;\n balance: number | null;\n todaySeconds: number | null;\n monthSeconds: number | null;\n totalSeconds: number | null;\n now?: number;\n }\n): void {\n const now = input.now ?? Date.now();\n query(\n SQL`INSERT INTO sfmc_qq_account_profile\n (player_xuid, player_name, balance, today_seconds, month_seconds, total_seconds, updated_at)\n VALUES\n (${input.playerId}, ${input.playerName}, ${input.balance}, ${input.todaySeconds}, ${input.monthSeconds}, ${input.totalSeconds}, ${now})\n ON CONFLICT(player_xuid) DO UPDATE SET\n player_name = excluded.player_name,\n balance = COALESCE(excluded.balance, sfmc_qq_account_profile.balance),\n today_seconds = COALESCE(excluded.today_seconds, sfmc_qq_account_profile.today_seconds),\n month_seconds = COALESCE(excluded.month_seconds, sfmc_qq_account_profile.month_seconds),\n total_seconds = COALESCE(excluded.total_seconds, sfmc_qq_account_profile.total_seconds),\n updated_at = excluded.updated_at`\n );\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -3,11 +3,11 @@
3
3
  *
4
4
  * 聚合策略:
5
5
  * - 玩家进退与死亡(join / leave / death):在时间窗口内收集缓冲,合并为单条消息发送,避免刷屏
6
- * - 关键运行态事件(crash / start):先立即冲刷(flush)当前窗口内的待发送事件,随后立即单独推送
6
+ * - 关键运行态事件(crash / start / stop):先立即冲刷(flush)当前窗口内的待发送事件,随后立即单独推送
7
7
  */
8
8
  import { type QqEventsConfig } from "@sfmc-bds/sdk/node/config";
9
9
  import type { OutboundConfig } from "./bridge.js";
10
- export type QqEventType = "join" | "leave" | "death" | "crash" | "start";
10
+ export type QqEventType = "join" | "leave" | "death" | "crash" | "start" | "stop";
11
11
  export type QqEventPayload = {
12
12
  type: QqEventType;
13
13
  player?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"qq-events.d.ts","sourceRoot":"","sources":["../../src/domain/qq-events.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAEzE,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;AAK9D,0CAA0C;AAC1C,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAuCpC;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,sBAAsB,CAe1E;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAK/D;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,sBAAsB,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAG1F;AAED,KAAK,QAAQ,GAAG;IACd,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,EAAE,MAAM,sBAAsB,CAAC;IACxC,WAAW,EAAE,MAAM,cAAc,CAAC;IAClC,0BAA0B;IAC1B,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;IAC7E,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,KAAK,IAAI,CAAC;IAC7D,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAiB3D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM,CAU9D;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAmBzE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,sBAAsB;oBAqC5C,cAAc,KAAG;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;wBAyBnD,cAAc,EAAE,KAAG;QAC5C,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB;uBA7CuB,IAAI;mBAyDR,IAAI;wBAKC,MAAM;EAKhC;AAED,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
1
+ {"version":3,"file":"qq-events.d.ts","sourceRoot":"","sources":["../../src/domain/qq-events.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAElF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;AAK9D,0CAA0C;AAC1C,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAuCpC;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,sBAAsB,CAgB1E;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAK/D;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,sBAAsB,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAG1F;AAED,KAAK,QAAQ,GAAG;IACd,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,EAAE,MAAM,sBAAsB,CAAC;IACxC,WAAW,EAAE,MAAM,cAAc,CAAC;IAClC,0BAA0B;IAC1B,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;IAC7E,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,KAAK,IAAI,CAAC;IAC7D,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAiB3D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM,CAW9D;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAmBzE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,sBAAsB;oBAsC5C,cAAc,KAAG;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;wBAyBnD,cAAc,EAAE,KAAG;QAC5C,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB;uBA9CuB,IAAI;mBA0DR,IAAI;wBAKC,MAAM;EAKhC;AAED,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
@@ -3,7 +3,7 @@ import {
3
3
  } from "@sfmc-bds/sdk/node/config";
4
4
  import { sendGroupOutbound } from "./bridge.js";
5
5
  const WINDOW_TYPES = /* @__PURE__ */ new Set(["join", "leave", "death"]);
6
- const IMMEDIATE_TYPES = /* @__PURE__ */ new Set(["crash", "start"]);
6
+ const IMMEDIATE_TYPES = /* @__PURE__ */ new Set(["crash", "start", "stop"]);
7
7
  const MAX_WINDOW_EVENTS = 20;
8
8
  const CAUSE_ZH = {
9
9
  anvil: "\u94C1\u7827",
@@ -50,7 +50,8 @@ function resolveQqEventsConfig(raw) {
50
50
  leave: o.leave === void 0 ? DEFAULT_QQ_EVENTS.leave : o.leave === true,
51
51
  death: o.death === void 0 ? DEFAULT_QQ_EVENTS.death : o.death === true,
52
52
  crash: o.crash === void 0 ? DEFAULT_QQ_EVENTS.crash : o.crash === true,
53
- start: o.start === void 0 ? DEFAULT_QQ_EVENTS.start : o.start === true
53
+ start: o.start === void 0 ? DEFAULT_QQ_EVENTS.start : o.start === true,
54
+ stop: o.stop === void 0 ? DEFAULT_QQ_EVENTS.stop : o.stop === true
54
55
  };
55
56
  }
56
57
  function localizeCause(cause) {
@@ -90,6 +91,7 @@ function formatImmediateBody(ev) {
90
91
  const d = String(ev.detail ?? "").trim();
91
92
  return d ? `[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8 (${d})` : "[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8";
92
93
  }
94
+ if (ev.type === "stop") return "[MC\u4E8B\u4EF6] BDS \u5DF2\u505C\u6B62";
93
95
  return `[MC\u4E8B\u4EF6] ${ev.type}`;
94
96
  }
95
97
  function normalizeEventPayload(raw) {
@@ -129,7 +131,8 @@ function createQqEventsAggregator(deps) {
129
131
  function flushWindow() {
130
132
  clearTimer();
131
133
  if (buffer.length === 0) return;
132
- const snapshot = buffer;
134
+ const cfg = deps.getConfig();
135
+ const snapshot = buffer.filter((event) => isEventTypeEnabled(cfg, event.type));
133
136
  buffer = [];
134
137
  const text = formatWindowBody(snapshot);
135
138
  if (text.split("\n").length > 1) send(text);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/domain/qq-events.ts"],
4
- "sourcesContent": ["/**\n * domain/qq-events.ts \u2014 Minecraft \u670D\u52A1\u5668\u4E8B\u4EF6\u7FA4\u63A8\u9001\u805A\u5408\u8282\u6D41\u5668\n *\n * \u805A\u5408\u7B56\u7565\uFF1A\n * - \u73A9\u5BB6\u8FDB\u9000\u4E0E\u6B7B\u4EA1\uFF08join / leave / death\uFF09\uFF1A\u5728\u65F6\u95F4\u7A97\u53E3\u5185\u6536\u96C6\u7F13\u51B2\uFF0C\u5408\u5E76\u4E3A\u5355\u6761\u6D88\u606F\u53D1\u9001\uFF0C\u907F\u514D\u5237\u5C4F\n * - \u5173\u952E\u8FD0\u884C\u6001\u4E8B\u4EF6\uFF08crash / start\uFF09\uFF1A\u5148\u7ACB\u5373\u51B2\u5237\uFF08flush\uFF09\u5F53\u524D\u7A97\u53E3\u5185\u7684\u5F85\u53D1\u9001\u4E8B\u4EF6\uFF0C\u968F\u540E\u7ACB\u5373\u5355\u72EC\u63A8\u9001\n */\n\nimport {\n DEFAULT_QQ_EVENTS,\n type QqEventsConfig,\n} from \"@sfmc-bds/sdk/node/config\";\n\nimport type { OutboundConfig } from \"./bridge.js\";\nimport { sendGroupOutbound } from \"./bridge.js\";\n\nexport type QqEventType = \"join\" | \"leave\" | \"death\" | \"crash\" | \"start\";\n\nexport type QqEventPayload = {\n type: QqEventType;\n player?: string;\n cause?: string;\n detail?: string;\n};\n\nexport type ResolvedQqEventsConfig = Required<QqEventsConfig>;\n\nconst WINDOW_TYPES = new Set<QqEventType>([\"join\", \"leave\", \"death\"]);\nconst IMMEDIATE_TYPES = new Set<QqEventType>([\"crash\", \"start\"]);\n\n/** \u7A97\u53E3\u5185\u6761\u6570\u4E0A\u9650\uFF1A\u8FBE\u5230\u4E0A\u9650\u65F6\u63D0\u524D\u89E6\u53D1 flush\uFF0C\u907F\u514D\u5355\u6B21\u79EF\u538B\u8FC7\u591A\u5185\u5BB9\u3002 */\nexport const MAX_WINDOW_EVENTS = 20;\n\n/** SAPI damageSource.cause \u2192 \u4E2D\u6587\u6620\u5C04\u5B57\u5178\uFF08\u672A\u6536\u5F55\u65F6\u4FDD\u7559\u539F\u82F1\u6587\u6807\u8BC6\uFF09\u3002 */\nconst CAUSE_ZH: Record<string, string> = {\n anvil: \"\u94C1\u7827\",\n blockExplosion: \"\u65B9\u5757\u7206\u70B8\",\n campfire: \"\u8425\u706B\",\n drowning: \"\u6EBA\u6C34\",\n entityAttack: \"\u751F\u7269\u653B\u51FB\",\n entityExplosion: \"\u5B9E\u4F53\u7206\u70B8\",\n fall: \"\u5760\u843D\",\n fallingBlock: \"\u843D\u77F3\",\n fire: \"\u706B\u7130\",\n fireTick: \"\u707C\u70E7\",\n flyIntoWall: \"\u649E\u5899\",\n freezing: \"\u51BB\u7ED3\",\n lava: \"\u5CA9\u6D46\",\n lightning: \"\u96F7\u51FB\",\n magic: \"\u9B54\u6CD5\",\n magma: \"\u5CA9\u6D46\u5757\",\n none: \"\u672A\u77E5\",\n override: \"\u5F3A\u5236\",\n piston: \"\u6D3B\u585E\",\n projectile: \"\u5F39\u5C04\u7269\",\n stalactite: \"\u949F\u4E73\u77F3\",\n stalagmite: \"\u77F3\u7B0B\",\n starve: \"\u9965\u997F\",\n suffocation: \"\u7A92\u606F\",\n suicide: \"\u81EA\u6740\",\n thorns: \"\u8346\u68D8\",\n void: \"\u865A\u7A7A\",\n wither: \"\u51CB\u96F6\",\n // \u5E38\u89C1\u751F\u7269\u540D\uFF08\u82E5\u4E0A\u5C42\u5C06\u5B9E\u4F53\u540D\u586B\u5145\u81F3 cause \u5B57\u6BB5\uFF09\n zombie: \"\u50F5\u5C38\",\n skeleton: \"\u9AB7\u9AC5\",\n creeper: \"\u82E6\u529B\u6015\",\n player: \"\u73A9\u5BB6\",\n};\n\n/**\n * \u89E3\u6790\u5E76\u586B\u5145 QQ \u4E8B\u4EF6\u63A8\u9001\u914D\u7F6E\u7684\u9ED8\u8BA4\u503C\u3002\n *\n * @param raw \u539F\u59CB\u914D\u7F6E\u5BF9\u8C61\u3002\n * @returns \u89C4\u8303\u5316\u7684\u5B8C\u6574\u914D\u7F6E\u5BF9\u8C61\u3002\n */\nexport function resolveQqEventsConfig(raw: unknown): ResolvedQqEventsConfig {\n const o = raw && typeof raw === \"object\" ? (raw as Record<string, unknown>) : {};\n const windowSec = Number(o.window_sec ?? DEFAULT_QQ_EVENTS.window_sec);\n return {\n enabled: o.enabled === undefined ? DEFAULT_QQ_EVENTS.enabled : o.enabled === true,\n window_sec:\n Number.isFinite(windowSec) && windowSec >= 5\n ? Math.min(600, Math.floor(windowSec))\n : DEFAULT_QQ_EVENTS.window_sec,\n join: o.join === undefined ? DEFAULT_QQ_EVENTS.join : o.join === true,\n leave: o.leave === undefined ? DEFAULT_QQ_EVENTS.leave : o.leave === true,\n death: o.death === undefined ? DEFAULT_QQ_EVENTS.death : o.death === true,\n crash: o.crash === undefined ? DEFAULT_QQ_EVENTS.crash : o.crash === true,\n start: o.start === undefined ? DEFAULT_QQ_EVENTS.start : o.start === true,\n };\n}\n\n/**\n * \u5C06 SAPI \u539F\u751F\u4F24\u5BB3\u6765\u6E90\u4EE3\u7801\uFF08damageSource.cause\uFF09\u8F6C\u6362\u4E3A\u6613\u8BFB\u7684\u4E2D\u6587\u6587\u672C\u3002\n *\n * @param cause \u4F24\u5BB3\u6765\u6E90\u6807\u8BC6\u3002\n * @returns \u4E2D\u6587\u4F24\u5BB3\u63CF\u8FF0\uFF08\u672A\u6536\u5F55\u65F6\u4FDD\u7559\u539F\u82F1\u6587\u4EE3\u7801\uFF09\u3002\n */\nexport function localizeCause(cause: string | undefined): string {\n if (!cause) return \"\";\n const key = cause.trim();\n if (!key) return \"\";\n return CAUSE_ZH[key] ?? CAUSE_ZH[key.toLowerCase()] ?? key;\n}\n\nexport function isEventTypeEnabled(cfg: ResolvedQqEventsConfig, type: QqEventType): boolean {\n if (!cfg.enabled) return false;\n return cfg[type] === true;\n}\n\ntype Buffered = {\n type: \"join\" | \"leave\" | \"death\";\n player: string;\n cause?: string;\n};\n\nexport type QqEventsAggregatorDeps = {\n getConfig: () => ResolvedQqEventsConfig;\n getOutbound: () => OutboundConfig;\n /** \u53EF\u9009\u6CE8\u5165\uFF1A\u4F9B\u5355\u5143\u6D4B\u8BD5\u6A21\u62DF\u65F6\u949F\u4E0E\u5B9A\u65F6\u5668\u3002 */\n setTimeoutFn?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;\n clearTimeoutFn?: (id: ReturnType<typeof setTimeout>) => void;\n send?: (text: string) => void;\n};\n\n/**\n * \u683C\u5F0F\u5316\u65F6\u95F4\u7A97\u53E3\u5185\u7684\u805A\u5408\u4E8B\u4EF6\u6587\u672C\u3002\n *\n * @param events \u7F13\u51B2\u533A\u5185\u7684\u4E8B\u4EF6\u5217\u8868\u3002\n * @returns \u683C\u5F0F\u5316\u540E\u7684\u591A\u884C\u6D88\u606F\u5B57\u7B26\u4E32\u3002\n */\nexport function formatWindowBody(events: Buffered[]): string {\n const joins: string[] = [];\n const leaves: string[] = [];\n const deaths: string[] = [];\n for (const e of events) {\n if (e.type === \"join\") joins.push(e.player);\n else if (e.type === \"leave\") leaves.push(e.player);\n else {\n const c = localizeCause(e.cause);\n deaths.push(c ? `${e.player}\uFF08${c}\uFF09` : e.player);\n }\n }\n const lines: string[] = [\"[MC\u4E8B\u4EF6]\"];\n if (joins.length) lines.push(`\u4E0A\u7EBF\uFF1A${joins.join(\"\u3001\")}`);\n if (leaves.length) lines.push(`\u4E0B\u7EBF\uFF1A${leaves.join(\"\u3001\")}`);\n if (deaths.length) lines.push(`\u6B7B\u4EA1\uFF1A${deaths.join(\"\u3001\")}`);\n return lines.join(\"\\n\");\n}\n\n/**\n * \u683C\u5F0F\u5316\u9AD8\u4F18\u5148\u7EA7\u7684\u975E\u805A\u5408\u5373\u65F6\u4E8B\u4EF6\u6587\u672C\uFF08\u5982 BDS \u5D29\u6E83\u6216\u542F\u52A8\u5C31\u7EEA\uFF09\u3002\n *\n * @param ev \u5373\u65F6\u4E8B\u4EF6\u8F7D\u8377\u3002\n * @returns \u683C\u5F0F\u5316\u540E\u7684\u4E8B\u4EF6\u901A\u77E5\u6587\u672C\u3002\n */\nexport function formatImmediateBody(ev: QqEventPayload): string {\n if (ev.type === \"crash\") {\n const d = String(ev.detail ?? \"\").trim();\n return d ? `[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA (${d})` : \"[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA\";\n }\n if (ev.type === \"start\") {\n const d = String(ev.detail ?? \"\").trim();\n return d ? `[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8 (${d})` : \"[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8\";\n }\n return `[MC\u4E8B\u4EF6] ${ev.type}`;\n}\n\nexport function normalizeEventPayload(raw: unknown): QqEventPayload | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const type = String(o.type ?? \"\").trim() as QqEventType;\n if (!WINDOW_TYPES.has(type) && !IMMEDIATE_TYPES.has(type)) return null;\n const out: QqEventPayload = { type };\n if (o.player !== undefined) {\n const player = String(o.player).trim();\n if (player) out.player = player;\n }\n if (o.cause !== undefined) {\n const cause = String(o.cause).trim();\n if (cause) out.cause = cause;\n }\n if (o.detail !== undefined) {\n const detail = String(o.detail).trim();\n if (detail) out.detail = detail;\n }\n return out;\n}\n\n/**\n * \u521B\u5EFA QQ \u670D\u52A1\u5668\u4E8B\u4EF6\u805A\u5408\u8282\u6D41\u5668\u5B9E\u4F8B\u3002\n *\n * @param deps \u4F9D\u8D56\u6CE8\u5165\u5BF9\u8C61\uFF08\u5305\u542B\u914D\u7F6E\u83B7\u53D6\u3001\u51FA\u7AD9\u53D1\u9001\u53CA\u5B9A\u65F6\u5668\u5B9E\u73B0\uFF09\u3002\n * @returns \u5305\u542B push, flush, cancel, getPendingCount \u7B49\u65B9\u6CD5\u7684\u805A\u5408\u5668\u63A7\u5236\u5668\u3002\n */\nexport function createQqEventsAggregator(deps: QqEventsAggregatorDeps) {\n\n const setTimeoutFn = deps.setTimeoutFn ?? setTimeout;\n const clearTimeoutFn = deps.clearTimeoutFn ?? clearTimeout;\n const send =\n deps.send ??\n ((text: string) => {\n sendGroupOutbound(deps.getOutbound(), text);\n });\n\n let buffer: Buffered[] = [];\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function clearTimer(): void {\n if (timer != null) {\n clearTimeoutFn(timer);\n timer = null;\n }\n }\n\n function flushWindow(): void {\n clearTimer();\n if (buffer.length === 0) return;\n const snapshot = buffer;\n buffer = [];\n const text = formatWindowBody(snapshot);\n if (text.split(\"\\n\").length > 1) send(text);\n }\n\n function scheduleFlush(windowSec: number): void {\n if (timer != null) return;\n timer = setTimeoutFn(() => {\n timer = null;\n flushWindow();\n }, windowSec * 1000);\n }\n\n function ingestOne(ev: QqEventPayload): { accepted: boolean; reason?: string } {\n const cfg = deps.getConfig();\n if (!isEventTypeEnabled(cfg, ev.type)) {\n return { accepted: false, reason: \"disabled\" };\n }\n\n if (IMMEDIATE_TYPES.has(ev.type)) {\n flushWindow();\n send(formatImmediateBody(ev));\n return { accepted: true };\n }\n\n const player = String(ev.player ?? \"\").trim();\n if (!player) return { accepted: false, reason: \"missing_player\" };\n\n buffer.push({\n type: ev.type as \"join\" | \"leave\" | \"death\",\n player,\n ...(ev.cause ? { cause: ev.cause } : {}),\n });\n scheduleFlush(cfg.window_sec);\n if (buffer.length >= MAX_WINDOW_EVENTS) flushWindow();\n return { accepted: true };\n }\n\n function ingestMany(items: QqEventPayload[]): {\n accepted: number;\n rejected: number;\n } {\n let accepted = 0;\n let rejected = 0;\n for (const ev of items) {\n const r = ingestOne(ev);\n if (r.accepted) accepted += 1;\n else rejected += 1;\n }\n return { accepted, rejected };\n }\n\n /** \u6D4B\u8BD5 / \u5173\u505C\uFF1A\u51B2\u5237\u5E76\u6E05\u5B9A\u65F6\u5668 */\n function dispose(): void {\n flushWindow();\n }\n\n /** \u4EC5\u6D4B\u7528\uFF1A\u5F53\u524D\u7F13\u51B2\u957F\u5EA6 */\n function pendingCount(): number {\n return buffer.length;\n }\n\n return { ingestOne, ingestMany, flushWindow, dispose, pendingCount };\n}\n\nexport type QqEventsAggregator = ReturnType<typeof createQqEventsAggregator>;\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * domain/qq-events.ts \u2014 Minecraft \u670D\u52A1\u5668\u4E8B\u4EF6\u7FA4\u63A8\u9001\u805A\u5408\u8282\u6D41\u5668\n *\n * \u805A\u5408\u7B56\u7565\uFF1A\n * - \u73A9\u5BB6\u8FDB\u9000\u4E0E\u6B7B\u4EA1\uFF08join / leave / death\uFF09\uFF1A\u5728\u65F6\u95F4\u7A97\u53E3\u5185\u6536\u96C6\u7F13\u51B2\uFF0C\u5408\u5E76\u4E3A\u5355\u6761\u6D88\u606F\u53D1\u9001\uFF0C\u907F\u514D\u5237\u5C4F\n * - \u5173\u952E\u8FD0\u884C\u6001\u4E8B\u4EF6\uFF08crash / start / stop\uFF09\uFF1A\u5148\u7ACB\u5373\u51B2\u5237\uFF08flush\uFF09\u5F53\u524D\u7A97\u53E3\u5185\u7684\u5F85\u53D1\u9001\u4E8B\u4EF6\uFF0C\u968F\u540E\u7ACB\u5373\u5355\u72EC\u63A8\u9001\n */\n\nimport {\n DEFAULT_QQ_EVENTS,\n type QqEventsConfig,\n} from \"@sfmc-bds/sdk/node/config\";\n\nimport type { OutboundConfig } from \"./bridge.js\";\nimport { sendGroupOutbound } from \"./bridge.js\";\n\nexport type QqEventType = \"join\" | \"leave\" | \"death\" | \"crash\" | \"start\" | \"stop\";\n\nexport type QqEventPayload = {\n type: QqEventType;\n player?: string;\n cause?: string;\n detail?: string;\n};\n\nexport type ResolvedQqEventsConfig = Required<QqEventsConfig>;\n\nconst WINDOW_TYPES = new Set<QqEventType>([\"join\", \"leave\", \"death\"]);\nconst IMMEDIATE_TYPES = new Set<QqEventType>([\"crash\", \"start\", \"stop\"]);\n\n/** \u7A97\u53E3\u5185\u6761\u6570\u4E0A\u9650\uFF1A\u8FBE\u5230\u4E0A\u9650\u65F6\u63D0\u524D\u89E6\u53D1 flush\uFF0C\u907F\u514D\u5355\u6B21\u79EF\u538B\u8FC7\u591A\u5185\u5BB9\u3002 */\nexport const MAX_WINDOW_EVENTS = 20;\n\n/** SAPI damageSource.cause \u2192 \u4E2D\u6587\u6620\u5C04\u5B57\u5178\uFF08\u672A\u6536\u5F55\u65F6\u4FDD\u7559\u539F\u82F1\u6587\u6807\u8BC6\uFF09\u3002 */\nconst CAUSE_ZH: Record<string, string> = {\n anvil: \"\u94C1\u7827\",\n blockExplosion: \"\u65B9\u5757\u7206\u70B8\",\n campfire: \"\u8425\u706B\",\n drowning: \"\u6EBA\u6C34\",\n entityAttack: \"\u751F\u7269\u653B\u51FB\",\n entityExplosion: \"\u5B9E\u4F53\u7206\u70B8\",\n fall: \"\u5760\u843D\",\n fallingBlock: \"\u843D\u77F3\",\n fire: \"\u706B\u7130\",\n fireTick: \"\u707C\u70E7\",\n flyIntoWall: \"\u649E\u5899\",\n freezing: \"\u51BB\u7ED3\",\n lava: \"\u5CA9\u6D46\",\n lightning: \"\u96F7\u51FB\",\n magic: \"\u9B54\u6CD5\",\n magma: \"\u5CA9\u6D46\u5757\",\n none: \"\u672A\u77E5\",\n override: \"\u5F3A\u5236\",\n piston: \"\u6D3B\u585E\",\n projectile: \"\u5F39\u5C04\u7269\",\n stalactite: \"\u949F\u4E73\u77F3\",\n stalagmite: \"\u77F3\u7B0B\",\n starve: \"\u9965\u997F\",\n suffocation: \"\u7A92\u606F\",\n suicide: \"\u81EA\u6740\",\n thorns: \"\u8346\u68D8\",\n void: \"\u865A\u7A7A\",\n wither: \"\u51CB\u96F6\",\n // \u5E38\u89C1\u751F\u7269\u540D\uFF08\u82E5\u4E0A\u5C42\u5C06\u5B9E\u4F53\u540D\u586B\u5145\u81F3 cause \u5B57\u6BB5\uFF09\n zombie: \"\u50F5\u5C38\",\n skeleton: \"\u9AB7\u9AC5\",\n creeper: \"\u82E6\u529B\u6015\",\n player: \"\u73A9\u5BB6\",\n};\n\n/**\n * \u89E3\u6790\u5E76\u586B\u5145 QQ \u4E8B\u4EF6\u63A8\u9001\u914D\u7F6E\u7684\u9ED8\u8BA4\u503C\u3002\n *\n * @param raw \u539F\u59CB\u914D\u7F6E\u5BF9\u8C61\u3002\n * @returns \u89C4\u8303\u5316\u7684\u5B8C\u6574\u914D\u7F6E\u5BF9\u8C61\u3002\n */\nexport function resolveQqEventsConfig(raw: unknown): ResolvedQqEventsConfig {\n const o = raw && typeof raw === \"object\" ? (raw as Record<string, unknown>) : {};\n const windowSec = Number(o.window_sec ?? DEFAULT_QQ_EVENTS.window_sec);\n return {\n enabled: o.enabled === undefined ? DEFAULT_QQ_EVENTS.enabled : o.enabled === true,\n window_sec:\n Number.isFinite(windowSec) && windowSec >= 5\n ? Math.min(600, Math.floor(windowSec))\n : DEFAULT_QQ_EVENTS.window_sec,\n join: o.join === undefined ? DEFAULT_QQ_EVENTS.join : o.join === true,\n leave: o.leave === undefined ? DEFAULT_QQ_EVENTS.leave : o.leave === true,\n death: o.death === undefined ? DEFAULT_QQ_EVENTS.death : o.death === true,\n crash: o.crash === undefined ? DEFAULT_QQ_EVENTS.crash : o.crash === true,\n start: o.start === undefined ? DEFAULT_QQ_EVENTS.start : o.start === true,\n stop: o.stop === undefined ? DEFAULT_QQ_EVENTS.stop : o.stop === true,\n };\n}\n\n/**\n * \u5C06 SAPI \u539F\u751F\u4F24\u5BB3\u6765\u6E90\u4EE3\u7801\uFF08damageSource.cause\uFF09\u8F6C\u6362\u4E3A\u6613\u8BFB\u7684\u4E2D\u6587\u6587\u672C\u3002\n *\n * @param cause \u4F24\u5BB3\u6765\u6E90\u6807\u8BC6\u3002\n * @returns \u4E2D\u6587\u4F24\u5BB3\u63CF\u8FF0\uFF08\u672A\u6536\u5F55\u65F6\u4FDD\u7559\u539F\u82F1\u6587\u4EE3\u7801\uFF09\u3002\n */\nexport function localizeCause(cause: string | undefined): string {\n if (!cause) return \"\";\n const key = cause.trim();\n if (!key) return \"\";\n return CAUSE_ZH[key] ?? CAUSE_ZH[key.toLowerCase()] ?? key;\n}\n\nexport function isEventTypeEnabled(cfg: ResolvedQqEventsConfig, type: QqEventType): boolean {\n if (!cfg.enabled) return false;\n return cfg[type] === true;\n}\n\ntype Buffered = {\n type: \"join\" | \"leave\" | \"death\";\n player: string;\n cause?: string;\n};\n\nexport type QqEventsAggregatorDeps = {\n getConfig: () => ResolvedQqEventsConfig;\n getOutbound: () => OutboundConfig;\n /** \u53EF\u9009\u6CE8\u5165\uFF1A\u4F9B\u5355\u5143\u6D4B\u8BD5\u6A21\u62DF\u65F6\u949F\u4E0E\u5B9A\u65F6\u5668\u3002 */\n setTimeoutFn?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;\n clearTimeoutFn?: (id: ReturnType<typeof setTimeout>) => void;\n send?: (text: string) => void;\n};\n\n/**\n * \u683C\u5F0F\u5316\u65F6\u95F4\u7A97\u53E3\u5185\u7684\u805A\u5408\u4E8B\u4EF6\u6587\u672C\u3002\n *\n * @param events \u7F13\u51B2\u533A\u5185\u7684\u4E8B\u4EF6\u5217\u8868\u3002\n * @returns \u683C\u5F0F\u5316\u540E\u7684\u591A\u884C\u6D88\u606F\u5B57\u7B26\u4E32\u3002\n */\nexport function formatWindowBody(events: Buffered[]): string {\n const joins: string[] = [];\n const leaves: string[] = [];\n const deaths: string[] = [];\n for (const e of events) {\n if (e.type === \"join\") joins.push(e.player);\n else if (e.type === \"leave\") leaves.push(e.player);\n else {\n const c = localizeCause(e.cause);\n deaths.push(c ? `${e.player}\uFF08${c}\uFF09` : e.player);\n }\n }\n const lines: string[] = [\"[MC\u4E8B\u4EF6]\"];\n if (joins.length) lines.push(`\u4E0A\u7EBF\uFF1A${joins.join(\"\u3001\")}`);\n if (leaves.length) lines.push(`\u4E0B\u7EBF\uFF1A${leaves.join(\"\u3001\")}`);\n if (deaths.length) lines.push(`\u6B7B\u4EA1\uFF1A${deaths.join(\"\u3001\")}`);\n return lines.join(\"\\n\");\n}\n\n/**\n * \u683C\u5F0F\u5316\u9AD8\u4F18\u5148\u7EA7\u7684\u975E\u805A\u5408\u5373\u65F6\u4E8B\u4EF6\u6587\u672C\uFF08\u5982 BDS \u5D29\u6E83\u6216\u542F\u52A8\u5C31\u7EEA\uFF09\u3002\n *\n * @param ev \u5373\u65F6\u4E8B\u4EF6\u8F7D\u8377\u3002\n * @returns \u683C\u5F0F\u5316\u540E\u7684\u4E8B\u4EF6\u901A\u77E5\u6587\u672C\u3002\n */\nexport function formatImmediateBody(ev: QqEventPayload): string {\n if (ev.type === \"crash\") {\n const d = String(ev.detail ?? \"\").trim();\n return d ? `[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA (${d})` : \"[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA\";\n }\n if (ev.type === \"start\") {\n const d = String(ev.detail ?? \"\").trim();\n return d ? `[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8 (${d})` : \"[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8\";\n }\n if (ev.type === \"stop\") return \"[MC\u4E8B\u4EF6] BDS \u5DF2\u505C\u6B62\";\n return `[MC\u4E8B\u4EF6] ${ev.type}`;\n}\n\nexport function normalizeEventPayload(raw: unknown): QqEventPayload | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const type = String(o.type ?? \"\").trim() as QqEventType;\n if (!WINDOW_TYPES.has(type) && !IMMEDIATE_TYPES.has(type)) return null;\n const out: QqEventPayload = { type };\n if (o.player !== undefined) {\n const player = String(o.player).trim();\n if (player) out.player = player;\n }\n if (o.cause !== undefined) {\n const cause = String(o.cause).trim();\n if (cause) out.cause = cause;\n }\n if (o.detail !== undefined) {\n const detail = String(o.detail).trim();\n if (detail) out.detail = detail;\n }\n return out;\n}\n\n/**\n * \u521B\u5EFA QQ \u670D\u52A1\u5668\u4E8B\u4EF6\u805A\u5408\u8282\u6D41\u5668\u5B9E\u4F8B\u3002\n *\n * @param deps \u4F9D\u8D56\u6CE8\u5165\u5BF9\u8C61\uFF08\u5305\u542B\u914D\u7F6E\u83B7\u53D6\u3001\u51FA\u7AD9\u53D1\u9001\u53CA\u5B9A\u65F6\u5668\u5B9E\u73B0\uFF09\u3002\n * @returns \u5305\u542B push, flush, cancel, getPendingCount \u7B49\u65B9\u6CD5\u7684\u805A\u5408\u5668\u63A7\u5236\u5668\u3002\n */\nexport function createQqEventsAggregator(deps: QqEventsAggregatorDeps) {\n\n const setTimeoutFn = deps.setTimeoutFn ?? setTimeout;\n const clearTimeoutFn = deps.clearTimeoutFn ?? clearTimeout;\n const send =\n deps.send ??\n ((text: string) => {\n sendGroupOutbound(deps.getOutbound(), text);\n });\n\n let buffer: Buffered[] = [];\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function clearTimer(): void {\n if (timer != null) {\n clearTimeoutFn(timer);\n timer = null;\n }\n }\n\n function flushWindow(): void {\n clearTimer();\n if (buffer.length === 0) return;\n const cfg = deps.getConfig();\n const snapshot = buffer.filter((event) => isEventTypeEnabled(cfg, event.type));\n buffer = [];\n const text = formatWindowBody(snapshot);\n if (text.split(\"\\n\").length > 1) send(text);\n }\n\n function scheduleFlush(windowSec: number): void {\n if (timer != null) return;\n timer = setTimeoutFn(() => {\n timer = null;\n flushWindow();\n }, windowSec * 1000);\n }\n\n function ingestOne(ev: QqEventPayload): { accepted: boolean; reason?: string } {\n const cfg = deps.getConfig();\n if (!isEventTypeEnabled(cfg, ev.type)) {\n return { accepted: false, reason: \"disabled\" };\n }\n\n if (IMMEDIATE_TYPES.has(ev.type)) {\n flushWindow();\n send(formatImmediateBody(ev));\n return { accepted: true };\n }\n\n const player = String(ev.player ?? \"\").trim();\n if (!player) return { accepted: false, reason: \"missing_player\" };\n\n buffer.push({\n type: ev.type as \"join\" | \"leave\" | \"death\",\n player,\n ...(ev.cause ? { cause: ev.cause } : {}),\n });\n scheduleFlush(cfg.window_sec);\n if (buffer.length >= MAX_WINDOW_EVENTS) flushWindow();\n return { accepted: true };\n }\n\n function ingestMany(items: QqEventPayload[]): {\n accepted: number;\n rejected: number;\n } {\n let accepted = 0;\n let rejected = 0;\n for (const ev of items) {\n const r = ingestOne(ev);\n if (r.accepted) accepted += 1;\n else rejected += 1;\n }\n return { accepted, rejected };\n }\n\n /** \u6D4B\u8BD5 / \u5173\u505C\uFF1A\u51B2\u5237\u5E76\u6E05\u5B9A\u65F6\u5668 */\n function dispose(): void {\n flushWindow();\n }\n\n /** \u4EC5\u6D4B\u7528\uFF1A\u5F53\u524D\u7F13\u51B2\u957F\u5EA6 */\n function pendingCount(): number {\n return buffer.length;\n }\n\n return { ingestOne, ingestMany, flushWindow, dispose, pendingCount };\n}\n\nexport type QqEventsAggregator = ReturnType<typeof createQqEventsAggregator>;\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/domain/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAoGjD"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/domain/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAmJjD"}
@@ -27,9 +27,44 @@ function initSchema(db) {
27
27
  level INTEGER DEFAULT 0,
28
28
  total_xp INTEGER DEFAULT 0,
29
29
  tags TEXT DEFAULT '',
30
+ spawn_dimension TEXT DEFAULT '',
31
+ spawn_x REAL,
32
+ spawn_y REAL,
33
+ spawn_z REAL,
34
+ dimension TEXT DEFAULT '',
35
+ x REAL,
36
+ y REAL,
37
+ z REAL,
38
+ game_mode TEXT DEFAULT '',
39
+ snapshot_hash TEXT DEFAULT '',
40
+ active_channel TEXT DEFAULT '',
41
+ subscribed_channels TEXT DEFAULT '[]',
30
42
  updated_at INTEGER DEFAULT 0
31
43
  )`
32
44
  );
45
+ const playerColumns = new Map(
46
+ db.prepare(`PRAGMA table_info("sfmc_players")`).all().map((column) => [
47
+ column.name,
48
+ true
49
+ ])
50
+ );
51
+ const missingPlayerColumns = [
52
+ ["spawn_dimension", "TEXT DEFAULT ''"],
53
+ ["spawn_x", "REAL"],
54
+ ["spawn_y", "REAL"],
55
+ ["spawn_z", "REAL"],
56
+ ["dimension", "TEXT DEFAULT ''"],
57
+ ["x", "REAL"],
58
+ ["y", "REAL"],
59
+ ["z", "REAL"],
60
+ ["game_mode", "TEXT DEFAULT ''"],
61
+ ["snapshot_hash", "TEXT DEFAULT ''"],
62
+ ["active_channel", "TEXT DEFAULT ''"],
63
+ ["subscribed_channels", "TEXT DEFAULT '[]'"]
64
+ ];
65
+ for (const [name, definition] of missingPlayerColumns) {
66
+ if (!playerColumns.has(name)) db.exec(`ALTER TABLE sfmc_players ADD COLUMN "${name}" ${definition}`);
67
+ }
33
68
  db.exec(
34
69
  /* sql */
35
70
  `
@@ -125,6 +160,19 @@ function initSchema(db) {
125
160
  `
126
161
  CREATE INDEX IF NOT EXISTS idx_qq_admin_actions_status ON sfmc_qq_admin_actions(status, created_at)`
127
162
  );
163
+ db.exec(
164
+ /* sql */
165
+ `
166
+ CREATE TABLE IF NOT EXISTS sfmc_qq_account_profile (
167
+ player_xuid TEXT PRIMARY KEY NOT NULL,
168
+ player_name TEXT NOT NULL DEFAULT '',
169
+ balance INTEGER,
170
+ today_seconds INTEGER,
171
+ month_seconds INTEGER,
172
+ total_seconds INTEGER,
173
+ updated_at INTEGER NOT NULL
174
+ )`
175
+ );
128
176
  }
129
177
  export {
130
178
  initSchema
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/domain/schema.ts"],
4
- "sourcesContent": ["/**\n * domain/schema.ts \u2014 \u6570\u636E\u5E93 Schema \u521D\u59CB\u5316\u5B9A\u4E49\uFF08\u5E73\u53F0\u6838\u5FC3\u5E95\u5EA7\u8868\uFF09\n *\n * \u96C6\u4E2D\u7BA1\u7406\u5E73\u53F0\u6838\u5FC3\u6570\u636E\u8868\u7684 CREATE TABLE IF NOT EXISTS \u4E0E CREATE INDEX IF NOT EXISTS DDL \u8BED\u53E5\u3002\n * \u8C03\u7528\u65B9\u5F0F\uFF1AinitSchema(db) \u2014\u2014 \u5728 openDatabase \u4E4B\u540E\u3001\u4E1A\u52A1\u67E5\u8BE2\u542F\u52A8\u524D\u6267\u884C\u3002\n *\n * \u67B6\u6784\u7EA6\u5B9A\uFF1A\n * \u4E1A\u52A1\u6A21\u5757\uFF08activity-log, coop, land, chat, economy \u7B49\uFF09\u79C1\u6709\u6570\u636E\u8868\u5747\u7531\u5404\u81EA\u6A21\u5757\u5728 SAPI \u8FD0\u884C\u671F\u901A\u8FC7\n * db.defineTable(...) \u81EA\u4E3B\u58F0\u660E\u4E0E\u7EF4\u62A4\uFF0C\u4E0D\u5728\u6B64\u5904\u9759\u6001\u786C\u7F16\u7801\uFF0C\u907F\u514D\u5217\u5B9A\u4E49\u4E0E\u7D22\u5F15\u51B2\u7A81\u3002\n *\n * \u6B64\u5904\u4EC5\u5B9A\u4E49\u5E73\u53F0\u6838\u5FC3\u8FD0\u884C\u3001\u8DE8\u670D\u52A1\u5171\u4EAB\uFF08qq-bridge / HTTP \u7AEF\u70B9 / status \u63A2\u6D3B\uFF09\u7684\u57FA\u7840\u5E95\u5EA7\u8868\uFF1A\n * 1. \u4E16\u754C\u4E0E\u73A9\u5BB6\u8FD0\u884C\u671F\u72B6\u6001\u5FEB\u7167\uFF08sfmc_world, sfmc_players\uFF09\n * 2. \u8DE8\u8FDB\u7A0B\u804A\u5929\u6D88\u606F\u805A\u5408\uFF08sfmc_chat_messages\uFF09\n * 3. QQ \u8EAB\u4EFD\u7ED1\u5B9A\u4E0E\u5BA1\u6279\u961F\u5217\uFF08sfmc_qq_*\uFF09\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/**\n * \u521D\u59CB\u5316\u6570\u636E\u5E93\u7ED3\u6784\uFF0C\u521B\u5EFA\u5E73\u53F0\u6838\u5FC3\u5E95\u5EA7\u8868\u4E0E\u7D22\u5F15\u3002\n *\n * @param db SQLite \u6570\u636E\u5E93\u8FDE\u63A5\u5B9E\u4F8B\u3002\n */\nexport function initSchema(db: DatabaseSync): void {\n // (1) \u4E16\u754C\u8FD0\u884C\u671F\u72B6\u6001\uFF08\u4F9B status \u8DEF\u7531\u4E0E qq-bridge \u8BFB\u53D6\uFF0C\u5B57\u6BB5\u4E0E data-backup \u517C\u5BB9\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_world (\n id TEXT PRIMARY KEY,\n seed TEXT DEFAULT '',\n difficulty TEXT DEFAULT '',\n spawn_x INTEGER DEFAULT 0,\n spawn_y INTEGER DEFAULT 0,\n spawn_z INTEGER DEFAULT 0,\n gamerules TEXT DEFAULT '{}',\n day INTEGER DEFAULT 0,\n absolute_time INTEGER DEFAULT 0,\n moon_phase INTEGER DEFAULT 0,\n updated_at INTEGER DEFAULT 0\n )`);\n\n // (2) \u73A9\u5BB6\u8FD0\u884C\u671F\u72B6\u6001\uFF08\u4F9B status \u8DEF\u7531\u4E0E qq-bridge \u8BFB\u53D6\uFF0C\u5B57\u6BB5\u4E0E data-backup \u517C\u5BB9\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_players (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL DEFAULT '',\n xuid TEXT DEFAULT '',\n last_online INTEGER DEFAULT 0,\n level INTEGER DEFAULT 0,\n total_xp INTEGER DEFAULT 0,\n tags TEXT DEFAULT '',\n updated_at INTEGER DEFAULT 0\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_players_name ON sfmc_players(name)`);\n\n // (3) \u804A\u5929\u6D88\u606F\u805A\u5408\uFF08\u4F9B routes/messages \u4E0E qq-bridge \u53CC\u5411\u540C\u6B65\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_chat_messages (\n id TEXT PRIMARY KEY NOT NULL,\n channel_id TEXT NOT NULL DEFAULT '',\n from_id TEXT NOT NULL DEFAULT '',\n from_name TEXT NOT NULL DEFAULT '',\n type TEXT NOT NULL DEFAULT 'text',\n content TEXT NOT NULL DEFAULT '',\n attachment TEXT DEFAULT NULL,\n show_timestamp INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_chat_messages_channel_time ON sfmc_chat_messages(channel_id, created_at ASC);\n CREATE INDEX IF NOT EXISTS idx_chat_messages_created_at ON sfmc_chat_messages(created_at ASC)`);\n\n // (4) QQ\u2194MC \u8EAB\u4EFD\u7ED1\u5B9A\uFF08\u5E73\u53F0\u8868\uFF1Bqq-bridge / \u6E38\u620F\u6A21\u5757\u5171\u7528 HTTP\uFF0C\u975E\u6A21\u5757\u79C1\u6709\u8868\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_bind_pending (\n code TEXT PRIMARY KEY NOT NULL,\n qq_user_openid TEXT NOT NULL,\n qq_backend TEXT NOT NULL DEFAULT 'official',\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_bindings (\n qq_user_openid TEXT PRIMARY KEY NOT NULL,\n player_xuid TEXT NOT NULL UNIQUE,\n player_name TEXT NOT NULL DEFAULT '',\n qq_backend TEXT NOT NULL DEFAULT 'official',\n bound_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_bind_pending_openid ON sfmc_qq_bind_pending(qq_user_openid);\n CREATE INDEX IF NOT EXISTS idx_qq_bindings_xuid ON sfmc_qq_bindings(player_xuid)`);\n\n // (5) QQ \u5165\u670D\u5BA1\u6279 + \u7BA1\u7406\u52A8\u4F5C\u961F\u5217\uFF08\u5E73\u53F0\u8868\uFF1BBDS \u751F\u6548\u7531 SAPI + server-admin\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_join_requests (\n id TEXT PRIMARY KEY NOT NULL,\n applicant_openid TEXT NOT NULL,\n player_name TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n qq_backend TEXT NOT NULL DEFAULT 'official',\n created_at INTEGER NOT NULL,\n decided_at INTEGER,\n decided_by TEXT,\n applied_at INTEGER,\n apply_error TEXT\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_join_status ON sfmc_qq_join_requests(status, created_at)`);\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_admin_actions (\n id TEXT PRIMARY KEY NOT NULL,\n kind TEXT NOT NULL,\n target_name TEXT NOT NULL,\n reason TEXT NOT NULL DEFAULT '',\n requested_by TEXT NOT NULL DEFAULT '',\n status TEXT NOT NULL DEFAULT 'pending',\n created_at INTEGER NOT NULL,\n applied_at INTEGER,\n apply_error TEXT\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_admin_actions_status ON sfmc_qq_admin_actions(status, created_at)`);\n}\n"],
5
- "mappings": "AAuBO,SAAS,WAAW,IAAwB;AAEjD,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAad;AAGJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA,EACkD;AAGpE,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA,EAE8E;AAGhG,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA,EAEiE;AAGnF,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA,EAC2E;AAC7F,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA,EACoF;AACxG;",
4
+ "sourcesContent": ["/**\n * domain/schema.ts \u2014 \u6570\u636E\u5E93 Schema \u521D\u59CB\u5316\u5B9A\u4E49\uFF08\u5E73\u53F0\u6838\u5FC3\u5E95\u5EA7\u8868\uFF09\n *\n * \u96C6\u4E2D\u7BA1\u7406\u5E73\u53F0\u6838\u5FC3\u6570\u636E\u8868\u7684 CREATE TABLE IF NOT EXISTS \u4E0E CREATE INDEX IF NOT EXISTS DDL \u8BED\u53E5\u3002\n * \u8C03\u7528\u65B9\u5F0F\uFF1AinitSchema(db) \u2014\u2014 \u5728 openDatabase \u4E4B\u540E\u3001\u4E1A\u52A1\u67E5\u8BE2\u542F\u52A8\u524D\u6267\u884C\u3002\n *\n * \u67B6\u6784\u7EA6\u5B9A\uFF1A\n * \u4E1A\u52A1\u6A21\u5757\uFF08activity-log, coop, land, chat, economy \u7B49\uFF09\u79C1\u6709\u6570\u636E\u8868\u5747\u7531\u5404\u81EA\u6A21\u5757\u5728 SAPI \u8FD0\u884C\u671F\u901A\u8FC7\n * db.defineTable(...) \u81EA\u4E3B\u58F0\u660E\u4E0E\u7EF4\u62A4\uFF0C\u4E0D\u5728\u6B64\u5904\u9759\u6001\u786C\u7F16\u7801\uFF0C\u907F\u514D\u5217\u5B9A\u4E49\u4E0E\u7D22\u5F15\u51B2\u7A81\u3002\n *\n * \u6B64\u5904\u4EC5\u5B9A\u4E49\u5E73\u53F0\u6838\u5FC3\u8FD0\u884C\u3001\u8DE8\u670D\u52A1\u5171\u4EAB\uFF08qq-bridge / HTTP \u7AEF\u70B9 / status \u63A2\u6D3B\uFF09\u7684\u57FA\u7840\u5E95\u5EA7\u8868\uFF1A\n * 1. \u4E16\u754C\u4E0E\u73A9\u5BB6\u8FD0\u884C\u671F\u72B6\u6001\u5FEB\u7167\uFF08sfmc_world, sfmc_players\uFF09\n * 2. \u8DE8\u8FDB\u7A0B\u804A\u5929\u6D88\u606F\u805A\u5408\uFF08sfmc_chat_messages\uFF09\n * 3. QQ \u8EAB\u4EFD\u7ED1\u5B9A\u4E0E\u5BA1\u6279\u961F\u5217\uFF08sfmc_qq_*\uFF09\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/**\n * \u521D\u59CB\u5316\u6570\u636E\u5E93\u7ED3\u6784\uFF0C\u521B\u5EFA\u5E73\u53F0\u6838\u5FC3\u5E95\u5EA7\u8868\u4E0E\u7D22\u5F15\u3002\n *\n * @param db SQLite \u6570\u636E\u5E93\u8FDE\u63A5\u5B9E\u4F8B\u3002\n */\nexport function initSchema(db: DatabaseSync): void {\n // (1) \u4E16\u754C\u8FD0\u884C\u671F\u72B6\u6001\uFF08\u4F9B status \u8DEF\u7531\u4E0E qq-bridge \u8BFB\u53D6\uFF0C\u5B57\u6BB5\u4E0E data-backup \u517C\u5BB9\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_world (\n id TEXT PRIMARY KEY,\n seed TEXT DEFAULT '',\n difficulty TEXT DEFAULT '',\n spawn_x INTEGER DEFAULT 0,\n spawn_y INTEGER DEFAULT 0,\n spawn_z INTEGER DEFAULT 0,\n gamerules TEXT DEFAULT '{}',\n day INTEGER DEFAULT 0,\n absolute_time INTEGER DEFAULT 0,\n moon_phase INTEGER DEFAULT 0,\n updated_at INTEGER DEFAULT 0\n )`);\n\n // (2) \u73A9\u5BB6\u8FD0\u884C\u671F\u72B6\u6001\uFF08\u4F9B status \u8DEF\u7531\u4E0E qq-bridge \u8BFB\u53D6\uFF0C\u5B57\u6BB5\u4E0E data-backup \u517C\u5BB9\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_players (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL DEFAULT '',\n xuid TEXT DEFAULT '',\n last_online INTEGER DEFAULT 0,\n level INTEGER DEFAULT 0,\n total_xp INTEGER DEFAULT 0,\n tags TEXT DEFAULT '',\n spawn_dimension TEXT DEFAULT '',\n spawn_x REAL,\n spawn_y REAL,\n spawn_z REAL,\n dimension TEXT DEFAULT '',\n x REAL,\n y REAL,\n z REAL,\n game_mode TEXT DEFAULT '',\n snapshot_hash TEXT DEFAULT '',\n active_channel TEXT DEFAULT '',\n subscribed_channels TEXT DEFAULT '[]',\n updated_at INTEGER DEFAULT 0\n )`);\n const playerColumns = new Map(\n (db.prepare(`PRAGMA table_info(\"sfmc_players\")`).all() as Array<{ name: string }>).map((column) => [\n column.name,\n true,\n ])\n );\n const missingPlayerColumns: Array<[string, string]> = [\n [\"spawn_dimension\", \"TEXT DEFAULT ''\"],\n [\"spawn_x\", \"REAL\"],\n [\"spawn_y\", \"REAL\"],\n [\"spawn_z\", \"REAL\"],\n [\"dimension\", \"TEXT DEFAULT ''\"],\n [\"x\", \"REAL\"],\n [\"y\", \"REAL\"],\n [\"z\", \"REAL\"],\n [\"game_mode\", \"TEXT DEFAULT ''\"],\n [\"snapshot_hash\", \"TEXT DEFAULT ''\"],\n [\"active_channel\", \"TEXT DEFAULT ''\"],\n [\"subscribed_channels\", \"TEXT DEFAULT '[]'\"],\n ];\n for (const [name, definition] of missingPlayerColumns) {\n if (!playerColumns.has(name)) db.exec(`ALTER TABLE sfmc_players ADD COLUMN \"${name}\" ${definition}`);\n }\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_players_name ON sfmc_players(name)`);\n\n // (3) \u804A\u5929\u6D88\u606F\u805A\u5408\uFF08\u4F9B routes/messages \u4E0E qq-bridge \u53CC\u5411\u540C\u6B65\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_chat_messages (\n id TEXT PRIMARY KEY NOT NULL,\n channel_id TEXT NOT NULL DEFAULT '',\n from_id TEXT NOT NULL DEFAULT '',\n from_name TEXT NOT NULL DEFAULT '',\n type TEXT NOT NULL DEFAULT 'text',\n content TEXT NOT NULL DEFAULT '',\n attachment TEXT DEFAULT NULL,\n show_timestamp INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_chat_messages_channel_time ON sfmc_chat_messages(channel_id, created_at ASC);\n CREATE INDEX IF NOT EXISTS idx_chat_messages_created_at ON sfmc_chat_messages(created_at ASC)`);\n\n // (4) QQ\u2194MC \u8EAB\u4EFD\u7ED1\u5B9A\uFF08\u5E73\u53F0\u8868\uFF1Bqq-bridge / \u6E38\u620F\u6A21\u5757\u5171\u7528 HTTP\uFF0C\u975E\u6A21\u5757\u79C1\u6709\u8868\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_bind_pending (\n code TEXT PRIMARY KEY NOT NULL,\n qq_user_openid TEXT NOT NULL,\n qq_backend TEXT NOT NULL DEFAULT 'official',\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_bindings (\n qq_user_openid TEXT PRIMARY KEY NOT NULL,\n player_xuid TEXT NOT NULL UNIQUE,\n player_name TEXT NOT NULL DEFAULT '',\n qq_backend TEXT NOT NULL DEFAULT 'official',\n bound_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_bind_pending_openid ON sfmc_qq_bind_pending(qq_user_openid);\n CREATE INDEX IF NOT EXISTS idx_qq_bindings_xuid ON sfmc_qq_bindings(player_xuid)`);\n\n // (5) QQ \u5165\u670D\u5BA1\u6279 + \u7BA1\u7406\u52A8\u4F5C\u961F\u5217\uFF08\u5E73\u53F0\u8868\uFF1BBDS \u751F\u6548\u7531 SAPI + server-admin\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_join_requests (\n id TEXT PRIMARY KEY NOT NULL,\n applicant_openid TEXT NOT NULL,\n player_name TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n qq_backend TEXT NOT NULL DEFAULT 'official',\n created_at INTEGER NOT NULL,\n decided_at INTEGER,\n decided_by TEXT,\n applied_at INTEGER,\n apply_error TEXT\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_join_status ON sfmc_qq_join_requests(status, created_at)`);\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_admin_actions (\n id TEXT PRIMARY KEY NOT NULL,\n kind TEXT NOT NULL,\n target_name TEXT NOT NULL,\n reason TEXT NOT NULL DEFAULT '',\n requested_by TEXT NOT NULL DEFAULT '',\n status TEXT NOT NULL DEFAULT 'pending',\n created_at INTEGER NOT NULL,\n applied_at INTEGER,\n apply_error TEXT\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_admin_actions_status ON sfmc_qq_admin_actions(status, created_at)`);\n\n // QQ \u8D26\u53F7\u9875\u5FEB\u7167\uFF1Aqq-link \u8C03\u7528\u7ECF\u6D4E / \u5728\u7EBF\u65F6\u957F\u670D\u52A1\u540E\u5199\u5165\uFF0C\u4F9B\u300C\u6211\u7684\u8D26\u53F7\u300D\u8BFB\u6700\u65B0\u6570\u5B57\u3002\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_account_profile (\n player_xuid TEXT PRIMARY KEY NOT NULL,\n player_name TEXT NOT NULL DEFAULT '',\n balance INTEGER,\n today_seconds INTEGER,\n month_seconds INTEGER,\n total_seconds INTEGER,\n updated_at INTEGER NOT NULL\n )`);\n}\n"],
5
+ "mappings": "AAuBO,SAAS,WAAW,IAAwB;AAEjD,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAad;AAGJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBd;AACJ,QAAM,gBAAgB,IAAI;AAAA,IACvB,GAAG,QAAQ,mCAAmC,EAAE,IAAI,EAA8B,IAAI,CAAC,WAAW;AAAA,MACjG,OAAO;AAAA,MACP;AAAA,IACF,CAAC;AAAA,EACH;AACA,QAAM,uBAAgD;AAAA,IACpD,CAAC,mBAAmB,iBAAiB;AAAA,IACrC,CAAC,WAAW,MAAM;AAAA,IAClB,CAAC,WAAW,MAAM;AAAA,IAClB,CAAC,WAAW,MAAM;AAAA,IAClB,CAAC,aAAa,iBAAiB;AAAA,IAC/B,CAAC,KAAK,MAAM;AAAA,IACZ,CAAC,KAAK,MAAM;AAAA,IACZ,CAAC,KAAK,MAAM;AAAA,IACZ,CAAC,aAAa,iBAAiB;AAAA,IAC/B,CAAC,iBAAiB,iBAAiB;AAAA,IACnC,CAAC,kBAAkB,iBAAiB;AAAA,IACpC,CAAC,uBAAuB,mBAAmB;AAAA,EAC7C;AACA,aAAW,CAAC,MAAM,UAAU,KAAK,sBAAsB;AACrD,QAAI,CAAC,cAAc,IAAI,IAAI,EAAG,IAAG,KAAK,wCAAwC,IAAI,KAAK,UAAU,EAAE;AAAA,EACrG;AACA,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA,EACkD;AAGpE,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA,EAE8E;AAGhG,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA,EAEiE;AAGnF,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA,EAC2E;AAC7F,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA,EACoF;AAGtG,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASd;AACN;",
6
6
  "names": []
7
7
  }
@@ -30,6 +30,7 @@ export type ProcessUptime = {
30
30
  running: boolean;
31
31
  };
32
32
  export type BdsStatus = ProcessUptime & {
33
+ version?: string;
33
34
  state: "running" | "stopped";
34
35
  };
35
36
  export type SystemStatusSnapshot = {
@@ -51,6 +52,8 @@ export declare function formatUptimeSec(sec: number): string;
51
52
  * @returns 主机状态对象。
52
53
  */
53
54
  export declare function collectHostStatus(nowSec?: number): HostStatus;
55
+ /** 只认当前进程的启动记录,拒绝停服、PID 复用或无法核验的旧版本。 */
56
+ export declare function readRunningBdsVersion(projectRoot: string, pid: number, uptimeSec: number | null): string | undefined;
54
57
  /**
55
58
  * 采集 BDS(Bedrock Dedicated Server)服务进程的运行状态。
56
59
  * 先尝试根据 `.sfmc/bds.pid` 进行探活,若未命中则通过系统进程快照匹配 `bedrock_server`。
@@ -1 +1 @@
1
- {"version":3,"file":"system-status.d.ts","sourceRoot":"","sources":["../../src/domain/system-status.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,mCAAmC;IACnC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG;IACtC,KAAK,EAAE,SAAS,GAAG,SAAS,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,UAAU,CAAC;IACjB,EAAE,EAAE,aAAa,CAAC;IAClB,GAAG,EAAE,SAAS,CAAC;CAChB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUnD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,SAAc,GAAG,UAAU,CAyBlE;AAqGD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAqB9E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,aAAa,CAQ/C;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAM5F"}
1
+ {"version":3,"file":"system-status.d.ts","sourceRoot":"","sources":["../../src/domain/system-status.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,mCAAmC;IACnC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,GAAG,SAAS,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,UAAU,CAAC;IACjB,EAAE,EAAE,aAAa,CAAC;IAClB,GAAG,EAAE,SAAS,CAAC;CAChB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUnD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,SAAc,GAAG,UAAU,CAyBlE;AAoGD,wCAAwC;AACxC,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAkBpH;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAuB9E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,aAAa,CAQ/C;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAM5F"}
@@ -1,9 +1,9 @@
1
+ import { stateDir } from "@sfmc-bds/sdk/node/config";
1
2
  import { execFile } from "node:child_process";
2
3
  import fs from "node:fs";
3
4
  import os from "node:os";
4
5
  import path from "node:path";
5
6
  import { promisify } from "node:util";
6
- import { stateDir } from "@sfmc-bds/sdk/node/config";
7
7
  const execFileAsync = promisify(execFile);
8
8
  const PROBE_TIMEOUT_MS = 2e3;
9
9
  function formatUptimeSec(sec) {
@@ -71,11 +71,10 @@ async function isPidAlive(pid) {
71
71
  if (!pid) return false;
72
72
  try {
73
73
  if (process.platform === "win32") {
74
- const { stdout } = await execFileAsync(
75
- "tasklist",
76
- ["/fi", `PID eq ${pid}`, "/nh"],
77
- { windowsHide: true, timeout: PROBE_TIMEOUT_MS }
78
- );
74
+ const { stdout } = await execFileAsync("tasklist", ["/fi", `PID eq ${pid}`, "/nh"], {
75
+ windowsHide: true,
76
+ timeout: PROBE_TIMEOUT_MS
77
+ });
79
78
  return String(stdout).includes(String(pid));
80
79
  }
81
80
  process.kill(pid, 0);
@@ -89,11 +88,11 @@ async function getProcessUptimeSec(pid) {
89
88
  try {
90
89
  if (process.platform === "win32") {
91
90
  const script = `(Get-Process -Id ${pid} -ErrorAction Stop).StartTime | ForEach-Object { [int]((Get-Date) - $_).TotalSeconds }`;
92
- const { stdout: stdout2 } = await execFileAsync(
93
- "powershell.exe",
94
- ["-NoProfile", "-NonInteractive", "-Command", script],
95
- { windowsHide: true, timeout: PROBE_TIMEOUT_MS, encoding: "utf8" }
96
- );
91
+ const { stdout: stdout2 } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
92
+ windowsHide: true,
93
+ timeout: PROBE_TIMEOUT_MS,
94
+ encoding: "utf8"
95
+ });
97
96
  const n2 = parseInt(String(stdout2).trim(), 10);
98
97
  return Number.isFinite(n2) && n2 >= 0 ? n2 : null;
99
98
  }
@@ -133,6 +132,19 @@ async function findBedrockServerPid() {
133
132
  return 0;
134
133
  }
135
134
  }
135
+ function readRunningBdsVersion(projectRoot, pid, uptimeSec) {
136
+ if (!pid || uptimeSec === null) return void 0;
137
+ try {
138
+ const record = JSON.parse(
139
+ fs.readFileSync(path.join(stateDir(projectRoot), "bds-runtime-version.json"), "utf8")
140
+ );
141
+ if (record.pid !== pid || typeof record.startedAt !== "number" || Math.abs(Date.now() - uptimeSec * 1e3 - record.startedAt) > 1e4)
142
+ return void 0;
143
+ return typeof record.version === "string" && /^\d+\.\d+\.\d+(?:\.\d+)?$/.test(record.version) ? record.version : void 0;
144
+ } catch {
145
+ return void 0;
146
+ }
147
+ }
136
148
  async function collectBdsStatus(projectRoot) {
137
149
  let pid = readBdsPidFile(projectRoot);
138
150
  const alive = pid > 0 ? await withTimeout(isPidAlive(pid), PROBE_TIMEOUT_MS) : false;
@@ -147,7 +159,9 @@ async function collectBdsStatus(projectRoot) {
147
159
  return { state: "stopped", running: false, pid: 0, uptimeSec: null, uptimeText: "\u672A\u8FD0\u884C" };
148
160
  }
149
161
  const uptimeSec = await withTimeout(getProcessUptimeSec(pid), PROBE_TIMEOUT_MS);
162
+ const version = readRunningBdsVersion(projectRoot, pid, uptimeSec);
150
163
  return {
164
+ ...version ? { version } : {},
151
165
  state: "running",
152
166
  running: true,
153
167
  pid,
@@ -176,6 +190,7 @@ export {
176
190
  collectDbStatus,
177
191
  collectHostStatus,
178
192
  collectSystemStatus,
179
- formatUptimeSec
193
+ formatUptimeSec,
194
+ readRunningBdsVersion
180
195
  };
181
196
  //# sourceMappingURL=system-status.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/domain/system-status.ts"],
4
- "sourcesContent": ["/**\n * domain/system-status.ts \u2014 \u4E3B\u673A / \u8FDB\u7A0B\u8FD0\u7EF4\u5FEB\u7167\uFF08\u4F9B GET /api/sfmc/status\uFF09\n *\n * \u4E0D\u4F9D\u8D56 bds-tools\uFF1A\u4EC5\u8BFB SFMC_ROOT/.sfmc/bds.pid + OS \u63A2\u6D3B\uFF0C\u907F\u514D db-server \u62C9\u5165\u91CD\u4F9D\u8D56\u3002\n */\n\nimport { execFile } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { stateDir } from \"@sfmc-bds/sdk/node/config\";\n\nconst execFileAsync = promisify(execFile);\n\n/** \u8FDB\u7A0B\u63A2\u6D3B / \u542F\u52A8\u65F6\u95F4\u67E5\u8BE2\u8D85\u65F6\uFF0C\u907F\u514D status \u5361\u4F4F */\nconst PROBE_TIMEOUT_MS = 2_000;\n\nexport type HostStatus = {\n hostname: string;\n platform: string;\n arch: string;\n release: string;\n uptimeSec: number;\n uptimeText: string;\n cpu: { model: string; cores: number };\n memory: {\n totalMb: number;\n freeMb: number;\n usedMb: number;\n usedPercent: number;\n };\n /** Linux \u6709\u610F\u4E49\uFF1BWindows \u5E38\u4E3A [0,0,0] */\n loadavg: [number, number, number];\n};\n\nexport type ProcessUptime = {\n pid: number;\n uptimeSec: number | null;\n uptimeText: string;\n running: boolean;\n};\n\nexport type BdsStatus = ProcessUptime & {\n state: \"running\" | \"stopped\";\n};\n\nexport type SystemStatusSnapshot = {\n host: HostStatus;\n db: ProcessUptime;\n bds: BdsStatus;\n};\n\n/**\n * \u5C06\u79D2\u6570\u683C\u5F0F\u5316\u4E3A\u4EBA\u7C7B\u53EF\u8BFB\u7684\u8FD0\u884C\u65F6\u957F\u5B57\u7B26\u4E32\uFF08\u5982 \"2\u5929 3\u5C0F\u65F6 15\u5206\"\uFF09\u3002\n *\n * @param sec \u8FD0\u884C\u65F6\u957F\u79D2\u6570\u3002\n * @returns \u4EBA\u7C7B\u53CB\u597D\u7684\u65F6\u957F\u63CF\u8FF0\u5B57\u7B26\u4E32\u3002\n */\nexport function formatUptimeSec(sec: number): string {\n const s = Math.max(0, Math.floor(sec));\n const d = Math.floor(s / 86400);\n const h = Math.floor((s % 86400) / 3600);\n const m = Math.floor((s % 3600) / 60);\n const r = s % 60;\n if (d > 0) return `${d}\u5929${h}\u65F6${m}\u5206`;\n if (h > 0) return `${h}\u65F6${m}\u5206`;\n if (m > 0) return `${m}\u5206${r}\u79D2`;\n return `${r}\u79D2`;\n}\n\n/**\n * \u91C7\u96C6\u4E3B\u673A\u786C\u4EF6\u4E0E\u64CD\u4F5C\u7CFB\u7EDF\u8FD0\u884C\u72B6\u6001\u5FEB\u7167\u3002\n *\n * @param nowSec \u5F53\u524D\u7CFB\u7EDF\u8FD0\u884C\u79D2\u6570\uFF08\u9ED8\u8BA4\u4E3A `os.uptime()`\uFF09\u3002\n * @returns \u4E3B\u673A\u72B6\u6001\u5BF9\u8C61\u3002\n */\nexport function collectHostStatus(nowSec = os.uptime()): HostStatus {\n const cpus = os.cpus();\n const total = os.totalmem();\n const free = os.freemem();\n const used = Math.max(0, total - free);\n const load = os.loadavg();\n return {\n hostname: os.hostname(),\n platform: os.platform(),\n arch: os.arch(),\n release: os.release(),\n uptimeSec: Math.floor(nowSec),\n uptimeText: formatUptimeSec(nowSec),\n cpu: {\n model: (cpus[0]?.model ?? \"unknown\").replace(/\\s+/g, \" \").trim().slice(0, 48),\n cores: cpus.length,\n },\n memory: {\n totalMb: Math.round(total / (1024 * 1024)),\n freeMb: Math.round(free / (1024 * 1024)),\n usedMb: Math.round(used / (1024 * 1024)),\n usedPercent: total > 0 ? Math.round((used / total) * 100) : 0,\n },\n loadavg: [round1(load[0] ?? 0), round1(load[1] ?? 0), round1(load[2] ?? 0)],\n };\n}\n\nfunction round1(n: number): number {\n return Math.round(n * 10) / 10;\n}\n\nfunction readBdsPidFile(projectRoot: string): number {\n try {\n const file = path.join(stateDir(projectRoot), \"bds.pid\");\n return parseInt(fs.readFileSync(file, \"utf-8\").trim(), 10) || 0;\n } catch {\n return 0;\n }\n}\n\nasync function withTimeout<T>(p: Promise<T>, ms: number): Promise<T | null> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n p,\n new Promise<null>((resolve) => {\n timer = setTimeout(() => resolve(null), ms);\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nasync function isPidAlive(pid: number): Promise<boolean> {\n if (!pid) return false;\n try {\n if (process.platform === \"win32\") {\n const { stdout } = await execFileAsync(\n \"tasklist\",\n [\"/fi\", `PID eq ${pid}`, \"/nh\"],\n { windowsHide: true, timeout: PROBE_TIMEOUT_MS }\n );\n return String(stdout).includes(String(pid));\n }\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** \u67E5\u8BE2\u8FDB\u7A0B\u5DF2\u8FD0\u884C\u79D2\u6570\uFF1B\u5931\u8D25\u8FD4\u56DE null\u3002 */\nasync function getProcessUptimeSec(pid: number): Promise<number | null> {\n if (!pid) return null;\n try {\n if (process.platform === \"win32\") {\n // \u7528 etimes \u7B49\u4EF7\uFF1A\u4ECE StartTime \u7B97\u5230\u73B0\u5728\uFF08\u79D2\uFF09\n const script = `(Get-Process -Id ${pid} -ErrorAction Stop).StartTime | ForEach-Object { [int]((Get-Date) - $_).TotalSeconds }`;\n const { stdout } = await execFileAsync(\n \"powershell.exe\",\n [\"-NoProfile\", \"-NonInteractive\", \"-Command\", script],\n { windowsHide: true, timeout: PROBE_TIMEOUT_MS, encoding: \"utf8\" }\n );\n const n = parseInt(String(stdout).trim(), 10);\n return Number.isFinite(n) && n >= 0 ? n : null;\n }\n // Linux: ps etimes\uFF08\u5DF2\u8FD0\u884C\u79D2\uFF09\n const { stdout } = await execFileAsync(\"ps\", [\"-p\", String(pid), \"-o\", \"etimes=\"], {\n timeout: PROBE_TIMEOUT_MS,\n encoding: \"utf8\",\n });\n const n = parseInt(String(stdout).trim(), 10);\n return Number.isFinite(n) && n >= 0 ? n : null;\n } catch {\n return null;\n }\n}\n\nasync function findBedrockServerPid(): Promise<number> {\n try {\n if (process.platform === \"win32\") {\n const { stdout } = await execFileAsync(\n \"tasklist\",\n [\"/fi\", \"IMAGENAME eq bedrock_server.exe\", \"/fo\", \"csv\", \"/nh\"],\n { windowsHide: true, timeout: PROBE_TIMEOUT_MS }\n );\n for (const line of String(stdout).split(/\\r?\\n/)) {\n const m = line.match(/\"bedrock_server\\.exe\",\"(\\d+)\"/i);\n if (m) {\n const pid = parseInt(m[1] ?? \"0\", 10);\n if (pid > 0) return pid;\n }\n }\n return 0;\n }\n const { stdout } = await execFileAsync(\"pgrep\", [\"-x\", \"bedrock_server\"], {\n timeout: PROBE_TIMEOUT_MS,\n });\n const pid = parseInt(String(stdout).trim().split(/\\r?\\n/)[0] ?? \"0\", 10);\n return pid > 0 ? pid : 0;\n } catch {\n return 0;\n }\n}\n\n/**\n * \u91C7\u96C6 BDS\uFF08Bedrock Dedicated Server\uFF09\u670D\u52A1\u8FDB\u7A0B\u7684\u8FD0\u884C\u72B6\u6001\u3002\n * \u5148\u5C1D\u8BD5\u6839\u636E `.sfmc/bds.pid` \u8FDB\u884C\u63A2\u6D3B\uFF0C\u82E5\u672A\u547D\u4E2D\u5219\u901A\u8FC7\u7CFB\u7EDF\u8FDB\u7A0B\u5FEB\u7167\u5339\u914D `bedrock_server`\u3002\n *\n * @param projectRoot SFMC \u5DE5\u4F5C\u6839\u76EE\u5F55\u3002\n * @returns BDS \u8FD0\u884C\u72B6\u6001\u4E0E\u8FD0\u884C\u65F6\u957F\u5FEB\u7167\u3002\n */\nexport async function collectBdsStatus(projectRoot: string): Promise<BdsStatus> {\n let pid = readBdsPidFile(projectRoot);\n const alive = pid > 0 ? await withTimeout(isPidAlive(pid), PROBE_TIMEOUT_MS) : false;\n if (!alive) {\n pid = (await withTimeout(findBedrockServerPid(), PROBE_TIMEOUT_MS)) ?? 0;\n }\n if (!pid) {\n return { state: \"stopped\", running: false, pid: 0, uptimeSec: null, uptimeText: \"\u672A\u8FD0\u884C\" };\n }\n const stillAlive = alive === true || (await withTimeout(isPidAlive(pid), PROBE_TIMEOUT_MS)) === true;\n if (!stillAlive) {\n return { state: \"stopped\", running: false, pid: 0, uptimeSec: null, uptimeText: \"\u672A\u8FD0\u884C\" };\n }\n const uptimeSec = await withTimeout(getProcessUptimeSec(pid), PROBE_TIMEOUT_MS);\n return {\n state: \"running\",\n running: true,\n pid,\n uptimeSec,\n uptimeText: uptimeSec != null ? formatUptimeSec(uptimeSec) : \"\u8FD0\u884C\u4E2D\",\n };\n}\n\n/**\n * \u91C7\u96C6\u5F53\u524D db-server \u8FDB\u7A0B\u81EA\u8EAB\u7684\u8FD0\u884C\u72B6\u6001\u4E0E\u5DF2\u8FD0\u884C\u65F6\u95F4\u3002\n *\n * @returns db-server \u8FDB\u7A0B\u72B6\u6001\u5FEB\u7167\u3002\n */\nexport function collectDbStatus(): ProcessUptime {\n const uptimeSec = Math.floor(process.uptime());\n return {\n pid: process.pid,\n running: true,\n uptimeSec,\n uptimeText: formatUptimeSec(uptimeSec),\n };\n}\n\n/**\n * \u7EC4\u88C5\u5B8C\u6574\u7684\u7CFB\u7EDF\u72B6\u6001\u5FEB\u7167\uFF08\u5305\u542B\u4E3B\u673A\u7CFB\u7EDF\u3001db-server \u4E0E BDS \u8FDB\u7A0B\u72B6\u6001\uFF09\u3002\n *\n * @param projectRoot SFMC \u5DE5\u4F5C\u6839\u76EE\u5F55\u3002\n * @returns \u5B8C\u6574\u7684\u7CFB\u7EDF\u72B6\u6001\u5FEB\u7167\u3002\n */\nexport async function collectSystemStatus(projectRoot: string): Promise<SystemStatusSnapshot> {\n return {\n host: collectHostStatus(),\n db: collectDbStatus(),\n bds: await collectBdsStatus(projectRoot),\n };\n}\n\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * domain/system-status.ts \u2014 \u4E3B\u673A / \u8FDB\u7A0B\u8FD0\u7EF4\u5FEB\u7167\uFF08\u4F9B GET /api/sfmc/status\uFF09\n *\n * \u4E0D\u4F9D\u8D56 bds-tools\uFF1A\u4EC5\u8BFB SFMC_ROOT/.sfmc/bds.pid + OS \u63A2\u6D3B\uFF0C\u907F\u514D db-server \u62C9\u5165\u91CD\u4F9D\u8D56\u3002\n */\n\nimport { stateDir } from \"@sfmc-bds/sdk/node/config\";\nimport { execFile } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\n/** \u8FDB\u7A0B\u63A2\u6D3B / \u542F\u52A8\u65F6\u95F4\u67E5\u8BE2\u8D85\u65F6\uFF0C\u907F\u514D status \u5361\u4F4F */\nconst PROBE_TIMEOUT_MS = 2_000;\n\nexport type HostStatus = {\n hostname: string;\n platform: string;\n arch: string;\n release: string;\n uptimeSec: number;\n uptimeText: string;\n cpu: { model: string; cores: number };\n memory: {\n totalMb: number;\n freeMb: number;\n usedMb: number;\n usedPercent: number;\n };\n /** Linux \u6709\u610F\u4E49\uFF1BWindows \u5E38\u4E3A [0,0,0] */\n loadavg: [number, number, number];\n};\n\nexport type ProcessUptime = {\n pid: number;\n uptimeSec: number | null;\n uptimeText: string;\n running: boolean;\n};\n\nexport type BdsStatus = ProcessUptime & {\n version?: string;\n state: \"running\" | \"stopped\";\n};\n\nexport type SystemStatusSnapshot = {\n host: HostStatus;\n db: ProcessUptime;\n bds: BdsStatus;\n};\n\n/**\n * \u5C06\u79D2\u6570\u683C\u5F0F\u5316\u4E3A\u4EBA\u7C7B\u53EF\u8BFB\u7684\u8FD0\u884C\u65F6\u957F\u5B57\u7B26\u4E32\uFF08\u5982 \"2\u5929 3\u5C0F\u65F6 15\u5206\"\uFF09\u3002\n *\n * @param sec \u8FD0\u884C\u65F6\u957F\u79D2\u6570\u3002\n * @returns \u4EBA\u7C7B\u53CB\u597D\u7684\u65F6\u957F\u63CF\u8FF0\u5B57\u7B26\u4E32\u3002\n */\nexport function formatUptimeSec(sec: number): string {\n const s = Math.max(0, Math.floor(sec));\n const d = Math.floor(s / 86400);\n const h = Math.floor((s % 86400) / 3600);\n const m = Math.floor((s % 3600) / 60);\n const r = s % 60;\n if (d > 0) return `${d}\u5929${h}\u65F6${m}\u5206`;\n if (h > 0) return `${h}\u65F6${m}\u5206`;\n if (m > 0) return `${m}\u5206${r}\u79D2`;\n return `${r}\u79D2`;\n}\n\n/**\n * \u91C7\u96C6\u4E3B\u673A\u786C\u4EF6\u4E0E\u64CD\u4F5C\u7CFB\u7EDF\u8FD0\u884C\u72B6\u6001\u5FEB\u7167\u3002\n *\n * @param nowSec \u5F53\u524D\u7CFB\u7EDF\u8FD0\u884C\u79D2\u6570\uFF08\u9ED8\u8BA4\u4E3A `os.uptime()`\uFF09\u3002\n * @returns \u4E3B\u673A\u72B6\u6001\u5BF9\u8C61\u3002\n */\nexport function collectHostStatus(nowSec = os.uptime()): HostStatus {\n const cpus = os.cpus();\n const total = os.totalmem();\n const free = os.freemem();\n const used = Math.max(0, total - free);\n const load = os.loadavg();\n return {\n hostname: os.hostname(),\n platform: os.platform(),\n arch: os.arch(),\n release: os.release(),\n uptimeSec: Math.floor(nowSec),\n uptimeText: formatUptimeSec(nowSec),\n cpu: {\n model: (cpus[0]?.model ?? \"unknown\").replace(/\\s+/g, \" \").trim().slice(0, 48),\n cores: cpus.length,\n },\n memory: {\n totalMb: Math.round(total / (1024 * 1024)),\n freeMb: Math.round(free / (1024 * 1024)),\n usedMb: Math.round(used / (1024 * 1024)),\n usedPercent: total > 0 ? Math.round((used / total) * 100) : 0,\n },\n loadavg: [round1(load[0] ?? 0), round1(load[1] ?? 0), round1(load[2] ?? 0)],\n };\n}\n\nfunction round1(n: number): number {\n return Math.round(n * 10) / 10;\n}\n\nfunction readBdsPidFile(projectRoot: string): number {\n try {\n const file = path.join(stateDir(projectRoot), \"bds.pid\");\n return parseInt(fs.readFileSync(file, \"utf-8\").trim(), 10) || 0;\n } catch {\n return 0;\n }\n}\n\nasync function withTimeout<T>(p: Promise<T>, ms: number): Promise<T | null> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n p,\n new Promise<null>((resolve) => {\n timer = setTimeout(() => resolve(null), ms);\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nasync function isPidAlive(pid: number): Promise<boolean> {\n if (!pid) return false;\n try {\n if (process.platform === \"win32\") {\n const { stdout } = await execFileAsync(\"tasklist\", [\"/fi\", `PID eq ${pid}`, \"/nh\"], {\n windowsHide: true,\n timeout: PROBE_TIMEOUT_MS,\n });\n return String(stdout).includes(String(pid));\n }\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** \u67E5\u8BE2\u8FDB\u7A0B\u5DF2\u8FD0\u884C\u79D2\u6570\uFF1B\u5931\u8D25\u8FD4\u56DE null\u3002 */\nasync function getProcessUptimeSec(pid: number): Promise<number | null> {\n if (!pid) return null;\n try {\n if (process.platform === \"win32\") {\n // \u7528 etimes \u7B49\u4EF7\uFF1A\u4ECE StartTime \u7B97\u5230\u73B0\u5728\uFF08\u79D2\uFF09\n const script = `(Get-Process -Id ${pid} -ErrorAction Stop).StartTime | ForEach-Object { [int]((Get-Date) - $_).TotalSeconds }`;\n const { stdout } = await execFileAsync(\"powershell.exe\", [\"-NoProfile\", \"-NonInteractive\", \"-Command\", script], {\n windowsHide: true,\n timeout: PROBE_TIMEOUT_MS,\n encoding: \"utf8\",\n });\n const n = parseInt(String(stdout).trim(), 10);\n return Number.isFinite(n) && n >= 0 ? n : null;\n }\n // Linux: ps etimes\uFF08\u5DF2\u8FD0\u884C\u79D2\uFF09\n const { stdout } = await execFileAsync(\"ps\", [\"-p\", String(pid), \"-o\", \"etimes=\"], {\n timeout: PROBE_TIMEOUT_MS,\n encoding: \"utf8\",\n });\n const n = parseInt(String(stdout).trim(), 10);\n return Number.isFinite(n) && n >= 0 ? n : null;\n } catch {\n return null;\n }\n}\n\nasync function findBedrockServerPid(): Promise<number> {\n try {\n if (process.platform === \"win32\") {\n const { stdout } = await execFileAsync(\n \"tasklist\",\n [\"/fi\", \"IMAGENAME eq bedrock_server.exe\", \"/fo\", \"csv\", \"/nh\"],\n { windowsHide: true, timeout: PROBE_TIMEOUT_MS }\n );\n for (const line of String(stdout).split(/\\r?\\n/)) {\n const m = line.match(/\"bedrock_server\\.exe\",\"(\\d+)\"/i);\n if (m) {\n const pid = parseInt(m[1] ?? \"0\", 10);\n if (pid > 0) return pid;\n }\n }\n return 0;\n }\n const { stdout } = await execFileAsync(\"pgrep\", [\"-x\", \"bedrock_server\"], {\n timeout: PROBE_TIMEOUT_MS,\n });\n const pid = parseInt(String(stdout).trim().split(/\\r?\\n/)[0] ?? \"0\", 10);\n return pid > 0 ? pid : 0;\n } catch {\n return 0;\n }\n}\n\n/** \u53EA\u8BA4\u5F53\u524D\u8FDB\u7A0B\u7684\u542F\u52A8\u8BB0\u5F55\uFF0C\u62D2\u7EDD\u505C\u670D\u3001PID \u590D\u7528\u6216\u65E0\u6CD5\u6838\u9A8C\u7684\u65E7\u7248\u672C\u3002 */\nexport function readRunningBdsVersion(projectRoot: string, pid: number, uptimeSec: number | null): string | undefined {\n if (!pid || uptimeSec === null) return undefined;\n try {\n const record = JSON.parse(\n fs.readFileSync(path.join(stateDir(projectRoot), \"bds-runtime-version.json\"), \"utf8\")\n ) as { pid?: number; startedAt?: number; version?: string };\n if (\n record.pid !== pid ||\n typeof record.startedAt !== \"number\" ||\n Math.abs(Date.now() - uptimeSec * 1000 - record.startedAt) > 10_000\n )\n return undefined;\n return typeof record.version === \"string\" && /^\\d+\\.\\d+\\.\\d+(?:\\.\\d+)?$/.test(record.version)\n ? record.version\n : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * \u91C7\u96C6 BDS\uFF08Bedrock Dedicated Server\uFF09\u670D\u52A1\u8FDB\u7A0B\u7684\u8FD0\u884C\u72B6\u6001\u3002\n * \u5148\u5C1D\u8BD5\u6839\u636E `.sfmc/bds.pid` \u8FDB\u884C\u63A2\u6D3B\uFF0C\u82E5\u672A\u547D\u4E2D\u5219\u901A\u8FC7\u7CFB\u7EDF\u8FDB\u7A0B\u5FEB\u7167\u5339\u914D `bedrock_server`\u3002\n *\n * @param projectRoot SFMC \u5DE5\u4F5C\u6839\u76EE\u5F55\u3002\n * @returns BDS \u8FD0\u884C\u72B6\u6001\u4E0E\u8FD0\u884C\u65F6\u957F\u5FEB\u7167\u3002\n */\nexport async function collectBdsStatus(projectRoot: string): Promise<BdsStatus> {\n let pid = readBdsPidFile(projectRoot);\n const alive = pid > 0 ? await withTimeout(isPidAlive(pid), PROBE_TIMEOUT_MS) : false;\n if (!alive) {\n pid = (await withTimeout(findBedrockServerPid(), PROBE_TIMEOUT_MS)) ?? 0;\n }\n if (!pid) {\n return { state: \"stopped\", running: false, pid: 0, uptimeSec: null, uptimeText: \"\u672A\u8FD0\u884C\" };\n }\n const stillAlive = alive === true || (await withTimeout(isPidAlive(pid), PROBE_TIMEOUT_MS)) === true;\n if (!stillAlive) {\n return { state: \"stopped\", running: false, pid: 0, uptimeSec: null, uptimeText: \"\u672A\u8FD0\u884C\" };\n }\n const uptimeSec = await withTimeout(getProcessUptimeSec(pid), PROBE_TIMEOUT_MS);\n const version = readRunningBdsVersion(projectRoot, pid, uptimeSec);\n return {\n ...(version ? { version } : {}),\n state: \"running\",\n running: true,\n pid,\n uptimeSec,\n uptimeText: uptimeSec != null ? formatUptimeSec(uptimeSec) : \"\u8FD0\u884C\u4E2D\",\n };\n}\n\n/**\n * \u91C7\u96C6\u5F53\u524D db-server \u8FDB\u7A0B\u81EA\u8EAB\u7684\u8FD0\u884C\u72B6\u6001\u4E0E\u5DF2\u8FD0\u884C\u65F6\u95F4\u3002\n *\n * @returns db-server \u8FDB\u7A0B\u72B6\u6001\u5FEB\u7167\u3002\n */\nexport function collectDbStatus(): ProcessUptime {\n const uptimeSec = Math.floor(process.uptime());\n return {\n pid: process.pid,\n running: true,\n uptimeSec,\n uptimeText: formatUptimeSec(uptimeSec),\n };\n}\n\n/**\n * \u7EC4\u88C5\u5B8C\u6574\u7684\u7CFB\u7EDF\u72B6\u6001\u5FEB\u7167\uFF08\u5305\u542B\u4E3B\u673A\u7CFB\u7EDF\u3001db-server \u4E0E BDS \u8FDB\u7A0B\u72B6\u6001\uFF09\u3002\n *\n * @param projectRoot SFMC \u5DE5\u4F5C\u6839\u76EE\u5F55\u3002\n * @returns \u5B8C\u6574\u7684\u7CFB\u7EDF\u72B6\u6001\u5FEB\u7167\u3002\n */\nexport async function collectSystemStatus(projectRoot: string): Promise<SystemStatusSnapshot> {\n return {\n host: collectHostStatus(),\n db: collectDbStatus(),\n bds: await collectBdsStatus(projectRoot),\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["stdout", "n", "pid"]
7
7
  }