@sfmc-bds/db-server 0.2.0-beta.13 → 0.2.0-beta.15

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 (88) hide show
  1. package/dist/domain/bridge.d.ts +1 -5
  2. package/dist/domain/bridge.d.ts.map +1 -1
  3. package/dist/domain/bridge.js +15 -14
  4. package/dist/domain/bridge.js.map +2 -2
  5. package/dist/domain/installed-content.d.ts +84 -0
  6. package/dist/domain/installed-content.d.ts.map +1 -0
  7. package/dist/domain/installed-content.js +149 -0
  8. package/dist/domain/installed-content.js.map +7 -0
  9. package/dist/domain/qq-account-profile.d.ts +51 -0
  10. package/dist/domain/qq-account-profile.d.ts.map +1 -0
  11. package/dist/domain/qq-account-profile.js +129 -0
  12. package/dist/domain/qq-account-profile.js.map +7 -0
  13. package/dist/domain/qq-events.d.ts +2 -2
  14. package/dist/domain/qq-events.d.ts.map +1 -1
  15. package/dist/domain/qq-events.js +6 -3
  16. package/dist/domain/qq-events.js.map +2 -2
  17. package/dist/domain/schema.d.ts.map +1 -1
  18. package/dist/domain/schema.js +13 -0
  19. package/dist/domain/schema.js.map +2 -2
  20. package/dist/domain/system-status.d.ts +3 -0
  21. package/dist/domain/system-status.d.ts.map +1 -1
  22. package/dist/domain/system-status.js +27 -12
  23. package/dist/domain/system-status.js.map +2 -2
  24. package/dist/env.d.ts +0 -2
  25. package/dist/env.d.ts.map +1 -1
  26. package/dist/env.js +12 -24
  27. package/dist/env.js.map +2 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +73 -21
  30. package/dist/index.js.map +3 -3
  31. package/dist/routes/config.d.ts +1 -1
  32. package/dist/routes/config.d.ts.map +1 -1
  33. package/dist/routes/config.js +0 -4
  34. package/dist/routes/config.js.map +2 -2
  35. package/dist/routes/content.d.ts +23 -0
  36. package/dist/routes/content.d.ts.map +1 -0
  37. package/dist/routes/content.js +19 -0
  38. package/dist/routes/content.js.map +7 -0
  39. package/dist/routes/messages.d.ts +14 -6
  40. package/dist/routes/messages.d.ts.map +1 -1
  41. package/dist/routes/messages.js +22 -8
  42. package/dist/routes/messages.js.map +2 -2
  43. package/dist/routes/qq-bind.d.ts.map +1 -1
  44. package/dist/routes/qq-bind.js +29 -1
  45. package/dist/routes/qq-bind.js.map +2 -2
  46. package/dist/routes/qq-events.d.ts +6 -2
  47. package/dist/routes/qq-events.d.ts.map +1 -1
  48. package/dist/routes/qq-events.js +35 -1
  49. package/dist/routes/qq-events.js.map +3 -3
  50. package/dist/routes/qq-join.js +1 -1
  51. package/dist/routes/qq-join.js.map +2 -2
  52. package/dist/routes/status.d.ts +3 -11
  53. package/dist/routes/status.d.ts.map +1 -1
  54. package/dist/routes/status.js +39 -49
  55. package/dist/routes/status.js.map +2 -2
  56. package/package.json +4 -5
  57. package/dist/domain/bridge.test.d.ts +0 -2
  58. package/dist/domain/bridge.test.d.ts.map +0 -1
  59. package/dist/domain/bridge.test.js +0 -87
  60. package/dist/domain/bridge.test.js.map +0 -7
  61. package/dist/domain/messages-bridge-filter.test.d.ts +0 -5
  62. package/dist/domain/messages-bridge-filter.test.d.ts.map +0 -1
  63. package/dist/domain/messages-bridge-filter.test.js +0 -20
  64. package/dist/domain/messages-bridge-filter.test.js.map +0 -7
  65. package/dist/domain/player-session.test.d.ts +0 -2
  66. package/dist/domain/player-session.test.d.ts.map +0 -1
  67. package/dist/domain/player-session.test.js +0 -33
  68. package/dist/domain/player-session.test.js.map +0 -7
  69. package/dist/domain/qq-bind-status.test.d.ts +0 -2
  70. package/dist/domain/qq-bind-status.test.d.ts.map +0 -1
  71. package/dist/domain/qq-bind-status.test.js +0 -211
  72. package/dist/domain/qq-bind-status.test.js.map +0 -7
  73. package/dist/domain/qq-events.test.d.ts +0 -5
  74. package/dist/domain/qq-events.test.d.ts.map +0 -1
  75. package/dist/domain/qq-events.test.js +0 -170
  76. package/dist/domain/qq-events.test.js.map +0 -7
  77. package/dist/domain/qq-join.test.d.ts +0 -2
  78. package/dist/domain/qq-join.test.d.ts.map +0 -1
  79. package/dist/domain/qq-join.test.js +0 -353
  80. package/dist/domain/qq-join.test.js.map +0 -7
  81. package/dist/domain/system-status.test.d.ts +0 -5
  82. package/dist/domain/system-status.test.d.ts.map +0 -1
  83. package/dist/domain/system-status.test.js +0 -19
  84. package/dist/domain/system-status.test.js.map +0 -7
  85. package/dist/runtime.test.d.ts +0 -2
  86. package/dist/runtime.test.d.ts.map +0 -1
  87. package/dist/runtime.test.js +0 -477
  88. package/dist/runtime.test.js.map +0 -7
@@ -1,68 +1,58 @@
1
- import { SQL } from "sql-template-strings";
2
- import { PROJECT_ROOT } from "../project-root.js";
3
1
  import { collectSystemStatus } from "../domain/system-status.js";
4
- import { json } from "./_shared.js";
5
- const FRESH_MS = 5 * 60 * 1e3;
6
- function createStatusRoutes({ query, collectSystem, projectRoot }) {
2
+ import { PROJECT_ROOT } from "../project-root.js";
3
+ import { body, json } from "./_shared.js";
4
+ const FRESH_MS = 6e4;
5
+ function createStatusRoutes({ collectSystem, projectRoot }) {
7
6
  const root = projectRoot ?? PROJECT_ROOT;
8
7
  const collect = collectSystem ?? collectSystemStatus;
9
- return async function handle({ path, method, res }) {
8
+ let live = null;
9
+ return async function handle({ path, method, req, res }) {
10
+ if (path === "/api/sfmc/status/live") {
11
+ if (method !== "POST") {
12
+ json(res, { success: false, error: "not_found" }, 404);
13
+ return true;
14
+ }
15
+ const data = await body(req);
16
+ const raw = data.players;
17
+ const day = data.day;
18
+ const difficulty = data.difficulty;
19
+ if (!Array.isArray(raw) || raw.length > 1e3 || !raw.every((name) => typeof name === "string" && name.trim().length > 0 && name.length <= 64) || typeof day !== "number" || !Number.isSafeInteger(day) || day < 0 || typeof difficulty !== "string" || difficulty.length > 32) {
20
+ json(res, { success: false, error: "invalid_live_status" }, 400);
21
+ return true;
22
+ }
23
+ live = {
24
+ online: raw.map((name) => ({ name: name.trim() })),
25
+ world: { day, difficulty },
26
+ updatedAt: Date.now()
27
+ };
28
+ json(res, { success: true });
29
+ return true;
30
+ }
10
31
  if (path !== "/api/sfmc/status") return false;
11
32
  if (method !== "GET") {
12
33
  json(res, { success: false, error: "not_found" }, 404);
13
34
  return true;
14
35
  }
15
- const now = Date.now();
16
- let world = null;
17
- let worldUpdatedAt = null;
18
- try {
19
- const worlds = query(SQL`SELECT * FROM sfmc_world LIMIT 1`);
20
- if (worlds.length > 0 && worlds[0]) {
21
- world = {
22
- day: worlds[0].day ?? null,
23
- difficulty: worlds[0].difficulty ?? null,
24
- absolute_time: worlds[0].absolute_time ?? null,
25
- moon_phase: worlds[0].moon_phase ?? null
26
- };
27
- worldUpdatedAt = worlds[0].updated_at ?? null;
28
- }
29
- } catch {
30
- world = null;
31
- }
32
- let players = [];
33
- try {
34
- const rows = query(
35
- SQL`SELECT id, name, updated_at FROM sfmc_players ORDER BY updated_at DESC LIMIT 64`
36
- );
37
- players = rows.map((r) => ({
38
- id: String(r.id ?? ""),
39
- name: String(r.name ?? ""),
40
- updated_at: Number(r.updated_at) || 0
41
- }));
42
- } catch {
43
- players = [];
44
- }
45
- const fresh = players.filter((p) => p.updated_at > 0 && now - p.updated_at <= FRESH_MS);
46
- const online = fresh.map((p) => ({ id: p.id, name: p.name }));
47
- const newestPlayer = players[0]?.updated_at ?? 0;
48
- const updatedAt = Math.max(newestPlayer, typeof worldUpdatedAt === "number" ? worldUpdatedAt : 0) || now;
49
36
  let system = null;
50
37
  try {
51
38
  system = await collect(root);
52
39
  } catch {
53
40
  system = null;
54
41
  }
42
+ const now = Date.now();
43
+ const bds = system?.bds;
44
+ const bdsAgeMs = typeof bds?.uptimeSec === "number" ? bds.uptimeSec * 1e3 : null;
45
+ const snapshot = live;
46
+ const fresh = snapshot !== null && bds?.state === "running" && now - snapshot.updatedAt <= FRESH_MS && (bdsAgeMs === null || now - snapshot.updatedAt <= bdsAgeMs + 2e3);
47
+ const current = fresh ? snapshot : null;
55
48
  json(res, {
56
- online,
57
- world,
58
- updatedAt,
49
+ online: current?.online ?? [],
50
+ world: current?.world ?? null,
51
+ updatedAt: current?.updatedAt ?? null,
59
52
  host: system?.host ?? null,
60
- processes: system ? {
61
- db: system.db,
62
- bds: system.bds
63
- } : null,
64
- source: "sfmc_players/sfmc_world+os",
65
- note: online.length === 0 ? "\u6682\u65E0\u65B0\u9C9C\u5728\u7EBF\u6570\u636E\uFF08\u9700 BDS \u540C\u6B65\u73A9\u5BB6\u8868\uFF0C\u6216\u6570\u636E\u8D85\u8FC7 5 \u5206\u949F\uFF09" : void 0
53
+ processes: system ? { db: system.db, bds: system.bds } : null,
54
+ source: "bds_script_live",
55
+ note: fresh ? void 0 : bds?.state === "stopped" ? "\u670D\u52A1\u5668\u672A\u8FD0\u884C" : "\u7B49\u5F85\u6E38\u620F\u5B9E\u65F6\u72B6\u6001\u540C\u6B65"
66
56
  });
67
57
  return true;
68
58
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/routes/status.ts"],
4
- "sourcesContent": ["/**\n * routes/status.ts \u2014 \u53EA\u8BFB\u8FD0\u7EF4\u6458\u8981\uFF08\u4F9B QQ status/online\uFF09\n *\n * GET /api/sfmc/status \u2014 \u516C\u5F00\uFF0C\u65E0\u9700 Bearer\n */\n\nimport { SQL } from \"sql-template-strings\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\nimport { PROJECT_ROOT } from \"../project-root.js\";\nimport { collectSystemStatus, type SystemStatusSnapshot } from \"../domain/system-status.js\";\nimport { json, type RouteFactory } from \"./_shared.js\";\n\n/** \u73A9\u5BB6\u884C\u65B0\u9C9C\u5EA6\u9608\u503C\uFF1A\u8D85\u8FC7\u5219\u89C6\u4E3A\u79BB\u7EBF\u5FEB\u7167\u8FC7\u671F */\nconst FRESH_MS = 5 * 60 * 1000;\n\ninterface Deps {\n query: QueryFn;\n /** \u6D4B\u8BD5\u53EF\u6CE8\u5165\uFF0C\u907F\u514D\u771F\u5B9E\u63A2\u6D3B */\n collectSystem?: (projectRoot: string) => Promise<SystemStatusSnapshot>;\n projectRoot?: string;\n}\n\nfunction createStatusRoutes({ query, collectSystem, projectRoot }: Deps): ReturnType<RouteFactory> {\n const root = projectRoot ?? PROJECT_ROOT;\n const collect = collectSystem ?? collectSystemStatus;\n\n return async function handle({ path, method, res }): Promise<boolean> {\n if (path !== \"/api/sfmc/status\") return false;\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n\n const now = Date.now();\n let world: Record<string, unknown> | null = null;\n let worldUpdatedAt: string | number | null = null;\n try {\n const worlds = query(SQL`SELECT * FROM sfmc_world LIMIT 1`) as Array<Record<string, unknown>>;\n if (worlds.length > 0 && worlds[0]) {\n world = {\n day: worlds[0].day ?? null,\n difficulty: worlds[0].difficulty ?? null,\n absolute_time: worlds[0].absolute_time ?? null,\n moon_phase: worlds[0].moon_phase ?? null,\n };\n worldUpdatedAt = (worlds[0].updated_at as string | number | null) ?? null;\n }\n } catch {\n world = null;\n }\n\n let players: Array<{ id: string; name: string; updated_at: number }> = [];\n try {\n const rows = query(\n SQL`SELECT id, name, updated_at FROM sfmc_players ORDER BY updated_at DESC LIMIT 64`\n ) as Array<{ id: string; name: string; updated_at: number }>;\n players = rows.map((r) => ({\n id: String(r.id ?? \"\"),\n name: String(r.name ?? \"\"),\n updated_at: Number(r.updated_at) || 0,\n }));\n } catch {\n players = [];\n }\n\n const fresh = players.filter((p) => p.updated_at > 0 && now - p.updated_at <= FRESH_MS);\n const online = fresh.map((p) => ({ id: p.id, name: p.name }));\n const newestPlayer = players[0]?.updated_at ?? 0;\n const updatedAt = Math.max(newestPlayer, typeof worldUpdatedAt === \"number\" ? worldUpdatedAt : 0) || now;\n\n let system: SystemStatusSnapshot | null = null;\n try {\n system = await collect(root);\n } catch {\n system = null;\n }\n\n json(res, {\n online,\n world,\n updatedAt,\n host: system?.host ?? null,\n processes: system\n ? {\n db: system.db,\n bds: system.bds,\n }\n : null,\n source: \"sfmc_players/sfmc_world+os\",\n note:\n online.length === 0\n ? \"\u6682\u65E0\u65B0\u9C9C\u5728\u7EBF\u6570\u636E\uFF08\u9700 BDS \u540C\u6B65\u73A9\u5BB6\u8868\uFF0C\u6216\u6570\u636E\u8D85\u8FC7 5 \u5206\u949F\uFF09\"\n : undefined,\n });\n return true;\n };\n}\n\nexport { createStatusRoutes, FRESH_MS };\n"],
5
- "mappings": "AAMA,SAAS,WAAW;AAEpB,SAAS,oBAAoB;AAC7B,SAAS,2BAAsD;AAC/D,SAAS,YAA+B;AAGxC,MAAM,WAAW,IAAI,KAAK;AAS1B,SAAS,mBAAmB,EAAE,OAAO,eAAe,YAAY,GAAmC;AACjG,QAAM,OAAO,eAAe;AAC5B,QAAM,UAAU,iBAAiB;AAEjC,SAAO,eAAe,OAAO,EAAE,MAAM,QAAQ,IAAI,GAAqB;AACpE,QAAI,SAAS,mBAAoB,QAAO;AACxC,QAAI,WAAW,OAAO;AACpB,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,aAAO;AAAA,IACT;AAEA,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,QAAwC;AAC5C,QAAI,iBAAyC;AAC7C,QAAI;AACF,YAAM,SAAS,MAAM,qCAAqC;AAC1D,UAAI,OAAO,SAAS,KAAK,OAAO,CAAC,GAAG;AAClC,gBAAQ;AAAA,UACN,KAAK,OAAO,CAAC,EAAE,OAAO;AAAA,UACtB,YAAY,OAAO,CAAC,EAAE,cAAc;AAAA,UACpC,eAAe,OAAO,CAAC,EAAE,iBAAiB;AAAA,UAC1C,YAAY,OAAO,CAAC,EAAE,cAAc;AAAA,QACtC;AACA,yBAAkB,OAAO,CAAC,EAAE,cAAyC;AAAA,MACvE;AAAA,IACF,QAAQ;AACN,cAAQ;AAAA,IACV;AAEA,QAAI,UAAmE,CAAC;AACxE,QAAI;AACF,YAAM,OAAO;AAAA,QACX;AAAA,MACF;AACA,gBAAU,KAAK,IAAI,CAAC,OAAO;AAAA,QACzB,IAAI,OAAO,EAAE,MAAM,EAAE;AAAA,QACrB,MAAM,OAAO,EAAE,QAAQ,EAAE;AAAA,QACzB,YAAY,OAAO,EAAE,UAAU,KAAK;AAAA,MACtC,EAAE;AAAA,IACJ,QAAQ;AACN,gBAAU,CAAC;AAAA,IACb;AAEA,UAAM,QAAQ,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,EAAE,cAAc,QAAQ;AACtF,UAAM,SAAS,MAAM,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,MAAM,EAAE,KAAK,EAAE;AAC5D,UAAM,eAAe,QAAQ,CAAC,GAAG,cAAc;AAC/C,UAAM,YAAY,KAAK,IAAI,cAAc,OAAO,mBAAmB,WAAW,iBAAiB,CAAC,KAAK;AAErG,QAAI,SAAsC;AAC1C,QAAI;AACF,eAAS,MAAM,QAAQ,IAAI;AAAA,IAC7B,QAAQ;AACN,eAAS;AAAA,IACX;AAEA,SAAK,KAAK;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM,QAAQ,QAAQ;AAAA,MACtB,WAAW,SACP;AAAA,QACE,IAAI,OAAO;AAAA,QACX,KAAK,OAAO;AAAA,MACd,IACA;AAAA,MACJ,QAAQ;AAAA,MACR,MACE,OAAO,WAAW,IACd,6JACA;AAAA,IACR,CAAC;AACD,WAAO;AAAA,EACT;AACF;",
4
+ "sourcesContent": ["/** GET /api/sfmc/status \u4E0E BDS \u5B9E\u65F6\u5FEB\u7167\u5165\u53E3\u3002 */\n\nimport { collectSystemStatus, type SystemStatusSnapshot } from \"../domain/system-status.js\";\nimport { PROJECT_ROOT } from \"../project-root.js\";\nimport { body, json, type RouteFactory } from \"./_shared.js\";\n\nconst FRESH_MS = 60_000;\n\ninterface LiveSnapshot {\n online: Array<{ name: string }>;\n world: { day: number; difficulty: string };\n updatedAt: number;\n}\n\ninterface Deps {\n collectSystem?: (projectRoot: string) => Promise<SystemStatusSnapshot>;\n projectRoot?: string;\n}\n\nfunction createStatusRoutes({ collectSystem, projectRoot }: Deps): ReturnType<RouteFactory> {\n const root = projectRoot ?? PROJECT_ROOT;\n const collect = collectSystem ?? collectSystemStatus;\n let live: LiveSnapshot | null = null;\n\n return async function handle({ path, method, req, res }): Promise<boolean> {\n if (path === \"/api/sfmc/status/live\") {\n if (method !== \"POST\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n const data = await body(req);\n const raw = data.players;\n const day = data.day;\n const difficulty = data.difficulty;\n if (\n !Array.isArray(raw) || raw.length > 1000 ||\n !raw.every((name) => typeof name === \"string\" && name.trim().length > 0 && name.length <= 64) ||\n typeof day !== \"number\" || !Number.isSafeInteger(day) || day < 0 ||\n typeof difficulty !== \"string\" || difficulty.length > 32\n ) {\n json(res, { success: false, error: \"invalid_live_status\" }, 400);\n return true;\n }\n live = {\n online: raw.map((name: string) => ({ name: name.trim() })),\n world: { day, difficulty },\n updatedAt: Date.now(),\n };\n json(res, { success: true });\n return true;\n }\n\n if (path !== \"/api/sfmc/status\") return false;\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n\n let system: SystemStatusSnapshot | null = null;\n try {\n system = await collect(root);\n } catch {\n system = null;\n }\n\n const now = Date.now();\n const bds = system?.bds;\n const bdsAgeMs = typeof bds?.uptimeSec === \"number\" ? bds.uptimeSec * 1000 : null;\n const snapshot = live;\n const fresh = snapshot !== null &&\n bds?.state === \"running\" &&\n now - snapshot.updatedAt <= FRESH_MS &&\n (bdsAgeMs === null || now - snapshot.updatedAt <= bdsAgeMs + 2_000);\n const current = fresh ? snapshot : null;\n\n json(res, {\n online: current?.online ?? [],\n world: current?.world ?? null,\n updatedAt: current?.updatedAt ?? null,\n host: system?.host ?? null,\n processes: system ? { db: system.db, bds: system.bds } : null,\n source: \"bds_script_live\",\n note: fresh ? undefined : bds?.state === \"stopped\" ? \"\u670D\u52A1\u5668\u672A\u8FD0\u884C\" : \"\u7B49\u5F85\u6E38\u620F\u5B9E\u65F6\u72B6\u6001\u540C\u6B65\",\n });\n return true;\n };\n}\n\nexport { createStatusRoutes, FRESH_MS };\n"],
5
+ "mappings": "AAEA,SAAS,2BAAsD;AAC/D,SAAS,oBAAoB;AAC7B,SAAS,MAAM,YAA+B;AAE9C,MAAM,WAAW;AAajB,SAAS,mBAAmB,EAAE,eAAe,YAAY,GAAmC;AAC1F,QAAM,OAAO,eAAe;AAC5B,QAAM,UAAU,iBAAiB;AACjC,MAAI,OAA4B;AAEhC,SAAO,eAAe,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,GAAqB;AACzE,QAAI,SAAS,yBAAyB;AACpC,UAAI,WAAW,QAAQ;AACrB,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,eAAO;AAAA,MACT;AACA,YAAM,OAAO,MAAM,KAAK,GAAG;AAC3B,YAAM,MAAM,KAAK;AACjB,YAAM,MAAM,KAAK;AACjB,YAAM,aAAa,KAAK;AACxB,UACE,CAAC,MAAM,QAAQ,GAAG,KAAK,IAAI,SAAS,OACpC,CAAC,IAAI,MAAM,CAAC,SAAS,OAAO,SAAS,YAAY,KAAK,KAAK,EAAE,SAAS,KAAK,KAAK,UAAU,EAAE,KAC5F,OAAO,QAAQ,YAAY,CAAC,OAAO,cAAc,GAAG,KAAK,MAAM,KAC/D,OAAO,eAAe,YAAY,WAAW,SAAS,IACtD;AACA,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,sBAAsB,GAAG,GAAG;AAC/D,eAAO;AAAA,MACT;AACA,aAAO;AAAA,QACL,QAAQ,IAAI,IAAI,CAAC,UAAkB,EAAE,MAAM,KAAK,KAAK,EAAE,EAAE;AAAA,QACzD,OAAO,EAAE,KAAK,WAAW;AAAA,QACzB,WAAW,KAAK,IAAI;AAAA,MACtB;AACA,WAAK,KAAK,EAAE,SAAS,KAAK,CAAC;AAC3B,aAAO;AAAA,IACT;AAEA,QAAI,SAAS,mBAAoB,QAAO;AACxC,QAAI,WAAW,OAAO;AACpB,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,aAAO;AAAA,IACT;AAEA,QAAI,SAAsC;AAC1C,QAAI;AACF,eAAS,MAAM,QAAQ,IAAI;AAAA,IAC7B,QAAQ;AACN,eAAS;AAAA,IACX;AAEA,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,MAAM,QAAQ;AACpB,UAAM,WAAW,OAAO,KAAK,cAAc,WAAW,IAAI,YAAY,MAAO;AAC7E,UAAM,WAAW;AACjB,UAAM,QAAQ,aAAa,QACzB,KAAK,UAAU,aACf,MAAM,SAAS,aAAa,aAC3B,aAAa,QAAQ,MAAM,SAAS,aAAa,WAAW;AAC/D,UAAM,UAAU,QAAQ,WAAW;AAEnC,SAAK,KAAK;AAAA,MACR,QAAQ,SAAS,UAAU,CAAC;AAAA,MAC5B,OAAO,SAAS,SAAS;AAAA,MACzB,WAAW,SAAS,aAAa;AAAA,MACjC,MAAM,QAAQ,QAAQ;AAAA,MACtB,WAAW,SAAS,EAAE,IAAI,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI;AAAA,MACzD,QAAQ;AAAA,MACR,MAAM,QAAQ,SAAY,KAAK,UAAU,YAAY,yCAAW;AAAA,IAClE,CAAC;AACD,WAAO;AAAA,EACT;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sfmc-bds/db-server",
3
- "version": "0.2.0-beta.13",
3
+ "version": "0.2.0-beta.15",
4
4
  "description": "SFMC SQLite HTTP REST backend — configs, modules, gameplay data",
5
5
  "author": "Shiroha7z",
6
6
  "license": "AGPL-3.0-only",
@@ -32,10 +32,11 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "sql-template-strings": "^2.2.2",
35
- "@sfmc-bds/sdk": "0.2.0-beta.16"
35
+ "@sfmc-bds/bds-tools": "0.2.0-beta.18",
36
+ "@sfmc-bds/sdk": "0.2.0-beta.18"
36
37
  },
37
38
  "devDependencies": {
38
- "@sfmc-bds/tools": "0.2.0-beta.17"
39
+ "@sfmc-bds/tools": "0.2.0-beta.19"
39
40
  },
40
41
  "publishConfig": {
41
42
  "access": "public",
@@ -47,8 +48,6 @@
47
48
  "dev": "tsx src/index.ts",
48
49
  "format": "prettier --write \"src/**/*.{ts,tsx,js,json}\"",
49
50
  "start": "node dist/index.js",
50
- "test": "node --test dist/**/*.test.js dist/*.test.js",
51
- "test:api": "pnpm run build && node scripts/test-db-api.mjs",
52
51
  "typecheck": "tsc7 --noEmit"
53
52
  }
54
53
  }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=bridge.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bridge.test.d.ts","sourceRoot":"","sources":["../../src/domain/bridge.test.ts"],"names":[],"mappings":""}
@@ -1,87 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { makeLLBotConfig, makeOutboundConfig, forwardToQQBridge } from "./bridge.js";
4
- test("makeOutboundConfig\uFF1Aofficial / llbot \u5206\u652F", () => {
5
- const official = makeOutboundConfig({
6
- QQ_BACKEND: "official",
7
- LLBOT_HOST: "127.0.0.1",
8
- LLBOT_PORT: 3004,
9
- LLBOT_TOKEN: "",
10
- QQ_GROUP_ID: "123",
11
- QQ_APP_ID: "app",
12
- QQ_APP_SECRET: "sec",
13
- QQ_SANDBOX: true,
14
- QQ_GROUP_OPENID: "GOPEN",
15
- MCTOQQ_PREFIX: "[MC]"
16
- });
17
- assert.equal(official.backend, "official");
18
- if (official.backend === "official") {
19
- assert.equal(official.groupOpenid, "GOPEN");
20
- assert.equal(official.creds.sandbox, true);
21
- assert.equal(official.prefix, "[MC]");
22
- }
23
- const llbot = makeOutboundConfig({
24
- QQ_BACKEND: "llbot",
25
- LLBOT_HOST: "127.0.0.1",
26
- LLBOT_PORT: 3004,
27
- LLBOT_TOKEN: "t",
28
- QQ_GROUP_ID: "99",
29
- QQ_APP_ID: "",
30
- QQ_APP_SECRET: "",
31
- QQ_SANDBOX: false,
32
- QQ_GROUP_OPENID: "",
33
- MCTOQQ_PREFIX: "[X]"
34
- });
35
- assert.equal(llbot.backend, "llbot");
36
- if (llbot.backend === "llbot") {
37
- assert.equal(llbot.llbot.groupId, "99");
38
- assert.equal(llbot.llbot.prefix, "[X]");
39
- }
40
- });
41
- test("makeLLBotConfig \u4F7F\u7528 mctoqq_prefix", () => {
42
- const cfg = makeLLBotConfig({
43
- LLBOT_HOST: "h",
44
- LLBOT_PORT: 1,
45
- LLBOT_TOKEN: "",
46
- QQ_GROUP_ID: "1",
47
- MCTOQQ_PREFIX: "[P]"
48
- });
49
- assert.equal(cfg.prefix, "[P]");
50
- });
51
- test("forwardToQQBridge official\uFF1A\u7F3A group_openid \u4E0D\u629B\u9519", () => {
52
- assert.doesNotThrow(() => {
53
- forwardToQQBridge(
54
- {
55
- backend: "official",
56
- creds: { appId: "a", appSecret: "s" },
57
- groupOpenid: "",
58
- prefix: "[MC]"
59
- },
60
- "ch",
61
- "Steve",
62
- "hi",
63
- "xuid"
64
- );
65
- });
66
- });
67
- test("forwardToQQBridge llbot\uFF1A\u7F3A\u7FA4\u53F7\u4E0D\u629B\u9519", () => {
68
- assert.doesNotThrow(() => {
69
- forwardToQQBridge(
70
- {
71
- backend: "llbot",
72
- llbot: {
73
- host: "127.0.0.1",
74
- port: 3004,
75
- token: "",
76
- groupId: "0",
77
- prefix: "[MC]"
78
- }
79
- },
80
- "ch",
81
- "Steve",
82
- "hi",
83
- "xuid"
84
- );
85
- });
86
- });
87
- //# sourceMappingURL=bridge.test.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/domain/bridge.test.ts"],
4
- "sourcesContent": ["import assert from \"node:assert/strict\";\r\nimport test from \"node:test\";\r\nimport { makeLLBotConfig, makeOutboundConfig, forwardToQQBridge } from \"./bridge.js\";\r\n\r\ntest(\"makeOutboundConfig\uFF1Aofficial / llbot \u5206\u652F\", () => {\r\n const official = makeOutboundConfig({\r\n QQ_BACKEND: \"official\",\r\n LLBOT_HOST: \"127.0.0.1\",\r\n LLBOT_PORT: 3004,\r\n LLBOT_TOKEN: \"\",\r\n QQ_GROUP_ID: \"123\",\r\n QQ_APP_ID: \"app\",\r\n QQ_APP_SECRET: \"sec\",\r\n QQ_SANDBOX: true,\r\n QQ_GROUP_OPENID: \"GOPEN\",\r\n MCTOQQ_PREFIX: \"[MC]\",\r\n });\r\n assert.equal(official.backend, \"official\");\r\n if (official.backend === \"official\") {\r\n assert.equal(official.groupOpenid, \"GOPEN\");\r\n assert.equal(official.creds.sandbox, true);\r\n assert.equal(official.prefix, \"[MC]\");\r\n }\r\n\r\n const llbot = makeOutboundConfig({\r\n QQ_BACKEND: \"llbot\",\r\n LLBOT_HOST: \"127.0.0.1\",\r\n LLBOT_PORT: 3004,\r\n LLBOT_TOKEN: \"t\",\r\n QQ_GROUP_ID: \"99\",\r\n QQ_APP_ID: \"\",\r\n QQ_APP_SECRET: \"\",\r\n QQ_SANDBOX: false,\r\n QQ_GROUP_OPENID: \"\",\r\n MCTOQQ_PREFIX: \"[X]\",\r\n });\r\n assert.equal(llbot.backend, \"llbot\");\r\n if (llbot.backend === \"llbot\") {\r\n assert.equal(llbot.llbot.groupId, \"99\");\r\n assert.equal(llbot.llbot.prefix, \"[X]\");\r\n }\r\n});\r\n\r\ntest(\"makeLLBotConfig \u4F7F\u7528 mctoqq_prefix\", () => {\r\n const cfg = makeLLBotConfig({\r\n LLBOT_HOST: \"h\",\r\n LLBOT_PORT: 1,\r\n LLBOT_TOKEN: \"\",\r\n QQ_GROUP_ID: \"1\",\r\n MCTOQQ_PREFIX: \"[P]\",\r\n });\r\n assert.equal(cfg.prefix, \"[P]\");\r\n});\r\n\r\ntest(\"forwardToQQBridge official\uFF1A\u7F3A group_openid \u4E0D\u629B\u9519\", () => {\r\n assert.doesNotThrow(() => {\r\n forwardToQQBridge(\r\n {\r\n backend: \"official\",\r\n creds: { appId: \"a\", appSecret: \"s\" },\r\n groupOpenid: \"\",\r\n prefix: \"[MC]\",\r\n },\r\n \"ch\",\r\n \"Steve\",\r\n \"hi\",\r\n \"xuid\"\r\n );\r\n });\r\n});\r\n\r\ntest(\"forwardToQQBridge llbot\uFF1A\u7F3A\u7FA4\u53F7\u4E0D\u629B\u9519\", () => {\r\n assert.doesNotThrow(() => {\r\n forwardToQQBridge(\r\n {\r\n backend: \"llbot\",\r\n llbot: {\r\n host: \"127.0.0.1\",\r\n port: 3004,\r\n token: \"\",\r\n groupId: \"0\",\r\n prefix: \"[MC]\",\r\n },\r\n },\r\n \"ch\",\r\n \"Steve\",\r\n \"hi\",\r\n \"xuid\"\r\n );\r\n });\r\n});\r\n\r"],
5
- "mappings": "AAAA,OAAO,YAAY;AACnB,OAAO,UAAU;AACjB,SAAS,iBAAiB,oBAAoB,yBAAyB;AAEvE,KAAK,yDAA0C,MAAM;AACnD,QAAM,WAAW,mBAAmB;AAAA,IAClC,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,aAAa;AAAA,IACb,WAAW;AAAA,IACX,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,eAAe;AAAA,EACjB,CAAC;AACD,SAAO,MAAM,SAAS,SAAS,UAAU;AACzC,MAAI,SAAS,YAAY,YAAY;AACnC,WAAO,MAAM,SAAS,aAAa,OAAO;AAC1C,WAAO,MAAM,SAAS,MAAM,SAAS,IAAI;AACzC,WAAO,MAAM,SAAS,QAAQ,MAAM;AAAA,EACtC;AAEA,QAAM,QAAQ,mBAAmB;AAAA,IAC/B,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,aAAa;AAAA,IACb,WAAW;AAAA,IACX,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,eAAe;AAAA,EACjB,CAAC;AACD,SAAO,MAAM,MAAM,SAAS,OAAO;AACnC,MAAI,MAAM,YAAY,SAAS;AAC7B,WAAO,MAAM,MAAM,MAAM,SAAS,IAAI;AACtC,WAAO,MAAM,MAAM,MAAM,QAAQ,KAAK;AAAA,EACxC;AACF,CAAC;AAED,KAAK,8CAAoC,MAAM;AAC7C,QAAM,MAAM,gBAAgB;AAAA,IAC1B,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,aAAa;AAAA,IACb,eAAe;AAAA,EACjB,CAAC;AACD,SAAO,MAAM,IAAI,QAAQ,KAAK;AAChC,CAAC;AAED,KAAK,0EAAiD,MAAM;AAC1D,SAAO,aAAa,MAAM;AACxB;AAAA,MACE;AAAA,QACE,SAAS;AAAA,QACT,OAAO,EAAE,OAAO,KAAK,WAAW,IAAI;AAAA,QACpC,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH,CAAC;AAED,KAAK,qEAAkC,MAAM;AAC3C,SAAO,aAAa,MAAM;AACxB;AAAA,MACE;AAAA,QACE,SAAS;AAAA,QACT,OAAO;AAAA,UACL,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,UACP,SAAS;AAAA,UACT,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH,CAAC;",
6
- "names": []
7
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * messages 出站过滤单测
3
- */
4
- export {};
5
- //# sourceMappingURL=messages-bridge-filter.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"messages-bridge-filter.test.d.ts","sourceRoot":"","sources":["../../src/domain/messages-bridge-filter.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -1,20 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { shouldForwardChatToQQ } from "../routes/messages.js";
4
- test("shouldForwardChatToQQ\uFF1A\u672A\u914D bridge \u4E0D\u51FA\u7AD9", () => {
5
- assert.equal(shouldForwardChatToQQ("", "bridge", "xuid"), false);
6
- });
7
- test("shouldForwardChatToQQ\uFF1A\u9891\u9053\u4E0D\u5339\u914D\u4E0D\u51FA\u7AD9", () => {
8
- assert.equal(shouldForwardChatToQQ("bridge", "other", "xuid"), false);
9
- });
10
- test("shouldForwardChatToQQ\uFF1Aqq_ \u6765\u6E90\u4E0D\u51FA\u7AD9\uFF08\u9632\u56DE\u73AF\uFF09", () => {
11
- assert.equal(shouldForwardChatToQQ("bridge", "bridge", "qq_123"), false);
12
- });
13
- test("shouldForwardChatToQQ\uFF1A\u7A7A fromId \u4E0D\u51FA\u7AD9", () => {
14
- assert.equal(shouldForwardChatToQQ("bridge", "bridge", ""), false);
15
- assert.equal(shouldForwardChatToQQ("bridge", "bridge", " "), false);
16
- });
17
- test("shouldForwardChatToQQ\uFF1A\u5339\u914D\u9891\u9053\u7684\u73A9\u5BB6\u6D88\u606F\u51FA\u7AD9", () => {
18
- assert.equal(shouldForwardChatToQQ("bridge", "bridge", "player-1"), true);
19
- });
20
- //# sourceMappingURL=messages-bridge-filter.test.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/domain/messages-bridge-filter.test.ts"],
4
- "sourcesContent": ["/**\n * messages \u51FA\u7AD9\u8FC7\u6EE4\u5355\u6D4B\n */\n\nimport assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport { shouldForwardChatToQQ } from \"../routes/messages.js\";\n\ntest(\"shouldForwardChatToQQ\uFF1A\u672A\u914D bridge \u4E0D\u51FA\u7AD9\", () => {\n assert.equal(shouldForwardChatToQQ(\"\", \"bridge\", \"xuid\"), false);\n});\n\ntest(\"shouldForwardChatToQQ\uFF1A\u9891\u9053\u4E0D\u5339\u914D\u4E0D\u51FA\u7AD9\", () => {\n assert.equal(shouldForwardChatToQQ(\"bridge\", \"other\", \"xuid\"), false);\n});\n\ntest(\"shouldForwardChatToQQ\uFF1Aqq_ \u6765\u6E90\u4E0D\u51FA\u7AD9\uFF08\u9632\u56DE\u73AF\uFF09\", () => {\n assert.equal(shouldForwardChatToQQ(\"bridge\", \"bridge\", \"qq_123\"), false);\n});\n\ntest(\"shouldForwardChatToQQ\uFF1A\u7A7A fromId \u4E0D\u51FA\u7AD9\", () => {\n assert.equal(shouldForwardChatToQQ(\"bridge\", \"bridge\", \"\"), false);\n assert.equal(shouldForwardChatToQQ(\"bridge\", \"bridge\", \" \"), false);\n});\n\ntest(\"shouldForwardChatToQQ\uFF1A\u5339\u914D\u9891\u9053\u7684\u73A9\u5BB6\u6D88\u606F\u51FA\u7AD9\", () => {\n assert.equal(shouldForwardChatToQQ(\"bridge\", \"bridge\", \"player-1\"), true);\n});\n"],
5
- "mappings": "AAIA,OAAO,YAAY;AACnB,OAAO,UAAU;AACjB,SAAS,6BAA6B;AAEtC,KAAK,qEAAuC,MAAM;AAChD,SAAO,MAAM,sBAAsB,IAAI,UAAU,MAAM,GAAG,KAAK;AACjE,CAAC;AAED,KAAK,+EAAkC,MAAM;AAC3C,SAAO,MAAM,sBAAsB,UAAU,SAAS,MAAM,GAAG,KAAK;AACtE,CAAC;AAED,KAAK,+FAAwC,MAAM;AACjD,SAAO,MAAM,sBAAsB,UAAU,UAAU,QAAQ,GAAG,KAAK;AACzE,CAAC;AAED,KAAK,+DAAsC,MAAM;AAC/C,SAAO,MAAM,sBAAsB,UAAU,UAAU,EAAE,GAAG,KAAK;AACjE,SAAO,MAAM,sBAAsB,UAAU,UAAU,KAAK,GAAG,KAAK;AACtE,CAAC;AAED,KAAK,iGAAqC,MAAM;AAC9C,SAAO,MAAM,sBAAsB,UAAU,UAAU,UAAU,GAAG,IAAI;AAC1E,CAAC;",
6
- "names": []
7
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=player-session.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"player-session.test.d.ts","sourceRoot":"","sources":["../../src/domain/player-session.test.ts"],"names":[],"mappings":""}
@@ -1,33 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { DatabaseSync } from "node:sqlite";
3
- import test from "node:test";
4
- import { createQuery } from "../lib/sqlite.js";
5
- import { syncPlayerSession } from "./player-session.js";
6
- import { initSchema } from "./schema.js";
7
- test("initSchema \u4E3A\u65E7\u73A9\u5BB6\u8868\u8865\u9F50\u5FEB\u7167\u5B57\u6BB5", () => {
8
- const db = new DatabaseSync(":memory:");
9
- db.exec("CREATE TABLE sfmc_players (id TEXT PRIMARY KEY, name TEXT, xuid TEXT)");
10
- initSchema(db);
11
- const columns = db.prepare('PRAGMA table_info("sfmc_players")').all();
12
- const names = new Set(columns.map((column) => column.name));
13
- for (const name of [
14
- "spawn_dimension",
15
- "spawn_x",
16
- "dimension",
17
- "game_mode",
18
- "snapshot_hash",
19
- "active_channel",
20
- "subscribed_channels"
21
- ]) {
22
- assert.equal(names.has(name), true, name);
23
- }
24
- });
25
- test("BDS \u73A9\u5BB6\u4F1A\u8BDD\u5199\u5165\u5E76\u5237\u65B0 XUID", () => {
26
- const db = new DatabaseSync(":memory:");
27
- initSchema(db);
28
- const query = createQuery(db);
29
- syncPlayerSession(query, { playerName: "Steve", xuid: "123" }, 42);
30
- const row = db.prepare("SELECT id, name, xuid, last_online FROM sfmc_players WHERE id = ?").get("123");
31
- assert.deepEqual({ ...row }, { id: "123", name: "Steve", xuid: "123", last_online: 42 });
32
- });
33
- //# sourceMappingURL=player-session.test.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/domain/player-session.test.ts"],
4
- "sourcesContent": ["import assert from \"node:assert/strict\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport test from \"node:test\";\nimport { createQuery } from \"../lib/sqlite.js\";\nimport { syncPlayerSession } from \"./player-session.js\";\nimport { initSchema } from \"./schema.js\";\n\ntest(\"initSchema \u4E3A\u65E7\u73A9\u5BB6\u8868\u8865\u9F50\u5FEB\u7167\u5B57\u6BB5\", () => {\n const db = new DatabaseSync(\":memory:\");\n db.exec(\"CREATE TABLE sfmc_players (id TEXT PRIMARY KEY, name TEXT, xuid TEXT)\");\n initSchema(db);\n const columns = db.prepare('PRAGMA table_info(\"sfmc_players\")').all() as Array<{ name: string }>;\n const names = new Set(columns.map((column) => column.name));\n for (const name of [\n \"spawn_dimension\",\n \"spawn_x\",\n \"dimension\",\n \"game_mode\",\n \"snapshot_hash\",\n \"active_channel\",\n \"subscribed_channels\",\n ]) {\n assert.equal(names.has(name), true, name);\n }\n});\n\ntest(\"BDS \u73A9\u5BB6\u4F1A\u8BDD\u5199\u5165\u5E76\u5237\u65B0 XUID\", () => {\n const db = new DatabaseSync(\":memory:\");\n initSchema(db);\n const query = createQuery(db);\n syncPlayerSession(query, { playerName: \"Steve\", xuid: \"123\" }, 42);\n const row = db.prepare(\"SELECT id, name, xuid, last_online FROM sfmc_players WHERE id = ?\").get(\"123\");\n assert.deepEqual({ ...row }, { id: \"123\", name: \"Steve\", xuid: \"123\", last_online: 42 });\n});\n"],
5
- "mappings": "AAAA,OAAO,YAAY;AACnB,SAAS,oBAAoB;AAC7B,OAAO,UAAU;AACjB,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,kBAAkB;AAE3B,KAAK,iFAA0B,MAAM;AACnC,QAAM,KAAK,IAAI,aAAa,UAAU;AACtC,KAAG,KAAK,uEAAuE;AAC/E,aAAW,EAAE;AACb,QAAM,UAAU,GAAG,QAAQ,mCAAmC,EAAE,IAAI;AACpE,QAAM,QAAQ,IAAI,IAAI,QAAQ,IAAI,CAAC,WAAW,OAAO,IAAI,CAAC;AAC1D,aAAW,QAAQ;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAAG;AACD,WAAO,MAAM,MAAM,IAAI,IAAI,GAAG,MAAM,IAAI;AAAA,EAC1C;AACF,CAAC;AAED,KAAK,mEAAsB,MAAM;AAC/B,QAAM,KAAK,IAAI,aAAa,UAAU;AACtC,aAAW,EAAE;AACb,QAAM,QAAQ,YAAY,EAAE;AAC5B,oBAAkB,OAAO,EAAE,YAAY,SAAS,MAAM,MAAM,GAAG,EAAE;AACjE,QAAM,MAAM,GAAG,QAAQ,mEAAmE,EAAE,IAAI,KAAK;AACrG,SAAO,UAAU,EAAE,GAAG,IAAI,GAAG,EAAE,IAAI,OAAO,MAAM,SAAS,MAAM,OAAO,aAAa,GAAG,CAAC;AACzF,CAAC;",
6
- "names": []
7
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=qq-bind-status.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"qq-bind-status.test.d.ts","sourceRoot":"","sources":["../../src/domain/qq-bind-status.test.ts"],"names":[],"mappings":""}
@@ -1,211 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { DatabaseSync } from "node:sqlite";
4
- import { SQL } from "sql-template-strings";
5
- import { initSchema } from "./schema.js";
6
- import { createQuery } from "../lib/sqlite.js";
7
- import { createStatusRoutes, FRESH_MS } from "../routes/status.js";
8
- import { createQqBindRoutes } from "../routes/qq-bind.js";
9
- function memDb() {
10
- const db = new DatabaseSync(":memory:");
11
- initSchema(db);
12
- return { db, query: createQuery(db) };
13
- }
14
- function mockRes() {
15
- let status = 200;
16
- let body = {};
17
- return {
18
- res: {
19
- writeHead(code) {
20
- status = code;
21
- },
22
- end(s) {
23
- body = JSON.parse(s);
24
- },
25
- setHeader() {
26
- }
27
- },
28
- get status() {
29
- return status;
30
- },
31
- get body() {
32
- return body;
33
- }
34
- };
35
- }
36
- test("initSchema \u542B qq bind \u8868", () => {
37
- const { db } = memDb();
38
- const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'sfmc_qq_%' ORDER BY name").all();
39
- const names = tables.map((t) => t.name);
40
- assert.ok(names.includes("sfmc_qq_bind_pending"));
41
- assert.ok(names.includes("sfmc_qq_bindings"));
42
- assert.ok(names.includes("sfmc_qq_join_requests"));
43
- assert.ok(names.includes("sfmc_qq_admin_actions"));
44
- assert.ok(FRESH_MS > 0);
45
- });
46
- test("GET /api/sfmc/status \u7A7A\u8868\u4E0D\u629B\u9519", async () => {
47
- const { query } = memDb();
48
- const handle = createStatusRoutes({
49
- query,
50
- collectSystem: async () => ({
51
- host: {
52
- hostname: "test-host",
53
- platform: "win32",
54
- arch: "x64",
55
- release: "10.0",
56
- uptimeSec: 3600,
57
- uptimeText: "1\u65F60\u5206",
58
- cpu: { model: "Test CPU", cores: 4 },
59
- memory: { totalMb: 8192, freeMb: 4096, usedMb: 4096, usedPercent: 50 },
60
- loadavg: [0, 0, 0]
61
- },
62
- db: { pid: 1, running: true, uptimeSec: 10, uptimeText: "10\u79D2" },
63
- bds: { state: "stopped", running: false, pid: 0, uptimeSec: null, uptimeText: "\u672A\u8FD0\u884C" }
64
- })
65
- });
66
- const m = mockRes();
67
- const ok = await handle({
68
- path: "/api/sfmc/status",
69
- method: "GET",
70
- params: new URLSearchParams(),
71
- req: {},
72
- res: m.res
73
- });
74
- assert.equal(ok, true);
75
- assert.equal(m.status, 200);
76
- assert.ok(Array.isArray(m.body.online));
77
- assert.equal(m.body.online.length, 0);
78
- assert.equal(m.body.host.hostname, "test-host");
79
- assert.equal(m.body.processes.bds.state, "stopped");
80
- });
81
- test("bind request \u2192 confirm \u2192 me \u2192 unbind + \u51B2\u7A81", async () => {
82
- const { query } = memDb();
83
- let bodyData = {};
84
- const handle = createQqBindRoutes({
85
- query,
86
- body: async () => bodyData,
87
- json: (res, data, status = 200) => {
88
- res.writeHead(status);
89
- res.end(JSON.stringify(data));
90
- }
91
- });
92
- bodyData = { openid: "oa", qq_backend: "official" };
93
- let m = mockRes();
94
- await handle({
95
- path: "/api/sfmc/qq/bind/request",
96
- method: "POST",
97
- params: new URLSearchParams(),
98
- req: {},
99
- res: m.res
100
- });
101
- assert.equal(m.body.success, true);
102
- const code = String(m.body.code);
103
- assert.match(code, /^\d{6}$/);
104
- bodyData = { code, xuid: "x1", name: "Steve" };
105
- m = mockRes();
106
- await handle({
107
- path: "/api/sfmc/qq/bind/confirm",
108
- method: "POST",
109
- params: new URLSearchParams(),
110
- req: {},
111
- res: m.res
112
- });
113
- assert.equal(m.body.success, true);
114
- m = mockRes();
115
- await handle({
116
- path: "/api/sfmc/qq/bind/me",
117
- method: "GET",
118
- params: new URLSearchParams("openid=oa"),
119
- req: {},
120
- res: m.res
121
- });
122
- assert.equal(m.body.bound, true);
123
- assert.equal(m.body.binding.player_name, "Steve");
124
- bodyData = { openid: "ob", qq_backend: "official" };
125
- m = mockRes();
126
- await handle({
127
- path: "/api/sfmc/qq/bind/request",
128
- method: "POST",
129
- params: new URLSearchParams(),
130
- req: {},
131
- res: m.res
132
- });
133
- const code2 = String(m.body.code);
134
- bodyData = { code: code2, xuid: "x1", name: "Alex" };
135
- m = mockRes();
136
- await handle({
137
- path: "/api/sfmc/qq/bind/confirm",
138
- method: "POST",
139
- params: new URLSearchParams(),
140
- req: {},
141
- res: m.res
142
- });
143
- assert.equal(m.body.success, false);
144
- assert.equal(m.body.error, "player_already_bound");
145
- bodyData = { openid: "oa" };
146
- m = mockRes();
147
- await handle({
148
- path: "/api/sfmc/qq/bind/unbind",
149
- method: "POST",
150
- params: new URLSearchParams(),
151
- req: {},
152
- res: m.res
153
- });
154
- assert.equal(m.body.unbound, true);
155
- bodyData = { openid: "oa", qq_backend: "official" };
156
- m = mockRes();
157
- await handle({
158
- path: "/api/sfmc/qq/bind/request",
159
- method: "POST",
160
- params: new URLSearchParams(),
161
- req: {},
162
- res: m.res
163
- });
164
- assert.equal(m.body.success, true);
165
- });
166
- test("status \u65B0\u9C9C\u73A9\u5BB6\u8BA1\u5165 online", async () => {
167
- const { query } = memDb();
168
- const now = Date.now();
169
- query(
170
- SQL`INSERT INTO sfmc_players (id, name, updated_at) VALUES ('p1', 'Steve', ${now})`
171
- );
172
- query(
173
- SQL`INSERT INTO sfmc_world (day, difficulty, updated_at) VALUES (3, 'normal', ${String(now)})`
174
- );
175
- const handle = createStatusRoutes({
176
- query,
177
- collectSystem: async () => ({
178
- host: {
179
- hostname: "h",
180
- platform: "linux",
181
- arch: "x64",
182
- release: "6",
183
- uptimeSec: 1,
184
- uptimeText: "1\u79D2",
185
- cpu: { model: "c", cores: 2 },
186
- memory: { totalMb: 1, freeMb: 1, usedMb: 0, usedPercent: 0 },
187
- loadavg: [0, 0, 0]
188
- },
189
- db: { pid: 1, running: true, uptimeSec: 1, uptimeText: "1\u79D2" },
190
- bds: {
191
- state: "running",
192
- running: true,
193
- pid: 99,
194
- uptimeSec: 120,
195
- uptimeText: "2\u52060\u79D2"
196
- }
197
- })
198
- });
199
- const m = mockRes();
200
- await handle({
201
- path: "/api/sfmc/status",
202
- method: "GET",
203
- params: new URLSearchParams(),
204
- req: {},
205
- res: m.res
206
- });
207
- assert.equal(m.body.online.length, 1);
208
- assert.equal(m.body.world.day, 3);
209
- assert.equal(m.body.processes.bds.pid, 99);
210
- });
211
- //# sourceMappingURL=qq-bind-status.test.js.map