@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
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["/**\n *\n * \u5DE5\u4F5C\u6D41:\n * 1. \u52A0\u8F7D env\n * 2. \u6821\u9A8C Node \u7248\u672C\n * 3. openDatabase + createPlatformTables(sfmc__audit / sfmc__idempotent)\n * 4. loadManifestV2() \u2014 \u5931\u8D25 = \u542F\u52A8\u5931\u8D25\n * 5. filterEnabled(loaded, lockFileEnabled)\n * 6. buildModuleAuth({auth_token, enabled_modules}) \u2190 \u4E0E DB \u540C\u76EE\u5F55 module-tokens.json\n * 7. \u5B9E\u4F8B\u5316:SchemaRegistry / ServiceRegistry / IdempotencyStore / TxRunner\n * 8. \u88C5\u914D v2 \u8DEF\u7531 (/api/sfmc/db/* + /api/sfmc/services* + /api/sfmc/configs/:key/*)\n * \u53E6\u4FDD\u7559 messages(qq-bridge) + config/modules/health \u5E73\u53F0\u8DEF\u7531\n * 9. createServer + listen\n *\n * \u9274\u6743(handle \u5C42):\n * - /api/sfmc/db/*, /api/sfmc/services*, /api/sfmc/configs/:key(/set|notify)*\n * \u2192 module token (Authorization: Bearer ...) + ?moduleId=...\n * - \u5176\u4ED6:env.AUTH_TOKEN \u65E7 auth(NEEDS_AUTH)\n */\n\nimport http from \"node:http\";\n\nimport { createIdempotencyStore } from \"./lib/idempotency-store.js\";\nimport { loadEnv } from \"./env.js\";\nimport { buildModuleAuth, verifyModuleAuth } from \"./module-auth.js\";\nimport { loadManifestV2 } from \"./manifest-loader.js\";\nimport { log } from \"./lib/log.js\";\nimport { assertNodeVersion } from \"./lib/runtime.js\";\nimport { createQuery, openDatabase } from \"./lib/sqlite.js\";\nimport { createServer, startConsole } from \"./server.js\";\nimport { createPlatformTables } from \"./db-tables.js\";\nimport { initSchema } from \"./domain/schema.js\";\nimport { SchemaRegistry } from \"./schema-registry.js\";\nimport { ServiceRegistry } from \"./service-registry.js\";\nimport { TxRunner } from \"./tx-runner.js\";\nimport { registerEnabledBuiltinServices } from \"./services/builtin-handlers.js\";\nimport { syncModuleRuntimeState } from \"./module-runtime-sync.js\";\n\nimport { ensureJson, patchJson, readJson } from \"@sfmc-bds/sdk/node/config\";\nimport { join } from \"node:path\";\n\nimport { createModuleConfigRoutes } from \"./routes/module-config-routes.js\";\nimport { createDbRoutes } from \"./routes/db-routes.js\";\nimport { createServiceRoutes } from \"./routes/service-routes.js\";\nimport { jsonV2Fail } from \"./routes/_shared.js\";\n\nimport { createStatusRoutes } from \"./routes/status.js\";\nimport { createQqBindRoutes } from \"./routes/qq-bind.js\";\nimport { createQqJoinRoutes, type JoinFeatureFlags } from \"./routes/qq-join.js\";\nimport { createQqEventsRoutes } from \"./routes/qq-events.js\";\nimport { createMessagesRoutes } from \"./routes/messages.js\";\nimport { createModuleRoutes } from \"./routes/modules.js\";\nimport { createConfigRoutes } from \"./routes/config.js\";\nimport { createHealthRoutes } from \"./routes/health.js\";\nimport { forwardToQQBridge, makeOutboundConfig } from \"./domain/bridge.js\";\nimport {\n createQqEventsAggregator,\n resolveQqEventsConfig,\n} from \"./domain/qq-events.js\";\nimport { isEnabled, loadModuleLock, saveModuleLock, updateModuleState } from \"./lib/module-state.js\";\nimport { body as sharedBody, json as sharedJson } from \"./lib/http.js\";\n\nif (!assertNodeVersion(22, 13)) {\n process.exit(2);\n}\n\nconst env = loadEnv();\nconst db = openDatabase(env.DB_PATH);\ncreatePlatformTables(db); // sfmc__audit / sfmc__idempotent\ninitSchema(db); // \u5E73\u53F0\u4E1A\u52A1 bootstrap \u8868(players/world/chat/...)\u2014\u2014qq-bridge \u5728 SAPI defineTable \u4E4B\u524D\u5C31\u9700\u8981\nconst query = createQuery(db);\n\n// \u2500\u2500 v2 manifest \u52A0\u8F7D(\u5931\u8D25 = \u542F\u52A8\u5931\u8D25)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst loadedManifest = loadManifestV2(); // throws on violation\nlog.success(\n `[manifest v2] loaded ${Object.keys(loadedManifest.modules).length} modules; provides ${loadedManifest.providesMap.size} services`\n);\n\n// \u2500\u2500 enabled \u96C6\u5408(\u4ECE lock file)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst lockFile = loadModuleLock(env.MODULE_LOCK_PATH);\nconst moduleCatalog = readJson<{ modules?: unknown[] }>(env.MODULE_CATALOG_PATH) ?? { modules: [] };\nconst catalogIds = new Set(\n Array.isArray(moduleCatalog.modules)\n ? (moduleCatalog.modules as Array<{ id?: string }>)\n .map((m) => String(m.id || \"\"))\n .filter((id) => id.length > 0)\n : []\n);\nconst enabledSet = new Set<string>();\nfor (const id of Object.keys(loadedManifest.modules)) {\n if (!catalogIds.has(id)) continue;\n const catalogEntry = (moduleCatalog.modules as Array<Record<string, unknown>>).find(\n (m) => String(m.id) === id\n );\n if (!catalogEntry) continue;\n const defaultEnabled = catalogEntry.enabledByDefault !== false;\n if (isEnabled(lockFile, id, defaultEnabled)) enabledSet.add(id);\n}\nconst enabledManifests = new Map<string, NonNullable<typeof loadedManifest.modules[string]>>();\nfor (const id of enabledSet) {\n const m = loadedManifest.modules[id];\n if (m) enabledManifests.set(id, m);\n}\nlog.info(\n `[manifest v2] enabled: ${[...enabledSet].sort().join(\", \") || \"(none)\"}`\n);\n\n// \u2500\u2500 \u6A21\u5757 HMAC token map(\u4E0E DB \u540C\u76EE\u5F55 module-tokens.json)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst moduleAuth = buildModuleAuth({\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledModuleIds: [...enabledSet],\n});\n\n// \u2500\u2500 \u4E09\u4EF6\u5957 + \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst schemaRegistry = new SchemaRegistry(db);\nconst serviceRegistry = new ServiceRegistry();\nconst idempotent = createIdempotencyStore(db);\nconst txRunner = new TxRunner({\n db,\n query,\n schema: schemaRegistry,\n serviceRegistry,\n enabled: enabledManifests,\n});\n\n// \u2500\u2500 \u8FDB\u7A0B\u5185\u7F6E service handler(\u6269\u5C55\u70B9:BUILTIN_SERVICE_PLUGINS) \u2500\u2500\n{\n const plugins = registerEnabledBuiltinServices(serviceRegistry, { query, db }, enabledSet);\n if (plugins > 0) {\n log.success(\n `[service] registered ${serviceRegistry.list().length} handlers from ${plugins} builtin plugin(s)`\n );\n }\n}\n\nconst json = sharedJson;\nconst body = sharedBody;\n\n// \u2500\u2500 \u5DE5\u5177\u51FD\u6570 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction loadModuleCatalog() {\n return Array.isArray(moduleCatalog.modules) ? moduleCatalog.modules : [];\n}\n\nfunction buildModuleList() {\n const catalog = loadModuleCatalog();\n return catalog\n .map((raw) => {\n const entry =\n (raw as Record<string, unknown>).entry && typeof (raw as Record<string, unknown>).entry === \"object\"\n ? ((raw as Record<string, unknown>).entry as Record<string, unknown>)\n : {};\n const id = String((raw as Record<string, unknown>).id || \"\").trim();\n const configKey = String(\n (raw as Record<string, unknown>).configKey || (raw as Record<string, unknown>).config_key || \"\"\n ).trim();\n if (!id || !configKey) return null;\n const state = lockFile.modules[id];\n const enabled = isEnabled(lockFile, id, (raw as Record<string, unknown>).enabledByDefault !== false);\n return {\n id,\n module_id: id,\n name: configKey,\n configKey,\n config_key: configKey,\n display_name: String((raw as Record<string, unknown>).name || configKey),\n description: String((raw as Record<string, unknown>).description || \"\"),\n default_enabled: (raw as Record<string, unknown>).enabledByDefault !== false,\n can_disable: moduleCanDisable(raw as Record<string, unknown>),\n // ConfigManager \u8BA4 installed!==false;\u5DF2\u88C5\u5305\u9ED8\u8BA4 true\n installed: true,\n requires: Array.isArray((raw as Record<string, unknown>).requires)\n ? ((raw as Record<string, unknown>).requires as unknown[]).filter(Boolean).map(String)\n : [],\n optional: Array.isArray((raw as Record<string, unknown>).optional)\n ? ((raw as Record<string, unknown>).optional as unknown[]).filter(Boolean).map(String)\n : [],\n commands: Array.isArray((raw as Record<string, unknown>).commands)\n ? ((raw as Record<string, unknown>).commands as unknown[]).filter(Boolean).map(String)\n : [],\n entry: {\n kind: String(entry.kind || \"\"),\n path: String(entry.path || \"\"),\n init: String(entry.init || \"\"),\n },\n updated_at: state?.updatedAt ?? null,\n enabled: !!enabled,\n };\n })\n .filter(Boolean);\n}\n\n/** catalog \u7701\u7565 canDisable \u65F6\u9ED8\u8BA4\u5141\u8BB8\u7981\u7528(\u4E0E buildModuleList.can_disable \u540C\u6E90)\u3002 */\nfunction moduleCanDisable(raw: Record<string, unknown>): boolean {\n return raw.canDisable !== false;\n}\n\nfunction resolveModuleByKey(key: string) {\n const k = String(key || \"\").trim();\n const catalog = loadModuleCatalog();\n const raw = catalog.find(\n (m) =>\n String((m as Record<string, unknown>).id || \"\") === k ||\n String((m as Record<string, unknown>).configKey || (m as Record<string, unknown>).config_key || \"\") === k\n ) as Record<string, unknown> | undefined;\n if (!raw) return null;\n const id = String(raw.id || \"\").trim();\n const configKey = String(raw.configKey || raw.config_key || \"\").trim();\n if (!id || !configKey) return null;\n /* LSP:\u4E0E buildModuleList.can_disable \u540C\u6E90 \u2014 \u7701\u7565\u5B57\u6BB5\u89C6\u4E3A\u53EF\u7981\u7528 */\n return { id, configKey, canDisable: moduleCanDisable(raw) };\n}\n\nfunction setModuleEnabled(mod: { id: string; canDisable: boolean }, enabled: boolean) {\n // \u76F4\u63A5\u66F4\u65B0\u542F\u52A8\u65F6\u7F13\u5B58\u7684 lockFile(\u800C\u975E\u53E6\u8BFB\u4E00\u4EFD\u65B0\u526F\u672C),\n // \u5426\u5219 buildModuleList() \u8BFB\u7684\u4ECD\u662F\u65E7\u7F13\u5B58,\u5BFC\u81F4\u542F\u505C\u540E enabled \u72B6\u6001\u4E0D\u7FFB\u8F6C\u3002\n updateModuleState(lockFile, mod.id, { enabled: !!enabled });\n // DRY:\u4E0E loadModuleLock \u5BF9\u79F0\u8D70 saveModuleLock,\u52FF\u6563\u843D writeJson\n saveModuleLock(env.MODULE_LOCK_PATH, lockFile);\n\n // DIP:\u70ED\u540C\u6B65 enabledSet / tokens / manifests / builtin handlers(\u4E0D\u91CD\u542F db-server)\n syncModuleRuntimeState({\n moduleId: mod.id,\n enabled: !!enabled,\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledSet,\n enabledManifests,\n loadedManifest,\n moduleAuth,\n serviceRegistry,\n builtinDeps: { query, db },\n });\n}\n\n// \u2500\u2500 \u5E73\u53F0\u8DEF\u7531(\u975E\u6A21\u5757\u4E1A\u52A1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst healthRoutes = createHealthRoutes();\nconst statusRoutes = createStatusRoutes({ query });\nconst qqBindRoutes = createQqBindRoutes({ query, body, json });\n\n/** \u6A21\u5757 qq-link \u7684\u914D\u7F6E\u6587\u4EF6\uFF08\u975E SDK qq_config\uFF09 */\nconst QQ_LINK_CONFIG_KEY = \"qq_link\";\nconst QQ_LINK_DEFAULTS = {\n allowlist_enabled: true,\n require_approval: true,\n /** \u662F\u5426\u5C06 QQ \u7FA4\u4E3B/\u7FA4\u7BA1\u89C6\u4F5C SFMC \u7BA1\u7406\u5458\uFF1B\u4EC5\u6539\u672C\u6587\u4EF6\uFF0CAPI/\u7FA4\u804A\u4E0D\u53EF\u5199 */\n treat_group_admins_as_admins: false,\n} as const;\n\nfunction asConfigBool(v: unknown, fallback: boolean): boolean {\n if (v === undefined || v === null) return fallback;\n if (typeof v === \"boolean\") return v;\n const s = String(v).trim().toLowerCase();\n if (s === \"true\" || s === \"1\" || s === \"yes\" || s === \"on\") return true;\n if (s === \"false\" || s === \"0\" || s === \"no\" || s === \"off\") return false;\n return fallback;\n}\n\nfunction qqLinkConfigFile(): string {\n return join(env.PROJECT_ROOT, \"configs\", `${QQ_LINK_CONFIG_KEY}.json`);\n}\n\nfunction readJoinFlags(): JoinFeatureFlags {\n // \u6A21\u5757\u914D\u7F6E configs/qq_link.json\uFF08\u975E SDK qq_config\uFF09\n const disk = ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n return {\n allowlistEnabled: asConfigBool(disk.allowlist_enabled, QQ_LINK_DEFAULTS.allowlist_enabled),\n requireApproval: asConfigBool(disk.require_approval, QQ_LINK_DEFAULTS.require_approval),\n treatGroupAdminsAsAdmins: asConfigBool(\n disk.treat_group_admins_as_admins,\n QQ_LINK_DEFAULTS.treat_group_admins_as_admins\n ),\n };\n}\n\nfunction writeJoinFlags(partial: Partial<JoinFeatureFlags>): JoinFeatureFlags {\n const file = qqLinkConfigFile();\n ensureJson<Record<string, unknown>>(file, { ...QQ_LINK_DEFAULTS });\n const patch: Record<string, boolean> = {};\n if (partial.allowlistEnabled !== undefined) patch.allowlist_enabled = partial.allowlistEnabled;\n if (partial.requireApproval !== undefined) patch.require_approval = partial.requireApproval;\n // \u6545\u610F\u4E0D\u5199 treat_group_admins_as_admins\uFF1A\u4EC5\u4EBA\u5DE5\u6539 configs/qq_link.json\n if (Object.keys(patch).length > 0) {\n patchJson(file, patch);\n }\n return readJoinFlags();\n}\n\n// \u542F\u52A8\u5373\u843D\u76D8\u6A21\u5757\u9ED8\u8BA4\u914D\u7F6E\uFF0C\u907F\u514D\u300C\u4ECE\u672A\u70B9\u8FC7\u914D\u7F6E/\u7533\u8BF7\u300D\u65F6 configs/qq_link.json \u4E0D\u5B58\u5728\n{\n const created = !readJson(qqLinkConfigFile());\n ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n if (created) {\n log.info(`\u5DF2\u5199\u5165\u6A21\u5757\u914D\u7F6E\u9AA8\u67B6: configs/${QQ_LINK_CONFIG_KEY}.json`);\n }\n}\n\nconst qqJoinRoutes = createQqJoinRoutes({\n query,\n body,\n json,\n getAdminOpenids: () => {\n const raw = env.qqconfig[\"qq_admin_openids\"];\n if (!Array.isArray(raw)) return [];\n return raw.map((x) => String(x).trim()).filter(Boolean);\n },\n getJoinFlags: () => readJoinFlags(),\n setJoinFlags: (partial) => writeJoinFlags(partial),\n});\n\nfunction currentOutbound() {\n return makeOutboundConfig({\n QQ_BACKEND: env.QQ_BACKEND,\n LLBOT_HOST: env.LLBOT_HOST,\n LLBOT_PORT: env.LLBOT_PORT,\n LLBOT_TOKEN: env.LLBOT_TOKEN,\n QQ_GROUP_ID: env.QQ_GROUP_ID,\n QQ_APP_ID: env.QQ_APP_ID,\n QQ_APP_SECRET: env.QQ_APP_SECRET,\n QQ_SANDBOX: env.QQ_SANDBOX,\n QQ_GROUP_OPENID: env.QQ_GROUP_OPENID,\n MCTOQQ_PREFIX: env.MCTOQQ_PREFIX,\n });\n}\n\nconst qqEventsAggregator = createQqEventsAggregator({\n getConfig: () => resolveQqEventsConfig(env.qqconfig[\"qq_events\"]),\n getOutbound: () => currentOutbound(),\n});\nconst qqEventsRoutes = createQqEventsRoutes({\n body,\n json,\n aggregator: qqEventsAggregator,\n});\n\nconst messagesRoutes = createMessagesRoutes({\n query,\n body,\n json,\n getBridgeChannelId: () => String(env.QQ_BRIDGE_CHANNEL_ID ?? \"\").trim(),\n forwardToQQBridge: (channelId: string, fromName: string, content: string, fromId: string) =>\n forwardToQQBridge(currentOutbound(), channelId, fromName, content, fromId),\n});\nconst configRoutes = createConfigRoutes({\n json,\n projectRoot: env.PROJECT_ROOT,\n // DIP:\u8DEF\u7531\u4E0D\u8BFB lock/catalog/token \u6587\u4EF6,\u7531\u5165\u53E3\u6CE8\u5165\u4E0E /modules \u540C\u6E90\u6570\u636E\n listModules: () => buildModuleList() as Array<Record<string, unknown>>,\n getModuleTokens: () => ({ ...moduleAuth.tokens }),\n});\nconst moduleRoutesInstance = createModuleRoutes({\n loadModuleCatalog,\n buildModuleList: buildModuleList as unknown as () => Array<Record<string, unknown>>,\n resolveModuleByKey,\n setModuleEnabled,\n body,\n json,\n});\n\n// \u2500\u2500 v2 \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// \u7C7B\u578B\u65AD\u8A00\u6210 unknown \u51FD\u6570 \u2014 v2 routes \u7684 ctx \u7C7B\u578B\u4E0E RouteCtx \u4E0D\u517C\u5BB9,\n// \u4F46\u8C03\u7528\u5951\u7EA6({path, method, params, req, res, body?})\u662F\u7A33\u5B9A\u7684\u3002\nconst dbRoutes = createDbRoutes({ schemaRegistry, txRunner, idempotent, json }) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\nconst serviceRoutes = createServiceRoutes({\n serviceRegistry,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\nconst moduleConfigRoutes = createModuleConfigRoutes({\n projectRoot: env.PROJECT_ROOT,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\n\n// \u2500\u2500 \u4E3B\u8BF7\u6C42\u5904\u7406\u5668 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const url = new URL(req.url || \"/\", `http://${req.headers.host || \"localhost\"}`);\n const path = url.pathname;\n const method = req.method || \"GET\";\n const params = url.searchParams;\n\n // loopback \u7ED1\u5B9A\n const remote = req.socket.remoteAddress || \"\";\n if (remote && !remote.startsWith(\"127.\") && remote !== \"::1\" && remote !== \"::ffff:127.\") {\n json(res, { success: false, error: \"forbidden\" }, 403);\n return;\n }\n\n // \u2500\u2500 \u9884\u8BFB body(\u6240\u6709\u8DEF\u7531\u5171\u4EAB) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await body(req);\n\n // \u2500\u2500 v2 \u6A21\u5757\u8EAB\u4EFD\u6821\u9A8C \u2192 \u5199\u5165\u8DEF\u7531 ctx.moduleAuth(LoD:\u4E0D\u6302 req \u79C1\u6709\u5B57\u6BB5) \u2500\u2500\n // \u6CE8\u610F:`/api/sfmc/configs/all` \u662F\u65E7\u7684\u4E00\u6B21\u6027\u914D\u7F6E\u5FEB\u7167\u7AEF\u70B9(SAPI ConfigManager.init\n // \u542F\u52A8\u5FC5\u7528),\u4E0D\u5C5E\u4E8E v2 \u6A21\u5757\u914D\u7F6E\u547D\u540D\u7A7A\u95F4(configs/<\u6A21\u5757 configKey>),\u5FC5\u987B\u8C41\u514D,\n // \u5426\u5219\u4F1A\u88AB\u6A21\u5757\u9274\u6743\u7F51\u5173\u62E6\u6210 401,\u5BFC\u81F4\u63D2\u4EF6\u7AEF\u8D77\u4E0D\u6765\u3002\n const isLegacyConfigAll = path === \"/api/sfmc/configs/all\";\n const needsModuleAuth =\n path.startsWith(\"/api/sfmc/db/\") ||\n path.startsWith(\"/api/sfmc/services\") ||\n (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+(?:\\/(?:set|notify))?$/.test(path) && !isLegacyConfigAll);\n let moduleAuthCtx: { id: string; permissions: string[] } | null = null;\n if (needsModuleAuth) {\n const id = verifyModuleAuth({\n headers: req.headers,\n params,\n auth: moduleAuth,\n enabledModuleIds: enabledSet,\n });\n if (!id) {\n // LSP: v2 \u6A21\u5757\u95E8\u4E0E service-routes \u540C\u7528 ok \u65B9\u8A00\n jsonV2Fail(res, \"unauthorized: module identity invalid\", 401, \"unauthorized\");\n return;\n }\n const manifest = enabledManifests.get(id);\n moduleAuthCtx = {\n id,\n permissions: manifest?.permissions ?? [],\n };\n } else {\n // \u2500\u2500 \u65E7 env token \u9274\u6743(\u53EA\u5BF9 POST/PUT \u751F\u6548) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const PUBLIC_GET =\n path === \"/api/health\" ||\n path === \"/api/sfmc/status\" ||\n path === \"/api/sfmc/qq/join/apply-queue\" ||\n path === \"/api/sfmc/qq/join/settings\" ||\n path === \"/api/sfmc/qq/admin/action-queue\" ||\n (method === \"GET\" &&\n (path === \"/api/sfmc/modules\" ||\n path === \"/api/sfmc/modules/catalog\" ||\n path.startsWith(\"/api/sfmc/modules/\") ||\n path === \"/api/sfmc/qq/bind/me\" ||\n path === \"/api/sfmc/qq/join/pending\"));\n // bind/join/events POST \u4E0E messages \u4E00\u6837\u8D70\u65E7 AUTH_TOKEN\uFF08\u82E5\u914D\u7F6E\uFF09\uFF1B\u6E38\u620F/\u6865\u540C\u4E3A loopback\n const NEEDS_AUTH =\n !PUBLIC_GET &&\n method !== \"GET\" &&\n !(\n path.startsWith(\"/api/sfmc/qq/bind/\") ||\n path.startsWith(\"/api/sfmc/qq/join/\") ||\n path.startsWith(\"/api/sfmc/qq/admin/\") ||\n path.startsWith(\"/api/sfmc/qq/events\")\n );\n if (env.AUTH_TOKEN && NEEDS_AUTH) {\n const auth = req.headers[\"authorization\"] || \"\";\n const provided = auth.startsWith(\"Bearer \") ? auth.slice(7) : (req.headers[\"x-db-token\"] as string) || \"\";\n if (provided !== env.AUTH_TOKEN) {\n json(res, { success: false, error: \"unauthorized\" }, 401);\n return;\n }\n }\n }\n\n try {\n // \u2500\u2500 v2 \u8DEF\u7531(\u4F18\u5148\u5339\u914D) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (moduleAuthCtx && (path.startsWith(\"/api/sfmc/db/\") || path.startsWith(\"/api/sfmc/services\") || (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+/.test(path) && !isLegacyConfigAll))) {\n const ctx: Record<string, unknown> = {\n path,\n method,\n params,\n req,\n res,\n moduleAuth: moduleAuthCtx,\n };\n // body \u5DF2\u5728\u4E0A\u9762 `await body(req)` \u9884\u8BFB\u5E76\u7F13\u5B58\u5230 req._bodyPromise;\n // \u8FD9\u91CC\u590D\u7528\u7F13\u5B58(\u539F\u5B9E\u73B0\u8BFB\u7684 req._body \u4ECE\u672A\u88AB\u8D4B\u503C,\u5BFC\u81F4\u6240\u6709 v2 \u8DEF\u7531 body \u6052\u4E3A\u7A7A)\u3002\n ctx[\"body\"] = await body(req);\n if (path.startsWith(\"/api/sfmc/db/\")) {\n if (await dbRoutes(ctx)) return;\n } else if (path.startsWith(\"/api/sfmc/services\")) {\n if (await serviceRoutes(ctx)) return;\n } else {\n if (await moduleConfigRoutes(ctx)) return;\n }\n }\n\n // \u2500\u2500 \u5E73\u53F0\u8DEF\u7531 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const ctxBase = { path, method, params, req, res } as { path: string; method: string; params: URLSearchParams; req: http.IncomingMessage; res: http.ServerResponse };\n if (await moduleRoutesInstance(ctxBase)) return;\n if (await healthRoutes(ctxBase)) return;\n if (await statusRoutes(ctxBase)) return;\n if (await qqBindRoutes(ctxBase)) return;\n if (await qqJoinRoutes(ctxBase)) return;\n if (await qqEventsRoutes(ctxBase)) return;\n if (await messagesRoutes(ctxBase)) return;\n if (await configRoutes(ctxBase)) return;\n\n json(res, { success: false, error: \"not_found\" }, 404);\n } catch (err) {\n log.err(err, \"DogeDB\");\n json(res, { success: false, error: (err as Error).message }, 500);\n }\n}\n\n// \u2500\u2500 \u542F\u52A8 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst server = createServer({\n env: { PORT: env.PORT, HOST: env.HOST, AUTH_TOKEN: env.AUTH_TOKEN },\n handle,\n});\nstartConsole(server, db);\n\nexport { db, env, query, schemaRegistry, serviceRegistry, txRunner, enabledManifests, moduleAuth };\n"],
5
- "mappings": "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",
6
- "names": []
4
+ "sourcesContent": ["/**\n *\n * \u5DE5\u4F5C\u6D41:\n * 1. \u52A0\u8F7D env\n * 2. \u6821\u9A8C Node \u7248\u672C\n * 3. openDatabase + createPlatformTables(sfmc__audit / sfmc__idempotent)\n * 4. loadManifestV2() \u2014 \u5931\u8D25 = \u542F\u52A8\u5931\u8D25\n * 5. filterEnabled(loaded, lockFileEnabled)\n * 6. buildModuleAuth({auth_token, enabled_modules}) \u2190 \u4E0E DB \u540C\u76EE\u5F55 module-tokens.json\n * 7. \u5B9E\u4F8B\u5316:SchemaRegistry / ServiceRegistry / IdempotencyStore / TxRunner\n * 8. \u88C5\u914D v2 \u8DEF\u7531 (/api/sfmc/db/* + /api/sfmc/services* + /api/sfmc/configs/:key/*)\n * \u53E6\u4FDD\u7559 messages(qq-bridge) + config/modules/health \u5E73\u53F0\u8DEF\u7531\n * 9. createServer + listen\n *\n * \u9274\u6743(handle \u5C42):\n * - /api/sfmc/db/*, /api/sfmc/services*, /api/sfmc/configs/:key(/set|notify)*\n * \u2192 module token (Authorization: Bearer ...) + ?moduleId=...\n * - \u5176\u4ED6:env.AUTH_TOKEN \u65E7 auth(NEEDS_AUTH)\n */\n\nimport http from \"node:http\";\nimport { SQL } from \"sql-template-strings\";\n\nimport { createPlatformTables } from \"./db-tables.js\";\nimport { initSchema } from \"./domain/schema.js\";\nimport { syncPlayerSession } from \"./domain/player-session.js\";\nimport { loadEnv } from \"./env.js\";\nimport { createIdempotencyStore } from \"./lib/idempotency-store.js\";\nimport { log } from \"./lib/log.js\";\nimport { assertNodeVersion } from \"./lib/runtime.js\";\nimport { createQuery, openDatabase } from \"./lib/sqlite.js\";\nimport { loadManifestV2 } from \"./manifest-loader.js\";\nimport { buildModuleAuth, verifyModuleAuth } from \"./module-auth.js\";\nimport { syncModuleRuntimeState } from \"./module-runtime-sync.js\";\nimport { SchemaRegistry } from \"./schema-registry.js\";\nimport { createServer, startConsole } from \"./server.js\";\nimport { ServiceRegistry } from \"./service-registry.js\";\nimport { registerEnabledBuiltinServices } from \"./services/builtin-handlers.js\";\nimport { TxRunner } from \"./tx-runner.js\";\n\nimport { configPath, ensureJson, patchJson, readJson } from \"@sfmc-bds/sdk/node/config\";\nimport path, { join } from \"node:path\";\n\nimport { jsonV2Fail } from \"./routes/_shared.js\";\nimport { createDbRoutes } from \"./routes/db-routes.js\";\nimport { createModuleConfigRoutes } from \"./routes/module-config-routes.js\";\nimport { createServiceRoutes } from \"./routes/service-routes.js\";\n\nimport { forwardToQQBridge, makeOutboundConfig } from \"./domain/bridge.js\";\nimport { createQqEventsAggregator, resolveQqEventsConfig, type ResolvedQqEventsConfig } from \"./domain/qq-events.js\";\nimport { body as sharedBody, json as sharedJson } from \"./lib/http.js\";\nimport { isEnabled, loadModuleLock, saveModuleLock, updateModuleState } from \"./lib/module-state.js\";\nimport { loadContentSnapshot } from \"./domain/installed-content.js\";\nimport { createConfigRoutes } from \"./routes/config.js\";\nimport { createContentRoutes } from \"./routes/content.js\";\nimport { createHealthRoutes } from \"./routes/health.js\";\nimport { createMessagesRoutes } from \"./routes/messages.js\";\nimport { createModuleRoutes } from \"./routes/modules.js\";\nimport { createQqBindRoutes } from \"./routes/qq-bind.js\";\nimport { createQqEventsRoutes } from \"./routes/qq-events.js\";\nimport { createQqJoinRoutes, type JoinFeatureFlags } from \"./routes/qq-join.js\";\nimport { createStatusRoutes } from \"./routes/status.js\";\n\nif (!assertNodeVersion(22, 13)) {\n process.exit(2);\n}\n\nconst env = loadEnv();\nconst db = openDatabase(env.DB_PATH);\ncreatePlatformTables(db); // sfmc__audit / sfmc__idempotent\ninitSchema(db); // \u5E73\u53F0\u4E1A\u52A1 bootstrap \u8868(players/world/chat/...)\u2014\u2014qq-bridge \u5728 SAPI defineTable \u4E4B\u524D\u5C31\u9700\u8981\nconst query = createQuery(db);\n\n// \u2500\u2500 v2 manifest \u52A0\u8F7D(\u5931\u8D25 = \u542F\u52A8\u5931\u8D25)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst loadedManifest = loadManifestV2(); // throws on violation\nlog.success(\n `[manifest v2] loaded ${Object.keys(loadedManifest.modules).length} modules; provides ${loadedManifest.providesMap.size} services`\n);\n\n// \u2500\u2500 enabled \u96C6\u5408(\u4ECE lock file)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst lockFile = loadModuleLock(env.MODULE_LOCK_PATH);\nconst moduleCatalog = readJson<{ modules?: unknown[] }>(env.MODULE_CATALOG_PATH) ?? { modules: [] };\nconst catalogIds = new Set(\n Array.isArray(moduleCatalog.modules)\n ? (moduleCatalog.modules as Array<{ id?: string }>).map((m) => String(m.id || \"\")).filter((id) => id.length > 0)\n : []\n);\nconst enabledSet = new Set<string>();\nfor (const id of Object.keys(loadedManifest.modules)) {\n if (!catalogIds.has(id)) continue;\n const catalogEntry = (moduleCatalog.modules as Array<Record<string, unknown>>).find((m) => String(m.id) === id);\n if (!catalogEntry) continue;\n const defaultEnabled = catalogEntry.enabledByDefault !== false;\n if (isEnabled(lockFile, id, defaultEnabled)) enabledSet.add(id);\n}\nconst enabledManifests = new Map<string, NonNullable<(typeof loadedManifest.modules)[string]>>();\nfor (const id of enabledSet) {\n const m = loadedManifest.modules[id];\n if (m) enabledManifests.set(id, m);\n}\nlog.info(`[manifest v2] enabled: ${[...enabledSet].sort().join(\", \") || \"(none)\"}`);\n\n// \u2500\u2500 \u6A21\u5757 HMAC token map(\u4E0E DB \u540C\u76EE\u5F55 module-tokens.json)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst moduleAuth = buildModuleAuth({\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledModuleIds: [...enabledSet],\n});\n\n// \u2500\u2500 \u4E09\u4EF6\u5957 + \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst schemaRegistry = new SchemaRegistry(db);\nconst serviceRegistry = new ServiceRegistry();\nconst idempotent = createIdempotencyStore(db);\nconst txRunner = new TxRunner({\n db,\n query,\n schema: schemaRegistry,\n serviceRegistry,\n enabled: enabledManifests,\n});\n\n// \u2500\u2500 \u8FDB\u7A0B\u5185\u7F6E service handler(\u6269\u5C55\u70B9:BUILTIN_SERVICE_PLUGINS) \u2500\u2500\n{\n const plugins = registerEnabledBuiltinServices(serviceRegistry, { query, db }, enabledSet);\n if (plugins > 0) {\n log.success(`[service] registered ${serviceRegistry.list().length} handlers from ${plugins} builtin plugin(s)`);\n }\n}\n\nconst json = sharedJson;\nconst body = sharedBody;\n\n// \u2500\u2500 \u5DE5\u5177\u51FD\u6570 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction loadModuleCatalog() {\n return Array.isArray(moduleCatalog.modules) ? moduleCatalog.modules : [];\n}\n\nfunction buildModuleList() {\n const catalog = loadModuleCatalog();\n return catalog\n .map((raw) => {\n const entry =\n (raw as Record<string, unknown>).entry && typeof (raw as Record<string, unknown>).entry === \"object\"\n ? ((raw as Record<string, unknown>).entry as Record<string, unknown>)\n : {};\n const id = String((raw as Record<string, unknown>).id || \"\").trim();\n const configKey = String(\n (raw as Record<string, unknown>).configKey || (raw as Record<string, unknown>).config_key || \"\"\n ).trim();\n if (!id || !configKey) return null;\n const state = lockFile.modules[id];\n const enabled = isEnabled(lockFile, id, (raw as Record<string, unknown>).enabledByDefault !== false);\n return {\n id,\n module_id: id,\n name: configKey,\n configKey,\n config_key: configKey,\n display_name: String((raw as Record<string, unknown>).name || configKey),\n description: String((raw as Record<string, unknown>).description || \"\"),\n default_enabled: (raw as Record<string, unknown>).enabledByDefault !== false,\n can_disable: moduleCanDisable(raw as Record<string, unknown>),\n // ConfigManager \u8BA4 installed!==false;\u5DF2\u88C5\u5305\u9ED8\u8BA4 true\n installed: true,\n requires: Array.isArray((raw as Record<string, unknown>).requires)\n ? ((raw as Record<string, unknown>).requires as unknown[]).filter(Boolean).map(String)\n : [],\n optional: Array.isArray((raw as Record<string, unknown>).optional)\n ? ((raw as Record<string, unknown>).optional as unknown[]).filter(Boolean).map(String)\n : [],\n commands: Array.isArray((raw as Record<string, unknown>).commands)\n ? ((raw as Record<string, unknown>).commands as unknown[]).filter(Boolean).map(String)\n : [],\n entry: {\n kind: String(entry.kind || \"\"),\n path: String(entry.path || \"\"),\n init: String(entry.init || \"\"),\n },\n updated_at: state?.updatedAt ?? null,\n enabled: !!enabled,\n };\n })\n .filter(Boolean);\n}\n\n/**\n * QQ\u300C\u6A21\u5757 / \u4E16\u754C\u5305\u300D\u5FEB\u7167\u3002\u6BCF\u6B21\u8BF7\u6C42\u73B0\u8BFB catalog \u4E0E\u78C1\u76D8\uFF0C\u88C5\u5305\u540E\u4E0D\u7528\u91CD\u542F db-server\u3002\n */\nfunction loadContentSnapshotForRequest() {\n const modules = (buildModuleList() ?? []).flatMap((raw) => {\n if (!raw || typeof raw !== \"object\") return [];\n const row = raw as { id?: unknown; display_name?: unknown; enabled?: unknown };\n const id = String(row.id ?? \"\").trim();\n if (!id) return [];\n return [\n {\n id,\n display_name: String(row.display_name || id),\n enabled: row.enabled !== false,\n },\n ];\n });\n const cfg = readJson<{ bds_path?: string }>(configPath(env.PROJECT_ROOT, \"bds_updater.json\"));\n const rawBds = typeof cfg?.bds_path === \"string\" ? cfg.bds_path.trim() : \"\";\n const bdsRoot = rawBds ? (path.isAbsolute(rawBds) ? rawBds : path.resolve(env.PROJECT_ROOT, rawBds)) : null;\n return loadContentSnapshot({\n packagesDir: join(env.MODULES_DIR, \"packages\"),\n modules,\n bdsRoot,\n });\n}\n\n/** catalog \u7701\u7565 canDisable \u65F6\u9ED8\u8BA4\u5141\u8BB8\u7981\u7528(\u4E0E buildModuleList.can_disable \u540C\u6E90)\u3002 */\nfunction moduleCanDisable(raw: Record<string, unknown>): boolean {\n return raw.canDisable !== false;\n}\n\nfunction resolveModuleByKey(key: string) {\n const k = String(key || \"\").trim();\n const catalog = loadModuleCatalog();\n const raw = catalog.find(\n (m) =>\n String((m as Record<string, unknown>).id || \"\") === k ||\n String((m as Record<string, unknown>).configKey || (m as Record<string, unknown>).config_key || \"\") === k\n ) as Record<string, unknown> | undefined;\n if (!raw) return null;\n const id = String(raw.id || \"\").trim();\n const configKey = String(raw.configKey || raw.config_key || \"\").trim();\n if (!id || !configKey) return null;\n /* LSP:\u4E0E buildModuleList.can_disable \u540C\u6E90 \u2014 \u7701\u7565\u5B57\u6BB5\u89C6\u4E3A\u53EF\u7981\u7528 */\n return { id, configKey, canDisable: moduleCanDisable(raw) };\n}\n\nfunction setModuleEnabled(mod: { id: string; canDisable: boolean }, enabled: boolean) {\n // \u76F4\u63A5\u66F4\u65B0\u542F\u52A8\u65F6\u7F13\u5B58\u7684 lockFile(\u800C\u975E\u53E6\u8BFB\u4E00\u4EFD\u65B0\u526F\u672C),\n // \u5426\u5219 buildModuleList() \u8BFB\u7684\u4ECD\u662F\u65E7\u7F13\u5B58,\u5BFC\u81F4\u542F\u505C\u540E enabled \u72B6\u6001\u4E0D\u7FFB\u8F6C\u3002\n updateModuleState(lockFile, mod.id, { enabled: !!enabled });\n // DRY:\u4E0E loadModuleLock \u5BF9\u79F0\u8D70 saveModuleLock,\u52FF\u6563\u843D writeJson\n saveModuleLock(env.MODULE_LOCK_PATH, lockFile);\n\n // DIP:\u70ED\u540C\u6B65 enabledSet / tokens / manifests / builtin handlers(\u4E0D\u91CD\u542F db-server)\n syncModuleRuntimeState({\n moduleId: mod.id,\n enabled: !!enabled,\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledSet,\n enabledManifests,\n loadedManifest,\n moduleAuth,\n serviceRegistry,\n builtinDeps: { query, db },\n });\n}\n\n// \u2500\u2500 \u5E73\u53F0\u8DEF\u7531(\u975E\u6A21\u5757\u4E1A\u52A1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst healthRoutes = createHealthRoutes();\nconst statusRoutes = createStatusRoutes({});\nconst qqBindRoutes = createQqBindRoutes({ query, body, json });\n\n/** \u6A21\u5757 qq-link \u7684\u914D\u7F6E\u6587\u4EF6\uFF08\u975E SDK qq_config\uFF09 */\nconst QQ_LINK_CONFIG_KEY = \"qq_link\";\nconst QQ_LINK_DEFAULTS = {\n allowlist_enabled: true,\n require_approval: true,\n /** \u662F\u5426\u5C06 QQ \u7FA4\u4E3B/\u7FA4\u7BA1\u89C6\u4F5C SFMC \u7BA1\u7406\u5458\uFF1B\u4EC5\u6539\u672C\u6587\u4EF6\uFF0CAPI/\u7FA4\u804A\u4E0D\u53EF\u5199 */\n treat_group_admins_as_admins: false,\n} as const;\n\nfunction asConfigBool(v: unknown, fallback: boolean): boolean {\n if (v === undefined || v === null) return fallback;\n if (typeof v === \"boolean\") return v;\n const s = String(v).trim().toLowerCase();\n if (s === \"true\" || s === \"1\" || s === \"yes\" || s === \"on\") return true;\n if (s === \"false\" || s === \"0\" || s === \"no\" || s === \"off\") return false;\n return fallback;\n}\n\nfunction qqLinkConfigFile(): string {\n return join(env.PROJECT_ROOT, \"configs\", `${QQ_LINK_CONFIG_KEY}.json`);\n}\n\nfunction readJoinFlags(): JoinFeatureFlags {\n // \u6A21\u5757\u914D\u7F6E configs/qq_link.json\uFF08\u975E SDK qq_config\uFF09\n const disk = ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n return {\n allowlistEnabled: asConfigBool(disk.allowlist_enabled, QQ_LINK_DEFAULTS.allowlist_enabled),\n requireApproval: asConfigBool(disk.require_approval, QQ_LINK_DEFAULTS.require_approval),\n treatGroupAdminsAsAdmins: asConfigBool(\n disk.treat_group_admins_as_admins,\n QQ_LINK_DEFAULTS.treat_group_admins_as_admins\n ),\n };\n}\n\nfunction writeJoinFlags(partial: Partial<JoinFeatureFlags>): JoinFeatureFlags {\n const file = qqLinkConfigFile();\n ensureJson<Record<string, unknown>>(file, { ...QQ_LINK_DEFAULTS });\n const patch: Record<string, boolean> = {};\n if (partial.allowlistEnabled !== undefined) patch.allowlist_enabled = partial.allowlistEnabled;\n if (partial.requireApproval !== undefined) patch.require_approval = partial.requireApproval;\n // \u6545\u610F\u4E0D\u5199 treat_group_admins_as_admins\uFF1A\u4EC5\u4EBA\u5DE5\u6539 configs/qq_link.json\n if (Object.keys(patch).length > 0) {\n patchJson(file, patch);\n }\n return readJoinFlags();\n}\n\n// \u542F\u52A8\u5373\u843D\u76D8\u6A21\u5757\u9ED8\u8BA4\u914D\u7F6E\uFF0C\u907F\u514D\u300C\u4ECE\u672A\u70B9\u8FC7\u914D\u7F6E/\u7533\u8BF7\u300D\u65F6 configs/qq_link.json \u4E0D\u5B58\u5728\n{\n const created = !readJson(qqLinkConfigFile());\n ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n if (created) {\n log.info(`\u5DF2\u5199\u5165\u6A21\u5757\u914D\u7F6E\u9AA8\u67B6: configs/${QQ_LINK_CONFIG_KEY}.json`);\n }\n}\n\nconst qqJoinRoutes = createQqJoinRoutes({\n query,\n body,\n json,\n getAdminOpenids: () => {\n const raw = (env.qqconfig[\"official\"] as { admin_openids?: unknown } | undefined)?.admin_openids;\n if (!Array.isArray(raw)) return [];\n return raw.map((x) => String(x).trim()).filter(Boolean);\n },\n getJoinFlags: () => readJoinFlags(),\n setJoinFlags: (partial) => writeJoinFlags(partial),\n});\n\nfunction readQqEventsSettings(): ResolvedQqEventsConfig {\n const disk = readJson<Record<string, unknown>>(join(env.PROJECT_ROOT, \"configs\", \"qq_config.json\"));\n if (!disk) throw new Error(\"qq_config.json \u65E0\u6CD5\u8BFB\u53D6\uFF0C\u4E8B\u4EF6\u8BBE\u7F6E\u672A\u4FEE\u6539\");\n return resolveQqEventsConfig(disk.qq_events);\n}\n\nfunction writeQqEventsSettings(patch: Partial<ResolvedQqEventsConfig>): ResolvedQqEventsConfig {\n const file = join(env.PROJECT_ROOT, \"configs\", \"qq_config.json\");\n const settings = { ...readQqEventsSettings(), ...patch };\n patchJson(file, { qq_events: settings });\n return settings;\n}\n\nfunction currentOutbound() {\n return makeOutboundConfig({\n QQ_BACKEND: env.QQ_BACKEND,\n LLBOT_HOST: env.LLBOT_HOST,\n LLBOT_PORT: env.LLBOT_PORT,\n LLBOT_TOKEN: env.LLBOT_TOKEN,\n QQ_GROUP_ID: env.QQ_GROUP_ID,\n QQ_APP_ID: env.QQ_APP_ID,\n QQ_APP_SECRET: env.QQ_APP_SECRET,\n QQ_SANDBOX: env.QQ_SANDBOX,\n QQ_GROUP_OPENID: env.QQ_GROUP_OPENID,\n });\n}\n\nconst qqEventsAggregator = createQqEventsAggregator({\n getConfig: readQqEventsSettings,\n getOutbound: () => currentOutbound(),\n});\nconst qqEventsRoutes = createQqEventsRoutes({\n body,\n json,\n aggregator: qqEventsAggregator,\n getSettings: readQqEventsSettings,\n setSettings: writeQqEventsSettings,\n isAdmin: (openid, asGroupAdmin) => {\n const admins = (env.qqconfig[\"official\"] as { admin_openids?: unknown } | undefined)?.admin_openids;\n return (\n (Array.isArray(admins) && admins.includes(openid)) ||\n (asGroupAdmin && readJoinFlags().treatGroupAdminsAsAdmins)\n );\n },\n});\n\nconst messagesRoutes = createMessagesRoutes({\n query,\n body,\n json,\n getChannelForQQ: (channelId: string) => {\n const rows = query(\n SQL`SELECT * FROM sfmc_chat_channels WHERE id = ${channelId}`\n );\n return Array.isArray(rows)\n ? (rows[0] as { prefix: string; forward_to_qq: number; source_game?: number; source_qq?: number; source_system?: number } | undefined) ?? null\n : null;\n },\n forwardToQQBridge: (channelId: string, prefix: string, fromName: string, content: string, fromId: string) =>\n forwardToQQBridge(currentOutbound(), channelId, prefix, fromName, content, fromId),\n});\nconst configRoutes = createConfigRoutes({\n json,\n projectRoot: env.PROJECT_ROOT,\n // DIP:\u8DEF\u7531\u4E0D\u8BFB lock/catalog/token \u6587\u4EF6,\u7531\u5165\u53E3\u6CE8\u5165\u4E0E /modules \u540C\u6E90\u6570\u636E\n listModules: () => buildModuleList() as Array<Record<string, unknown>>,\n getModuleTokens: () => ({ ...moduleAuth.tokens }),\n});\nconst contentRoutes = createContentRoutes({\n json,\n loadSnapshot: loadContentSnapshotForRequest,\n});\nconst moduleRoutesInstance = createModuleRoutes({\n loadModuleCatalog,\n buildModuleList: buildModuleList as unknown as () => Array<Record<string, unknown>>,\n resolveModuleByKey,\n setModuleEnabled,\n body,\n json,\n});\n\n// \u2500\u2500 v2 \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// \u7C7B\u578B\u65AD\u8A00\u6210 unknown \u51FD\u6570 \u2014 v2 routes \u7684 ctx \u7C7B\u578B\u4E0E RouteCtx \u4E0D\u517C\u5BB9,\n// \u4F46\u8C03\u7528\u5951\u7EA6({path, method, params, req, res, body?})\u662F\u7A33\u5B9A\u7684\u3002\nconst dbRoutes = createDbRoutes({ schemaRegistry, txRunner, idempotent, json }) as unknown as (\n ctx: Record<string, unknown>\n) => Promise<boolean>;\nconst serviceRoutes = createServiceRoutes({\n serviceRegistry,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\nconst moduleConfigRoutes = createModuleConfigRoutes({\n projectRoot: env.PROJECT_ROOT,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\n\n// \u2500\u2500 \u4E3B\u8BF7\u6C42\u5904\u7406\u5668 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const url = new URL(req.url || \"/\", `http://${req.headers.host || \"localhost\"}`);\n const path = url.pathname;\n const method = req.method || \"GET\";\n const params = url.searchParams;\n\n // loopback \u7ED1\u5B9A\n const remote = req.socket.remoteAddress || \"\";\n if (remote && !remote.startsWith(\"127.\") && remote !== \"::1\" && remote !== \"::ffff:127.\") {\n json(res, { success: false, error: \"forbidden\" }, 403);\n return;\n }\n\n // \u2500\u2500 \u9884\u8BFB body(\u6240\u6709\u8DEF\u7531\u5171\u4EAB) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await body(req);\n\n // \u2500\u2500 v2 \u6A21\u5757\u8EAB\u4EFD\u6821\u9A8C \u2192 \u5199\u5165\u8DEF\u7531 ctx.moduleAuth(LoD:\u4E0D\u6302 req \u79C1\u6709\u5B57\u6BB5) \u2500\u2500\n // \u6CE8\u610F:`/api/sfmc/configs/all` \u662F\u65E7\u7684\u4E00\u6B21\u6027\u914D\u7F6E\u5FEB\u7167\u7AEF\u70B9(SAPI ConfigManager.init\n // \u542F\u52A8\u5FC5\u7528),\u4E0D\u5C5E\u4E8E v2 \u6A21\u5757\u914D\u7F6E\u547D\u540D\u7A7A\u95F4(configs/<\u6A21\u5757 configKey>),\u5FC5\u987B\u8C41\u514D,\n // \u5426\u5219\u4F1A\u88AB\u6A21\u5757\u9274\u6743\u7F51\u5173\u62E6\u6210 401,\u5BFC\u81F4\u63D2\u4EF6\u7AEF\u8D77\u4E0D\u6765\u3002\n const isLegacyConfigAll = path === \"/api/sfmc/configs/all\";\n const needsModuleAuth =\n path.startsWith(\"/api/sfmc/db/\") ||\n path.startsWith(\"/api/sfmc/services\") ||\n (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+(?:\\/(?:set|notify))?$/.test(path) && !isLegacyConfigAll);\n let moduleAuthCtx: { id: string; permissions: string[] } | null = null;\n if (needsModuleAuth) {\n const id = verifyModuleAuth({\n headers: req.headers,\n params,\n auth: moduleAuth,\n enabledModuleIds: enabledSet,\n });\n if (!id) {\n // LSP: v2 \u6A21\u5757\u95E8\u4E0E service-routes \u540C\u7528 ok \u65B9\u8A00\n jsonV2Fail(res, \"unauthorized: module identity invalid\", 401, \"unauthorized\");\n return;\n }\n const manifest = enabledManifests.get(id);\n moduleAuthCtx = {\n id,\n permissions: manifest?.permissions ?? [],\n };\n } else {\n // \u2500\u2500 \u65E7 env token \u9274\u6743(\u53EA\u5BF9 POST/PUT \u751F\u6548) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const PUBLIC_GET =\n path === \"/api/health\" ||\n path === \"/api/sfmc/status\" ||\n path === \"/api/sfmc/qq/join/apply-queue\" ||\n path === \"/api/sfmc/qq/join/settings\" ||\n path === \"/api/sfmc/qq/admin/action-queue\" ||\n (method === \"GET\" &&\n (path === \"/api/sfmc/content\" ||\n path === \"/api/sfmc/modules\" ||\n path === \"/api/sfmc/modules/catalog\" ||\n path.startsWith(\"/api/sfmc/modules/\") ||\n path === \"/api/sfmc/qq/bind/me\" ||\n path === \"/api/sfmc/qq/join/pending\"));\n // bind/join/events POST \u4E0E messages \u4E00\u6837\u8D70\u65E7 AUTH_TOKEN\uFF08\u82E5\u914D\u7F6E\uFF09\uFF1B\u6E38\u620F/\u6865\u540C\u4E3A loopback\n const NEEDS_AUTH =\n !PUBLIC_GET &&\n method !== \"GET\" &&\n !(\n path.startsWith(\"/api/sfmc/qq/bind/\") ||\n path.startsWith(\"/api/sfmc/qq/join/\") ||\n path.startsWith(\"/api/sfmc/qq/admin/\") ||\n path.startsWith(\"/api/sfmc/qq/events\")\n );\n if (env.AUTH_TOKEN && NEEDS_AUTH) {\n const auth = req.headers[\"authorization\"] || \"\";\n const provided = auth.startsWith(\"Bearer \") ? auth.slice(7) : (req.headers[\"x-db-token\"] as string) || \"\";\n if (provided !== env.AUTH_TOKEN) {\n json(res, { success: false, error: \"unauthorized\" }, 401);\n return;\n }\n }\n }\n\n try {\n if (path === \"/api/sfmc/player-session\" && method === \"POST\") {\n const data = (await body(req)) as Record<string, unknown>;\n const playerName = String(data.playerName ?? \"\").trim();\n const xuid = String(data.xuid ?? \"\").trim();\n if (!playerName || !xuid) {\n json(res, { success: false, error: \"playerName_and_xuid_required\" }, 400);\n return;\n }\n syncPlayerSession(query, { playerName, xuid });\n json(res, { success: true });\n return;\n }\n // \u2500\u2500 v2 \u8DEF\u7531(\u4F18\u5148\u5339\u914D) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (\n moduleAuthCtx &&\n (path.startsWith(\"/api/sfmc/db/\") ||\n path.startsWith(\"/api/sfmc/services\") ||\n (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+/.test(path) && !isLegacyConfigAll))\n ) {\n const ctx: Record<string, unknown> = {\n path,\n method,\n params,\n req,\n res,\n moduleAuth: moduleAuthCtx,\n };\n // body \u5DF2\u5728\u4E0A\u9762 `await body(req)` \u9884\u8BFB\u5E76\u7F13\u5B58\u5230 req._bodyPromise;\n // \u8FD9\u91CC\u590D\u7528\u7F13\u5B58(\u539F\u5B9E\u73B0\u8BFB\u7684 req._body \u4ECE\u672A\u88AB\u8D4B\u503C,\u5BFC\u81F4\u6240\u6709 v2 \u8DEF\u7531 body \u6052\u4E3A\u7A7A)\u3002\n ctx[\"body\"] = await body(req);\n if (path.startsWith(\"/api/sfmc/db/\")) {\n if (await dbRoutes(ctx)) return;\n } else if (path.startsWith(\"/api/sfmc/services\")) {\n if (await serviceRoutes(ctx)) return;\n } else {\n if (await moduleConfigRoutes(ctx)) return;\n }\n }\n\n // \u2500\u2500 \u5E73\u53F0\u8DEF\u7531 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const ctxBase = { path, method, params, req, res } as {\n path: string;\n method: string;\n params: URLSearchParams;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n };\n if (await contentRoutes(ctxBase)) return;\n if (await moduleRoutesInstance(ctxBase)) return;\n if (await healthRoutes(ctxBase)) return;\n if (await statusRoutes(ctxBase)) return;\n if (await qqBindRoutes(ctxBase)) return;\n if (await qqJoinRoutes(ctxBase)) return;\n if (await qqEventsRoutes(ctxBase)) return;\n if (await messagesRoutes(ctxBase)) return;\n if (await configRoutes(ctxBase)) return;\n\n json(res, { success: false, error: \"not_found\" }, 404);\n } catch (err) {\n log.err(err, \"DogeDB\");\n json(res, { success: false, error: (err as Error).message }, 500);\n }\n}\n\n// \u2500\u2500 \u542F\u52A8 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst server = createServer({\n env: { PORT: env.PORT, HOST: env.HOST, AUTH_TOKEN: env.AUTH_TOKEN },\n handle,\n});\nstartConsole(server, db);\n\nexport { db, enabledManifests, env, moduleAuth, query, schemaRegistry, serviceRegistry, txRunner };\n"],
5
+ "mappings": "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",
6
+ "names": ["path"]
7
7
  }
@@ -4,7 +4,7 @@
4
4
  * 路由列表:
5
5
  * GET /api/sfmc/configs/all — SAPI ConfigManager 启动快照(仅平台域)
6
6
  * GET /api/sfmc/settings — 平铺 settings.json(legacy)
7
- * GET /api/sfmc/settings/:key — 含 land:* / bridge_channel_id 等 fallback
7
+ * GET /api/sfmc/settings/:key — 含 land:* 等 fallback
8
8
  * GET /api/sfmc/areas|permissions|… — legacy 只读 JSON(文件名对 db-server 为黑箱)
9
9
  *
10
10
  * DIP:模块/legacy JSON 不进 SDK ConfigName;本路由用 configDir + 文件名拼接路径。
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/routes/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,UAAU,IAAI;IACZ,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChD;AAmCD,iBAAS,kBAAkB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,EAAE,IAAI,iDAyGhF;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAuErB;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/routes/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,UAAU,IAAI;IACZ,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChD;AAmCD,iBAAS,kBAAkB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,EAAE,IAAI,iDAqGhF;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAuErB;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -50,10 +50,6 @@ function createConfigRoutes({ json, projectRoot, listModules, getModuleTokens })
50
50
  if (Object.prototype.hasOwnProperty.call(settings, key) && !isMetaKey(key)) {
51
51
  return { value: settings[key], source: "settings.json" };
52
52
  }
53
- if (key === "bridge_channel_id") {
54
- const qq = readPlatform("qq_config.json") ?? {};
55
- if (qq.bridge_channel_id) return { value: qq.bridge_channel_id, source: "qq_config.json" };
56
- }
57
53
  if (key.startsWith("land:")) {
58
54
  const land = readOpaque("land.json") ?? {};
59
55
  if (Object.prototype.hasOwnProperty.call(land, key) && !isMetaKey(key)) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/routes/config.ts"],
4
- "sourcesContent": ["/**\n * routes/config.ts \u2014 \u914D\u7F6E\u8DEF\u7531\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/configs/all \u2014 SAPI ConfigManager \u542F\u52A8\u5FEB\u7167\uFF08\u4EC5\u5E73\u53F0\u57DF\uFF09\n * GET /api/sfmc/settings \u2014 \u5E73\u94FA settings.json\uFF08legacy\uFF09\n * GET /api/sfmc/settings/:key \u2014 \u542B land:* / bridge_channel_id \u7B49 fallback\n * GET /api/sfmc/areas|permissions|\u2026 \u2014 legacy \u53EA\u8BFB JSON\uFF08\u6587\u4EF6\u540D\u5BF9 db-server \u4E3A\u9ED1\u7BB1\uFF09\n *\n * DIP\uFF1A\u6A21\u5757/legacy JSON \u4E0D\u8FDB SDK ConfigName\uFF1B\u672C\u8DEF\u7531\u7528 configDir + \u6587\u4EF6\u540D\u62FC\u63A5\u8DEF\u5F84\u3002\n * \u6A21\u5757\u79C1\u6709\u914D\u7F6E\u7684\u8BFB\u5199\u6743\u5A01\u5165\u53E3\u662F module-config-routes\uFF08configs/:configKey\uFF09\u3002\n */\n\nimport { join } from \"node:path\";\nimport { configDir, configPath, readJson, type ConfigName } from \"@sfmc-bds/sdk/node/config\";\n\ninterface Deps {\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n projectRoot: string;\n /**\n * \u6CE8\u5165\u6A21\u5757\u5217\u8868(\u4E0E GET /api/sfmc/modules \u540C\u6E90 \u2014 DRY)\u3002\n * \u52FF\u5728\u672C\u8DEF\u7531\u518D\u8BFB catalog/lock(DIP:\u9AD8\u5C42\u88C5\u914D,\u8DEF\u7531\u53EA\u6D88\u8D39\u62BD\u8C61)\u3002\n */\n listModules?: () => Array<Record<string, unknown>>;\n /**\n * \u6CE8\u5165\u6A21\u5757 HMAC token \u8868(\u4EC5 loopback \u53EF\u8FBE;\u4F9B SAPI host-bootstrap \u6CE8\u5165\n * set*ModuleContext,\u56E0 SAPI \u4E0D\u80FD\u76F4\u63A5\u8BFB\u4E0E DB \u540C\u76EE\u5F55\u7684 module-tokens.json)\u3002\n */\n getModuleTokens?: () => Record<string, string>;\n}\n\nfunction isMetaKey(k: string): boolean {\n return k === \"$schema\" || String(k).startsWith(\"_\");\n}\n\nfunction stripMetaDeep(v: unknown): unknown {\n if (Array.isArray(v)) return v.map(stripMetaDeep);\n if (v && typeof v === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n if (!isMetaKey(k)) out[k] = stripMetaDeep(val);\n }\n return out;\n }\n return v;\n}\n\nfunction stripMeta(obj: Record<string, unknown> | null): Record<string, unknown> {\n if (!obj || typeof obj !== \"object\" || Array.isArray(obj)) return {};\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (!isMetaKey(k)) out[k] = v;\n }\n return out;\n}\n\nfunction jsonValue(v: unknown): string {\n return v !== null && typeof v === \"object\" ? JSON.stringify(v) : String(v ?? \"\");\n}\n\nfunction arrayOrEmpty(v: unknown): unknown[] {\n return Array.isArray(v) ? v : [];\n}\n\nfunction createConfigRoutes({ json, projectRoot, listModules, getModuleTokens }: Deps) {\n /** \u5E73\u53F0 ConfigName\uFF08qq_config \u7B49\uFF09 */\n const readPlatform = (name: ConfigName): unknown => readJson(configPath(projectRoot, name));\n /** \u9ED1\u7BB1 JSON\uFF1A\u4EFB\u610F configs/<file>\uFF0C\u4E0D\u8FDB ConfigName */\n const readOpaque = (fileName: string): unknown => readJson(join(configDir(projectRoot), fileName));\n\n /**\n * SAPI ConfigManager \u5951\u7EA6\uFF1A\u53EA\u7F13\u5B58 modules / settings / permissions\u3002\n * \u52FF\u518D\u5F80 all \u585E areas \u7B49 legacy \u57DF\uFF08\u90A3\u4E9B\u8D70\u4E13\u7528 GET \u6216\u6A21\u5757 configKey\uFF09\u3002\n */\n function getAllConfigs(): Record<string, unknown> {\n const modules = typeof listModules === \"function\" ? listModules() : [];\n const module_tokens = typeof getModuleTokens === \"function\" ? getModuleTokens() : {};\n return {\n modules,\n module_tokens,\n settings: stripMeta(readOpaque(\"settings.json\") as Record<string, unknown> | null),\n permissions: getPermissions(),\n };\n }\n\n function getSettingsFlat(): Array<{ key: string; value: string }> {\n const obj = stripMeta(readOpaque(\"settings.json\") as Record<string, unknown> | null);\n return Object.entries(obj).map(([key, value]) => ({ key, value: jsonValue(value) }));\n }\n\n function getSettingByKey(key: string): { value: unknown; source?: string } {\n const settings = (readOpaque(\"settings.json\") as Record<string, unknown> | null) ?? {};\n if (Object.prototype.hasOwnProperty.call(settings, key) && !isMetaKey(key)) {\n return { value: settings[key], source: \"settings.json\" };\n }\n if (key === \"bridge_channel_id\") {\n const qq = (readPlatform(\"qq_config.json\") as Record<string, unknown> | null) ?? {};\n if (qq.bridge_channel_id) return { value: qq.bridge_channel_id, source: \"qq_config.json\" };\n }\n if (key.startsWith(\"land:\")) {\n const land = (readOpaque(\"land.json\") as Record<string, unknown> | null) ?? {};\n if (Object.prototype.hasOwnProperty.call(land, key) && !isMetaKey(key)) {\n return { value: land[key], source: \"land.json\" };\n }\n }\n return { value: null };\n }\n\n function getAreas(): unknown[] {\n return (arrayOrEmpty(readOpaque(\"areas.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.module && r.dimension != null)\n .map((r) => stripMetaDeep(r));\n }\n\n function getPermissions(): unknown[] {\n return (arrayOrEmpty(readPlatform(\"permissions.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.player_name)\n .map((r) => stripMetaDeep(r));\n }\n\n function getBannedItems(): string[] {\n return (arrayOrEmpty(readOpaque(\"banned_items.json\")) as Array<string>).filter(\n (i) => typeof i === \"string\" && i && !i.startsWith(\"_\")\n );\n }\n\n function getClean(): { item_max: number; poll_interval: number } {\n const c = stripMetaDeep(readOpaque(\"clean.json\") ?? {}) as Record<string, unknown>;\n return { item_max: (c.item_max as number) ?? 192, poll_interval: (c.poll_interval as number) ?? 60 };\n }\n\n function getGrids(): unknown[] {\n return (arrayOrEmpty(readOpaque(\"grids.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.name)\n .map((r) => stripMetaDeep(r));\n }\n\n function getPeaceFilters(): unknown[] {\n return (arrayOrEmpty(readOpaque(\"peace_filters.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.family)\n .map((r) => stripMetaDeep(r));\n }\n\n function getQA(): Array<Record<string, unknown>> {\n return (arrayOrEmpty(readOpaque(\"questions.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.question)\n .map((r, idx) => {\n const clean = stripMetaDeep(r) as Record<string, unknown>;\n return {\n id: idx + 1,\n weight: clean.weight ?? 1,\n question: clean.question,\n answers: clean.answers ?? [],\n msg_right: clean.msg_right ?? \"\",\n msg_wrong: clean.msg_wrong ?? \"\",\n explanation: clean.explanation ?? \"\",\n min_rank: clean.min_rank ?? null,\n max_rank: clean.max_rank ?? null,\n rewards: clean.rewards ?? [],\n punishments: clean.punishments ?? [],\n };\n });\n }\n\n return async function handleConfigRoute({\n path: requestPath,\n method,\n params,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n void params;\n if (requestPath === \"/api/sfmc/configs/all\") {\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n json(res, getAllConfigs());\n return true;\n }\n if (requestPath === \"/api/sfmc/settings\") {\n if (method === \"GET\") {\n json(res, { settings: getSettingsFlat() });\n return true;\n }\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (requestPath.startsWith(\"/api/sfmc/settings/\")) {\n const key = decodeURIComponent(requestPath.slice(\"/api/sfmc/settings/\".length));\n if (method === \"GET\") {\n json(res, getSettingByKey(key));\n return true;\n }\n json(res, { success: false, error: \"method_not_allowed\" }, 405);\n return true;\n }\n if (requestPath === \"/api/sfmc/areas\") {\n if (method === \"GET\") {\n json(res, { areas: getAreas() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/permissions\") {\n if (method === \"GET\") {\n json(res, { permissions: getPermissions() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/banned_items\") {\n if (method === \"GET\") {\n json(res, { items: getBannedItems() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/clean\") {\n if (method === \"GET\") {\n json(res, { clean: getClean() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/grids\") {\n if (method === \"GET\") {\n json(res, { grids: getGrids() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/peace_filters\") {\n if (method === \"GET\") {\n json(res, { filters: getPeaceFilters() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/qa\") {\n if (method === \"GET\") {\n json(res, { questions: getQA() });\n return true;\n }\n }\n return false;\n };\n}\n\nexport { createConfigRoutes };\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * routes/config.ts \u2014 \u914D\u7F6E\u8DEF\u7531\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/configs/all \u2014 SAPI ConfigManager \u542F\u52A8\u5FEB\u7167\uFF08\u4EC5\u5E73\u53F0\u57DF\uFF09\n * GET /api/sfmc/settings \u2014 \u5E73\u94FA settings.json\uFF08legacy\uFF09\n * GET /api/sfmc/settings/:key \u2014 \u542B land:* \u7B49 fallback\n * GET /api/sfmc/areas|permissions|\u2026 \u2014 legacy \u53EA\u8BFB JSON\uFF08\u6587\u4EF6\u540D\u5BF9 db-server \u4E3A\u9ED1\u7BB1\uFF09\n *\n * DIP\uFF1A\u6A21\u5757/legacy JSON \u4E0D\u8FDB SDK ConfigName\uFF1B\u672C\u8DEF\u7531\u7528 configDir + \u6587\u4EF6\u540D\u62FC\u63A5\u8DEF\u5F84\u3002\n * \u6A21\u5757\u79C1\u6709\u914D\u7F6E\u7684\u8BFB\u5199\u6743\u5A01\u5165\u53E3\u662F module-config-routes\uFF08configs/:configKey\uFF09\u3002\n */\n\nimport { join } from \"node:path\";\nimport { configDir, configPath, readJson, type ConfigName } from \"@sfmc-bds/sdk/node/config\";\n\ninterface Deps {\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n projectRoot: string;\n /**\n * \u6CE8\u5165\u6A21\u5757\u5217\u8868(\u4E0E GET /api/sfmc/modules \u540C\u6E90 \u2014 DRY)\u3002\n * \u52FF\u5728\u672C\u8DEF\u7531\u518D\u8BFB catalog/lock(DIP:\u9AD8\u5C42\u88C5\u914D,\u8DEF\u7531\u53EA\u6D88\u8D39\u62BD\u8C61)\u3002\n */\n listModules?: () => Array<Record<string, unknown>>;\n /**\n * \u6CE8\u5165\u6A21\u5757 HMAC token \u8868(\u4EC5 loopback \u53EF\u8FBE;\u4F9B SAPI host-bootstrap \u6CE8\u5165\n * set*ModuleContext,\u56E0 SAPI \u4E0D\u80FD\u76F4\u63A5\u8BFB\u4E0E DB \u540C\u76EE\u5F55\u7684 module-tokens.json)\u3002\n */\n getModuleTokens?: () => Record<string, string>;\n}\n\nfunction isMetaKey(k: string): boolean {\n return k === \"$schema\" || String(k).startsWith(\"_\");\n}\n\nfunction stripMetaDeep(v: unknown): unknown {\n if (Array.isArray(v)) return v.map(stripMetaDeep);\n if (v && typeof v === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n if (!isMetaKey(k)) out[k] = stripMetaDeep(val);\n }\n return out;\n }\n return v;\n}\n\nfunction stripMeta(obj: Record<string, unknown> | null): Record<string, unknown> {\n if (!obj || typeof obj !== \"object\" || Array.isArray(obj)) return {};\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (!isMetaKey(k)) out[k] = v;\n }\n return out;\n}\n\nfunction jsonValue(v: unknown): string {\n return v !== null && typeof v === \"object\" ? JSON.stringify(v) : String(v ?? \"\");\n}\n\nfunction arrayOrEmpty(v: unknown): unknown[] {\n return Array.isArray(v) ? v : [];\n}\n\nfunction createConfigRoutes({ json, projectRoot, listModules, getModuleTokens }: Deps) {\n /** \u5E73\u53F0 ConfigName\uFF08qq_config \u7B49\uFF09 */\n const readPlatform = (name: ConfigName): unknown => readJson(configPath(projectRoot, name));\n /** \u9ED1\u7BB1 JSON\uFF1A\u4EFB\u610F configs/<file>\uFF0C\u4E0D\u8FDB ConfigName */\n const readOpaque = (fileName: string): unknown => readJson(join(configDir(projectRoot), fileName));\n\n /**\n * SAPI ConfigManager \u5951\u7EA6\uFF1A\u53EA\u7F13\u5B58 modules / settings / permissions\u3002\n * \u52FF\u518D\u5F80 all \u585E areas \u7B49 legacy \u57DF\uFF08\u90A3\u4E9B\u8D70\u4E13\u7528 GET \u6216\u6A21\u5757 configKey\uFF09\u3002\n */\n function getAllConfigs(): Record<string, unknown> {\n const modules = typeof listModules === \"function\" ? listModules() : [];\n const module_tokens = typeof getModuleTokens === \"function\" ? getModuleTokens() : {};\n return {\n modules,\n module_tokens,\n settings: stripMeta(readOpaque(\"settings.json\") as Record<string, unknown> | null),\n permissions: getPermissions(),\n };\n }\n\n function getSettingsFlat(): Array<{ key: string; value: string }> {\n const obj = stripMeta(readOpaque(\"settings.json\") as Record<string, unknown> | null);\n return Object.entries(obj).map(([key, value]) => ({ key, value: jsonValue(value) }));\n }\n\n function getSettingByKey(key: string): { value: unknown; source?: string } {\n const settings = (readOpaque(\"settings.json\") as Record<string, unknown> | null) ?? {};\n if (Object.prototype.hasOwnProperty.call(settings, key) && !isMetaKey(key)) {\n return { value: settings[key], source: \"settings.json\" };\n }\n if (key.startsWith(\"land:\")) {\n const land = (readOpaque(\"land.json\") as Record<string, unknown> | null) ?? {};\n if (Object.prototype.hasOwnProperty.call(land, key) && !isMetaKey(key)) {\n return { value: land[key], source: \"land.json\" };\n }\n }\n return { value: null };\n }\n\n function getAreas(): unknown[] {\n return (arrayOrEmpty(readOpaque(\"areas.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.module && r.dimension != null)\n .map((r) => stripMetaDeep(r));\n }\n\n function getPermissions(): unknown[] {\n return (arrayOrEmpty(readPlatform(\"permissions.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.player_name)\n .map((r) => stripMetaDeep(r));\n }\n\n function getBannedItems(): string[] {\n return (arrayOrEmpty(readOpaque(\"banned_items.json\")) as Array<string>).filter(\n (i) => typeof i === \"string\" && i && !i.startsWith(\"_\")\n );\n }\n\n function getClean(): { item_max: number; poll_interval: number } {\n const c = stripMetaDeep(readOpaque(\"clean.json\") ?? {}) as Record<string, unknown>;\n return { item_max: (c.item_max as number) ?? 192, poll_interval: (c.poll_interval as number) ?? 60 };\n }\n\n function getGrids(): unknown[] {\n return (arrayOrEmpty(readOpaque(\"grids.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.name)\n .map((r) => stripMetaDeep(r));\n }\n\n function getPeaceFilters(): unknown[] {\n return (arrayOrEmpty(readOpaque(\"peace_filters.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.family)\n .map((r) => stripMetaDeep(r));\n }\n\n function getQA(): Array<Record<string, unknown>> {\n return (arrayOrEmpty(readOpaque(\"questions.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.question)\n .map((r, idx) => {\n const clean = stripMetaDeep(r) as Record<string, unknown>;\n return {\n id: idx + 1,\n weight: clean.weight ?? 1,\n question: clean.question,\n answers: clean.answers ?? [],\n msg_right: clean.msg_right ?? \"\",\n msg_wrong: clean.msg_wrong ?? \"\",\n explanation: clean.explanation ?? \"\",\n min_rank: clean.min_rank ?? null,\n max_rank: clean.max_rank ?? null,\n rewards: clean.rewards ?? [],\n punishments: clean.punishments ?? [],\n };\n });\n }\n\n return async function handleConfigRoute({\n path: requestPath,\n method,\n params,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n void params;\n if (requestPath === \"/api/sfmc/configs/all\") {\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n json(res, getAllConfigs());\n return true;\n }\n if (requestPath === \"/api/sfmc/settings\") {\n if (method === \"GET\") {\n json(res, { settings: getSettingsFlat() });\n return true;\n }\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (requestPath.startsWith(\"/api/sfmc/settings/\")) {\n const key = decodeURIComponent(requestPath.slice(\"/api/sfmc/settings/\".length));\n if (method === \"GET\") {\n json(res, getSettingByKey(key));\n return true;\n }\n json(res, { success: false, error: \"method_not_allowed\" }, 405);\n return true;\n }\n if (requestPath === \"/api/sfmc/areas\") {\n if (method === \"GET\") {\n json(res, { areas: getAreas() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/permissions\") {\n if (method === \"GET\") {\n json(res, { permissions: getPermissions() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/banned_items\") {\n if (method === \"GET\") {\n json(res, { items: getBannedItems() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/clean\") {\n if (method === \"GET\") {\n json(res, { clean: getClean() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/grids\") {\n if (method === \"GET\") {\n json(res, { grids: getGrids() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/peace_filters\") {\n if (method === \"GET\") {\n json(res, { filters: getPeaceFilters() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/qa\") {\n if (method === \"GET\") {\n json(res, { questions: getQA() });\n return true;\n }\n }\n return false;\n };\n}\n\nexport { createConfigRoutes };\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * routes/content.ts — QQ 查询已装模块与世界资源包。
3
+ *
4
+ * GET /api/sfmc/content
5
+ *
6
+ * 只读。模块启停与资源包文件仍由 catalog / 世界目录维护,本路由不改它们。
7
+ */
8
+ import type { ContentSnapshot } from "../domain/installed-content.js";
9
+ interface Deps {
10
+ json: (res: import("http").ServerResponse, data: Record<string, unknown>, status?: number) => void;
11
+ /** 每次请求现读,避免 db-server 启动后装包却一直返回旧列表。 */
12
+ loadSnapshot: () => ContentSnapshot;
13
+ }
14
+ /** 创建内容查询路由。当前只给 qq-bridge 的「模块」「资源包」命令使用。 */
15
+ declare function createContentRoutes({ json, loadSnapshot }: Deps): ({ path, method, res, }: {
16
+ path: string;
17
+ method: string;
18
+ params: URLSearchParams;
19
+ req: import("http").IncomingMessage;
20
+ res: import("http").ServerResponse;
21
+ }) => Promise<boolean>;
22
+ export { createContentRoutes };
23
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/routes/content.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAEtE,UAAU,IAAI;IACZ,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,yCAAyC;IACzC,YAAY,EAAE,MAAM,eAAe,CAAC;CACrC;AAED,8CAA8C;AAC9C,iBAAS,mBAAmB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,IAAI,4BAKpD;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CASrB;AAED,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
@@ -0,0 +1,19 @@
1
+ function createContentRoutes({ json, loadSnapshot }) {
2
+ return async function handle({
3
+ path,
4
+ method,
5
+ res
6
+ }) {
7
+ if (path !== "/api/sfmc/content") return false;
8
+ if (method !== "GET") {
9
+ json(res, { success: false, error: "not_found" }, 404);
10
+ return true;
11
+ }
12
+ json(res, loadSnapshot());
13
+ return true;
14
+ };
15
+ }
16
+ export {
17
+ createContentRoutes
18
+ };
19
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/routes/content.ts"],
4
+ "sourcesContent": ["/**\n * routes/content.ts \u2014 QQ \u67E5\u8BE2\u5DF2\u88C5\u6A21\u5757\u4E0E\u4E16\u754C\u8D44\u6E90\u5305\u3002\n *\n * GET /api/sfmc/content\n *\n * \u53EA\u8BFB\u3002\u6A21\u5757\u542F\u505C\u4E0E\u8D44\u6E90\u5305\u6587\u4EF6\u4ECD\u7531 catalog / \u4E16\u754C\u76EE\u5F55\u7EF4\u62A4\uFF0C\u672C\u8DEF\u7531\u4E0D\u6539\u5B83\u4EEC\u3002\n */\n\nimport type { ContentSnapshot } from \"../domain/installed-content.js\";\n\ninterface Deps {\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n /** \u6BCF\u6B21\u8BF7\u6C42\u73B0\u8BFB\uFF0C\u907F\u514D db-server \u542F\u52A8\u540E\u88C5\u5305\u5374\u4E00\u76F4\u8FD4\u56DE\u65E7\u5217\u8868\u3002 */\n loadSnapshot: () => ContentSnapshot;\n}\n\n/** \u521B\u5EFA\u5185\u5BB9\u67E5\u8BE2\u8DEF\u7531\u3002\u5F53\u524D\u53EA\u7ED9 qq-bridge \u7684\u300C\u6A21\u5757\u300D\u300C\u8D44\u6E90\u5305\u300D\u547D\u4EE4\u4F7F\u7528\u3002 */\nfunction createContentRoutes({ json, loadSnapshot }: Deps) {\n return async function handle({\n path,\n method,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n if (path !== \"/api/sfmc/content\") return false;\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n json(res, loadSnapshot() as unknown as Record<string, unknown>);\n return true;\n };\n}\n\nexport { createContentRoutes };\n"],
5
+ "mappings": "AAiBA,SAAS,oBAAoB,EAAE,MAAM,aAAa,GAAS;AACzD,SAAO,eAAe,OAAO;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAMqB;AACnB,QAAI,SAAS,oBAAqB,QAAO;AACzC,QAAI,WAAW,OAAO;AACpB,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,aAAO;AAAA,IACT;AACA,SAAK,KAAK,aAAa,CAAuC;AAC9D,WAAO;AAAA,EACT;AACF;",
6
+ "names": []
7
+ }
@@ -3,20 +3,28 @@
3
3
  *
4
4
  * 路由列表:
5
5
  * GET /api/sfmc/messages — 模糊/过滤查询消息
6
- * POST /api/sfmc/messages — 批量 INSERT OR REPLACE 消息(匹配 bridge 频道时转发至 QQ)
6
+ * POST /api/sfmc/messages — 批量 INSERT OR REPLACE 消息(按频道开关转发至 QQ)
7
7
  */
8
8
  import type { QueryFn } from "../lib/sqlite.js";
9
9
  interface Deps {
10
10
  query: QueryFn;
11
11
  body: (req: import("http").IncomingMessage) => Promise<Record<string, unknown>>;
12
12
  json: (res: import("http").ServerResponse, data: Record<string, unknown>, status?: number) => void;
13
- forwardToQQBridge: (channelId: string, fromName: string, content: string, fromId: string) => void;
14
- /** 当前桥接频道;空则不做 MC→QQ 聊天出站(事件通道不受影响) */
15
- getBridgeChannelId: () => string;
13
+ forwardToQQBridge: (channelId: string, prefix: string, fromName: string, content: string, fromId: string) => void;
14
+ /** 查询来源频道的前缀和 QQ 转发开关。 */
15
+ getChannelForQQ: (channelId: string) => {
16
+ prefix: string;
17
+ forward_to_qq: number;
18
+ source_game?: number;
19
+ source_qq?: number;
20
+ source_system?: number;
21
+ } | null;
16
22
  }
17
23
  /** 是否应将本条消息推到 QQ 群 */
18
- export declare function shouldForwardChatToQQ(bridgeChannelId: string, channelId: string, fromId: string): boolean;
19
- declare function createMessagesRoutes({ query, body, json, forwardToQQBridge, getBridgeChannelId }: Deps): ({ path, method, params, req, res, }: {
24
+ export declare function shouldForwardChatToQQ(channel: {
25
+ forward_to_qq: number;
26
+ } | null, fromId: string): boolean;
27
+ declare function createMessagesRoutes({ query, body, json, forwardToQQBridge, getChannelForQQ }: Deps): ({ path, method, params, req, res, }: {
20
28
  path: string;
21
29
  method: string;
22
30
  params: URLSearchParams;
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/routes/messages.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,UAAU,IAAI;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAClG,uCAAuC;IACvC,kBAAkB,EAAE,MAAM,MAAM,CAAC;CAClC;AAED,sBAAsB;AACtB,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,MAAM,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAUT;AAED,iBAAS,oBAAoB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,EAAE,IAAI,yCAO3F;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAyDrB;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/routes/messages.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,UAAU,IAAI;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAClH,0BAA0B;IAC1B,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK;QACtC,MAAM,EAAE,MAAM,CAAC;QACf,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,GAAG,IAAI,CAAC;CACV;AAED,sBAAsB;AACtB,wBAAgB,qBAAqB,CACnC,OAAO,EAAE;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EACzC,MAAM,EAAE,MAAM,GACb,OAAO,CAQT;AAED,iBAAS,oBAAoB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,iBAAiB,EAAE,eAAe,EAAE,EAAE,IAAI,yCAOxF;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CA8ErB;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
@@ -1,14 +1,12 @@
1
1
  import { SQL } from "sql-template-strings";
2
- function shouldForwardChatToQQ(bridgeChannelId, channelId, fromId) {
3
- const bridge = String(bridgeChannelId ?? "").trim();
4
- if (!bridge) return false;
5
- if (String(channelId ?? "").trim() !== bridge) return false;
2
+ function shouldForwardChatToQQ(channel, fromId) {
3
+ if (!channel || channel.forward_to_qq !== 1) return false;
6
4
  const fid = String(fromId ?? "").trim();
7
5
  if (!fid) return false;
8
6
  if (fid.startsWith("qq_")) return false;
9
7
  return true;
10
8
  }
11
- function createMessagesRoutes({ query, body, json, forwardToQQBridge, getBridgeChannelId }) {
9
+ function createMessagesRoutes({ query, body, json, forwardToQQBridge, getChannelForQQ }) {
12
10
  return async function handle({
13
11
  path,
14
12
  method,
@@ -45,6 +43,15 @@ function createMessagesRoutes({ query, body, json, forwardToQQBridge, getBridgeC
45
43
  json(res, { success: false, error: "too many requests" }, 413);
46
44
  return true;
47
45
  }
46
+ for (const m of messages) {
47
+ const channel = getChannelForQQ(String(m.channelId ?? ""));
48
+ const fromId = String(m.fromid ?? "");
49
+ const sourceField = fromId.startsWith("qq_") ? "source_qq" : fromId === "system" || fromId === "SYSTEM" ? "source_system" : "source_game";
50
+ if (channel?.[sourceField] === 0) {
51
+ json(res, { success: false, error: "channel source disabled" }, 403);
52
+ return true;
53
+ }
54
+ }
48
55
  for (const m of messages) {
49
56
  query(
50
57
  SQL`INSERT OR REPLACE INTO sfmc_chat_messages (
@@ -56,12 +63,19 @@ function createMessagesRoutes({ query, body, json, forwardToQQBridge, getBridgeC
56
63
  )`
57
64
  );
58
65
  }
59
- const bridgeId = getBridgeChannelId();
60
66
  for (const m of messages) {
61
67
  const channelId = String(m.channelId ?? "");
62
68
  const fromId = String(m.fromid ?? "");
63
- if (!shouldForwardChatToQQ(bridgeId, channelId, fromId)) continue;
64
- forwardToQQBridge(channelId, String(m.fromName ?? ""), String(m.content ?? ""), fromId);
69
+ if (!fromId.trim() || fromId.startsWith("qq_")) continue;
70
+ const channel = getChannelForQQ(channelId);
71
+ if (!shouldForwardChatToQQ(channel, fromId)) continue;
72
+ forwardToQQBridge(
73
+ channelId,
74
+ channel.prefix || channelId,
75
+ String(m.fromName ?? ""),
76
+ String(m.content ?? ""),
77
+ fromId
78
+ );
65
79
  }
66
80
  json(res, { success: true });
67
81
  } else {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/routes/messages.ts"],
4
- "sourcesContent": ["/**\n * routes/messages.ts \u2014 \u804A\u5929\u6D88\u606F (sfmc_chat_messages)\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/messages \u2014 \u6A21\u7CCA/\u8FC7\u6EE4\u67E5\u8BE2\u6D88\u606F\n * POST /api/sfmc/messages \u2014 \u6279\u91CF INSERT OR REPLACE \u6D88\u606F\uFF08\u5339\u914D bridge \u9891\u9053\u65F6\u8F6C\u53D1\u81F3 QQ\uFF09\n */\n\nimport { SQL } from \"sql-template-strings\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\n\ninterface Deps {\n query: QueryFn;\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n forwardToQQBridge: (channelId: string, fromName: string, content: string, fromId: string) => void;\n /** \u5F53\u524D\u6865\u63A5\u9891\u9053\uFF1B\u7A7A\u5219\u4E0D\u505A MC\u2192QQ \u804A\u5929\u51FA\u7AD9\uFF08\u4E8B\u4EF6\u901A\u9053\u4E0D\u53D7\u5F71\u54CD\uFF09 */\n getBridgeChannelId: () => string;\n}\n\n/** \u662F\u5426\u5E94\u5C06\u672C\u6761\u6D88\u606F\u63A8\u5230 QQ \u7FA4 */\nexport function shouldForwardChatToQQ(\n bridgeChannelId: string,\n channelId: string,\n fromId: string\n): boolean {\n const bridge = String(bridgeChannelId ?? \"\").trim();\n if (!bridge) return false;\n if (String(channelId ?? \"\").trim() !== bridge) return false;\n const fid = String(fromId ?? \"\").trim();\n // \u65E0\u53D1\u9001\u8005 id \u4E0D\u51FA\u7AD9\uFF08\u907F\u514D\u89E3\u6790\u5931\u8D25\u65F6\u8BEF\u63A8\uFF09\n if (!fid) return false;\n // QQ\u2192MC \u5199\u5165\u7684 fromid \u5F62\u5982 qq_*\uFF0C\u7981\u6B62\u56DE\u73AF\u63A8\u7FA4\n if (fid.startsWith(\"qq_\")) return false;\n return true;\n}\n\nfunction createMessagesRoutes({ query, body, json, forwardToQQBridge, getBridgeChannelId }: Deps) {\n return async function handle({\n path,\n method,\n params,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n if (path === \"/api/sfmc/messages\") {\n if (method === \"GET\") {\n const stmt = SQL`SELECT * FROM sfmc_chat_messages WHERE 1=1`;\n const search = params.get(\"search\")?.trim();\n if (search) stmt.append(SQL` AND (content LIKE ${`%${search}%`})`);\n const type = params.get(\"type\")?.trim();\n if (type) stmt.append(SQL` AND type = ${type}`);\n const channelId = params.get(\"channelId\")?.trim();\n if (channelId) stmt.append(SQL` AND channel_id = ${channelId}`);\n const from = params.get(\"from\")?.trim();\n if (from) stmt.append(SQL` AND from_id = ${from}`);\n const minCreatedAt = params.get(\"minCreatedAt\")?.trim();\n if (minCreatedAt) stmt.append(SQL` AND created_at >= ${Number(minCreatedAt)}`);\n const minSentAt = params.get(\"minSentAt\")?.trim();\n if (minSentAt) stmt.append(SQL` AND created_at >= ${Number(minSentAt)}`);\n const maxCreatedAt = params.get(\"maxCreatedAt\")?.trim();\n if (maxCreatedAt) stmt.append(SQL` AND created_at <= ${Number(maxCreatedAt)}`);\n stmt.append(SQL` ORDER BY created_at ASC`);\n json(res, { messages: query(stmt) });\n } else if (method === \"POST\") {\n const { messages } = await body(req);\n if (!Array.isArray(messages) || messages.length === 0) {\n json(res, { success: false, error: \"invalid\" }, 400);\n return true;\n }\n if ((messages as unknown[]).length > 100) {\n json(res, { success: false, error: \"too many requests\" }, 413);\n return true;\n }\n // \u591A\u884C INSERT \u6539\u6210\u5FAA\u73AF\u5355\u6761 INSERT OR REPLACE \u2014\u2014 \u7B80\u5355\u4F18\u5148\n for (const m of messages as Array<Record<string, unknown>>) {\n query(\n SQL`INSERT OR REPLACE INTO sfmc_chat_messages (\n id, channel_id, from_id, from_name, type, content, attachment, show_timestamp, created_at\n ) VALUES (\n ${m.id}, ${m.channelId}, ${m.fromid}, ${m.fromName},\n ${m.type ?? \"text\"}, ${m.content}, ${m.attachment ?? null},\n ${m.showTimestamp ? 1 : 0}, ${m.timestamp}\n )`\n );\n }\n const bridgeId = getBridgeChannelId();\n for (const m of messages as Array<Record<string, unknown>>) {\n const channelId = String(m.channelId ?? \"\");\n const fromId = String(m.fromid ?? \"\");\n if (!shouldForwardChatToQQ(bridgeId, channelId, fromId)) continue;\n forwardToQQBridge(channelId, String(m.fromName ?? \"\"), String(m.content ?? \"\"), fromId);\n }\n json(res, { success: true });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n }\n return false;\n };\n}\n\nexport { createMessagesRoutes };\n"],
5
- "mappings": "AAQA,SAAS,WAAW;AAab,SAAS,sBACd,iBACA,WACA,QACS;AACT,QAAM,SAAS,OAAO,mBAAmB,EAAE,EAAE,KAAK;AAClD,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,aAAa,EAAE,EAAE,KAAK,MAAM,OAAQ,QAAO;AACtD,QAAM,MAAM,OAAO,UAAU,EAAE,EAAE,KAAK;AAEtC,MAAI,CAAC,IAAK,QAAO;AAEjB,MAAI,IAAI,WAAW,KAAK,EAAG,QAAO;AAClC,SAAO;AACT;AAEA,SAAS,qBAAqB,EAAE,OAAO,MAAM,MAAM,mBAAmB,mBAAmB,GAAS;AAChG,SAAO,eAAe,OAAO;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAMqB;AACnB,QAAI,SAAS,sBAAsB;AACjC,UAAI,WAAW,OAAO;AACpB,cAAM,OAAO;AACb,cAAM,SAAS,OAAO,IAAI,QAAQ,GAAG,KAAK;AAC1C,YAAI,OAAQ,MAAK,OAAO,yBAAyB,IAAI,MAAM,GAAG,GAAG;AACjE,cAAM,OAAO,OAAO,IAAI,MAAM,GAAG,KAAK;AACtC,YAAI,KAAM,MAAK,OAAO,kBAAkB,IAAI,EAAE;AAC9C,cAAM,YAAY,OAAO,IAAI,WAAW,GAAG,KAAK;AAChD,YAAI,UAAW,MAAK,OAAO,wBAAwB,SAAS,EAAE;AAC9D,cAAM,OAAO,OAAO,IAAI,MAAM,GAAG,KAAK;AACtC,YAAI,KAAM,MAAK,OAAO,qBAAqB,IAAI,EAAE;AACjD,cAAM,eAAe,OAAO,IAAI,cAAc,GAAG,KAAK;AACtD,YAAI,aAAc,MAAK,OAAO,yBAAyB,OAAO,YAAY,CAAC,EAAE;AAC7E,cAAM,YAAY,OAAO,IAAI,WAAW,GAAG,KAAK;AAChD,YAAI,UAAW,MAAK,OAAO,yBAAyB,OAAO,SAAS,CAAC,EAAE;AACvE,cAAM,eAAe,OAAO,IAAI,cAAc,GAAG,KAAK;AACtD,YAAI,aAAc,MAAK,OAAO,yBAAyB,OAAO,YAAY,CAAC,EAAE;AAC7E,aAAK,OAAO,6BAA6B;AACzC,aAAK,KAAK,EAAE,UAAU,MAAM,IAAI,EAAE,CAAC;AAAA,MACrC,WAAW,WAAW,QAAQ;AAC5B,cAAM,EAAE,SAAS,IAAI,MAAM,KAAK,GAAG;AACnC,YAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,GAAG;AACrD,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,UAAU,GAAG,GAAG;AACnD,iBAAO;AAAA,QACT;AACA,YAAK,SAAuB,SAAS,KAAK;AACxC,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,oBAAoB,GAAG,GAAG;AAC7D,iBAAO;AAAA,QACT;AAEA,mBAAW,KAAK,UAA4C;AAC1D;AAAA,YACE;AAAA;AAAA;AAAA,kBAGM,EAAE,EAAE,KAAK,EAAE,SAAS,KAAK,EAAE,MAAM,KAAK,EAAE,QAAQ;AAAA,kBAChD,EAAE,QAAQ,MAAM,KAAK,EAAE,OAAO,KAAK,EAAE,cAAc,IAAI;AAAA,kBACvD,EAAE,gBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS;AAAA;AAAA,UAE/C;AAAA,QACF;AACA,cAAM,WAAW,mBAAmB;AACpC,mBAAW,KAAK,UAA4C;AAC1D,gBAAM,YAAY,OAAO,EAAE,aAAa,EAAE;AAC1C,gBAAM,SAAS,OAAO,EAAE,UAAU,EAAE;AACpC,cAAI,CAAC,sBAAsB,UAAU,WAAW,MAAM,EAAG;AACzD,4BAAkB,WAAW,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM;AAAA,QACxF;AACA,aAAK,KAAK,EAAE,SAAS,KAAK,CAAC;AAAA,MAC7B,OAAO;AACL,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AAAA,MACvD;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACF;",
4
+ "sourcesContent": ["/**\n * routes/messages.ts \u2014 \u804A\u5929\u6D88\u606F (sfmc_chat_messages)\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/messages \u2014 \u6A21\u7CCA/\u8FC7\u6EE4\u67E5\u8BE2\u6D88\u606F\n * POST /api/sfmc/messages \u2014 \u6279\u91CF INSERT OR REPLACE \u6D88\u606F\uFF08\u6309\u9891\u9053\u5F00\u5173\u8F6C\u53D1\u81F3 QQ\uFF09\n */\n\nimport { SQL } from \"sql-template-strings\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\n\ninterface Deps {\n query: QueryFn;\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n forwardToQQBridge: (channelId: string, prefix: string, fromName: string, content: string, fromId: string) => void;\n /** \u67E5\u8BE2\u6765\u6E90\u9891\u9053\u7684\u524D\u7F00\u548C QQ \u8F6C\u53D1\u5F00\u5173\u3002 */\n getChannelForQQ: (channelId: string) => {\n prefix: string;\n forward_to_qq: number;\n source_game?: number;\n source_qq?: number;\n source_system?: number;\n } | null;\n}\n\n/** \u662F\u5426\u5E94\u5C06\u672C\u6761\u6D88\u606F\u63A8\u5230 QQ \u7FA4 */\nexport function shouldForwardChatToQQ(\n channel: { forward_to_qq: number } | null,\n fromId: string\n): boolean {\n if (!channel || channel.forward_to_qq !== 1) return false;\n const fid = String(fromId ?? \"\").trim();\n // \u65E0\u53D1\u9001\u8005 id \u4E0D\u51FA\u7AD9\uFF08\u907F\u514D\u89E3\u6790\u5931\u8D25\u65F6\u8BEF\u63A8\uFF09\n if (!fid) return false;\n // QQ\u2192MC \u5199\u5165\u7684 fromid \u5F62\u5982 qq_*\uFF0C\u7981\u6B62\u56DE\u73AF\u63A8\u7FA4\n if (fid.startsWith(\"qq_\")) return false;\n return true;\n}\n\nfunction createMessagesRoutes({ query, body, json, forwardToQQBridge, getChannelForQQ }: Deps) {\n return async function handle({\n path,\n method,\n params,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n if (path === \"/api/sfmc/messages\") {\n if (method === \"GET\") {\n const stmt = SQL`SELECT * FROM sfmc_chat_messages WHERE 1=1`;\n const search = params.get(\"search\")?.trim();\n if (search) stmt.append(SQL` AND (content LIKE ${`%${search}%`})`);\n const type = params.get(\"type\")?.trim();\n if (type) stmt.append(SQL` AND type = ${type}`);\n const channelId = params.get(\"channelId\")?.trim();\n if (channelId) stmt.append(SQL` AND channel_id = ${channelId}`);\n const from = params.get(\"from\")?.trim();\n if (from) stmt.append(SQL` AND from_id = ${from}`);\n const minCreatedAt = params.get(\"minCreatedAt\")?.trim();\n if (minCreatedAt) stmt.append(SQL` AND created_at >= ${Number(minCreatedAt)}`);\n const minSentAt = params.get(\"minSentAt\")?.trim();\n if (minSentAt) stmt.append(SQL` AND created_at >= ${Number(minSentAt)}`);\n const maxCreatedAt = params.get(\"maxCreatedAt\")?.trim();\n if (maxCreatedAt) stmt.append(SQL` AND created_at <= ${Number(maxCreatedAt)}`);\n stmt.append(SQL` ORDER BY created_at ASC`);\n json(res, { messages: query(stmt) });\n } else if (method === \"POST\") {\n const { messages } = await body(req);\n if (!Array.isArray(messages) || messages.length === 0) {\n json(res, { success: false, error: \"invalid\" }, 400);\n return true;\n }\n if ((messages as unknown[]).length > 100) {\n json(res, { success: false, error: \"too many requests\" }, 413);\n return true;\n }\n // \u5148\u68C0\u67E5\u6574\u4E2A\u6279\u6B21\uFF0C\u907F\u514D\u524D\u51E0\u6761\u5DF2\u5199\u5165\u540E\u624D\u53D1\u73B0\u88AB\u7981\u7528\u7684\u6765\u6E90\u3002\n for (const m of messages as Array<Record<string, unknown>>) {\n const channel = getChannelForQQ(String(m.channelId ?? \"\"));\n const fromId = String(m.fromid ?? \"\");\n const sourceField = fromId.startsWith(\"qq_\")\n ? \"source_qq\"\n : fromId === \"system\" || fromId === \"SYSTEM\"\n ? \"source_system\"\n : \"source_game\";\n if (channel?.[sourceField] === 0) {\n json(res, { success: false, error: \"channel source disabled\" }, 403);\n return true;\n }\n }\n // \u591A\u884C INSERT \u6539\u6210\u5FAA\u73AF\u5355\u6761 INSERT OR REPLACE \u2014\u2014 \u7B80\u5355\u4F18\u5148\n for (const m of messages as Array<Record<string, unknown>>) {\n query(\n SQL`INSERT OR REPLACE INTO sfmc_chat_messages (\n id, channel_id, from_id, from_name, type, content, attachment, show_timestamp, created_at\n ) VALUES (\n ${m.id}, ${m.channelId}, ${m.fromid}, ${m.fromName},\n ${m.type ?? \"text\"}, ${m.content}, ${m.attachment ?? null},\n ${m.showTimestamp ? 1 : 0}, ${m.timestamp}\n )`\n );\n }\n for (const m of messages as Array<Record<string, unknown>>) {\n const channelId = String(m.channelId ?? \"\");\n const fromId = String(m.fromid ?? \"\");\n if (!fromId.trim() || fromId.startsWith(\"qq_\")) continue;\n const channel = getChannelForQQ(channelId);\n if (!shouldForwardChatToQQ(channel, fromId)) continue;\n forwardToQQBridge(\n channelId,\n channel!.prefix || channelId,\n String(m.fromName ?? \"\"),\n String(m.content ?? \"\"),\n fromId\n );\n }\n json(res, { success: true });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n }\n return false;\n };\n}\n\nexport { createMessagesRoutes };\n"],
5
+ "mappings": "AAQA,SAAS,WAAW;AAmBb,SAAS,sBACd,SACA,QACS;AACT,MAAI,CAAC,WAAW,QAAQ,kBAAkB,EAAG,QAAO;AACpD,QAAM,MAAM,OAAO,UAAU,EAAE,EAAE,KAAK;AAEtC,MAAI,CAAC,IAAK,QAAO;AAEjB,MAAI,IAAI,WAAW,KAAK,EAAG,QAAO;AAClC,SAAO;AACT;AAEA,SAAS,qBAAqB,EAAE,OAAO,MAAM,MAAM,mBAAmB,gBAAgB,GAAS;AAC7F,SAAO,eAAe,OAAO;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAMqB;AACnB,QAAI,SAAS,sBAAsB;AACjC,UAAI,WAAW,OAAO;AACpB,cAAM,OAAO;AACb,cAAM,SAAS,OAAO,IAAI,QAAQ,GAAG,KAAK;AAC1C,YAAI,OAAQ,MAAK,OAAO,yBAAyB,IAAI,MAAM,GAAG,GAAG;AACjE,cAAM,OAAO,OAAO,IAAI,MAAM,GAAG,KAAK;AACtC,YAAI,KAAM,MAAK,OAAO,kBAAkB,IAAI,EAAE;AAC9C,cAAM,YAAY,OAAO,IAAI,WAAW,GAAG,KAAK;AAChD,YAAI,UAAW,MAAK,OAAO,wBAAwB,SAAS,EAAE;AAC9D,cAAM,OAAO,OAAO,IAAI,MAAM,GAAG,KAAK;AACtC,YAAI,KAAM,MAAK,OAAO,qBAAqB,IAAI,EAAE;AACjD,cAAM,eAAe,OAAO,IAAI,cAAc,GAAG,KAAK;AACtD,YAAI,aAAc,MAAK,OAAO,yBAAyB,OAAO,YAAY,CAAC,EAAE;AAC7E,cAAM,YAAY,OAAO,IAAI,WAAW,GAAG,KAAK;AAChD,YAAI,UAAW,MAAK,OAAO,yBAAyB,OAAO,SAAS,CAAC,EAAE;AACvE,cAAM,eAAe,OAAO,IAAI,cAAc,GAAG,KAAK;AACtD,YAAI,aAAc,MAAK,OAAO,yBAAyB,OAAO,YAAY,CAAC,EAAE;AAC7E,aAAK,OAAO,6BAA6B;AACzC,aAAK,KAAK,EAAE,UAAU,MAAM,IAAI,EAAE,CAAC;AAAA,MACrC,WAAW,WAAW,QAAQ;AAC5B,cAAM,EAAE,SAAS,IAAI,MAAM,KAAK,GAAG;AACnC,YAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,GAAG;AACrD,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,UAAU,GAAG,GAAG;AACnD,iBAAO;AAAA,QACT;AACA,YAAK,SAAuB,SAAS,KAAK;AACxC,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,oBAAoB,GAAG,GAAG;AAC7D,iBAAO;AAAA,QACT;AAEA,mBAAW,KAAK,UAA4C;AAC1D,gBAAM,UAAU,gBAAgB,OAAO,EAAE,aAAa,EAAE,CAAC;AACzD,gBAAM,SAAS,OAAO,EAAE,UAAU,EAAE;AACpC,gBAAM,cAAc,OAAO,WAAW,KAAK,IACvC,cACA,WAAW,YAAY,WAAW,WAChC,kBACA;AACN,cAAI,UAAU,WAAW,MAAM,GAAG;AAChC,iBAAK,KAAK,EAAE,SAAS,OAAO,OAAO,0BAA0B,GAAG,GAAG;AACnE,mBAAO;AAAA,UACT;AAAA,QACF;AAEA,mBAAW,KAAK,UAA4C;AAC1D;AAAA,YACE;AAAA;AAAA;AAAA,kBAGM,EAAE,EAAE,KAAK,EAAE,SAAS,KAAK,EAAE,MAAM,KAAK,EAAE,QAAQ;AAAA,kBAChD,EAAE,QAAQ,MAAM,KAAK,EAAE,OAAO,KAAK,EAAE,cAAc,IAAI;AAAA,kBACvD,EAAE,gBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS;AAAA;AAAA,UAE/C;AAAA,QACF;AACA,mBAAW,KAAK,UAA4C;AAC1D,gBAAM,YAAY,OAAO,EAAE,aAAa,EAAE;AAC1C,gBAAM,SAAS,OAAO,EAAE,UAAU,EAAE;AACpC,cAAI,CAAC,OAAO,KAAK,KAAK,OAAO,WAAW,KAAK,EAAG;AAChD,gBAAM,UAAU,gBAAgB,SAAS;AACzC,cAAI,CAAC,sBAAsB,SAAS,MAAM,EAAG;AAC7C;AAAA,YACE;AAAA,YACA,QAAS,UAAU;AAAA,YACnB,OAAO,EAAE,YAAY,EAAE;AAAA,YACvB,OAAO,EAAE,WAAW,EAAE;AAAA,YACtB;AAAA,UACF;AAAA,QACF;AACA,aAAK,KAAK,EAAE,SAAS,KAAK,CAAC;AAAA,MAC7B,OAAO;AACL,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AAAA,MACvD;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACF;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"qq-bind.d.ts","sourceRoot":"","sources":["../../src/routes/qq-bind.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,QAAA,MAAM,WAAW,QAAgB,CAAC;AAGlC,UAAU,IAAI;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACpG;AAQD,iBAAS,kBAAkB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,yCAOlD;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAsLrB;AAED,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"qq-bind.d.ts","sourceRoot":"","sources":["../../src/routes/qq-bind.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,QAAA,MAAM,WAAW,QAAgB,CAAC;AAGlC,UAAU,IAAI;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACpG;AAQD,iBAAS,kBAAkB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,yCAOlD;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAmNrB;AAED,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { randomInt } from "node:crypto";
2
2
  import { SQL } from "sql-template-strings";
3
+ import { loadQqAccountProfile, saveQqAccountProfile } from "../domain/qq-account-profile.js";
3
4
  const CODE_TTL_MS = 5 * 60 * 1e3;
4
5
  const CODE_LEN = 6;
5
6
  function genCode() {
@@ -25,6 +26,8 @@ function createQqBindRoutes({ query, body, json }) {
25
26
  }
26
27
  const rows = openid ? query(SQL`SELECT * FROM sfmc_qq_bindings WHERE qq_user_openid = ${openid} LIMIT 1`) : query(SQL`SELECT * FROM sfmc_qq_bindings WHERE player_xuid = ${xuid} LIMIT 1`);
27
28
  const row = rows[0] ?? null;
29
+ const playerId = row ? String(row.player_xuid ?? "") : "";
30
+ const playerName = row ? String(row.player_name ?? "") : "";
28
31
  json(res, {
29
32
  success: true,
30
33
  bound: !!row,
@@ -34,10 +37,35 @@ function createQqBindRoutes({ query, body, json }) {
34
37
  player_name: row.player_name,
35
38
  qq_backend: row.qq_backend,
36
39
  bound_at: row.bound_at
37
- } : null
40
+ } : null,
41
+ ...row ? { profile: loadQqAccountProfile(query, playerId, playerName) } : {}
38
42
  });
39
43
  return true;
40
44
  }
45
+ if (path === "/api/sfmc/qq/bind/profile" && method === "POST") {
46
+ const data = await body(req);
47
+ const playerId = String(data.xuid ?? data.player_xuid ?? "").trim();
48
+ const playerName = String(data.name ?? data.player_name ?? "").trim();
49
+ if (!playerId) {
50
+ json(res, { success: false, error: "xuid_required" }, 400);
51
+ return true;
52
+ }
53
+ const optionalNumber = (value) => {
54
+ if (value == null || value === "") return null;
55
+ const n = Number(value);
56
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : null;
57
+ };
58
+ saveQqAccountProfile(query, {
59
+ playerId,
60
+ playerName,
61
+ balance: optionalNumber(data.balance),
62
+ todaySeconds: optionalNumber(data.today_seconds),
63
+ monthSeconds: optionalNumber(data.month_seconds),
64
+ totalSeconds: optionalNumber(data.total_seconds)
65
+ });
66
+ json(res, { success: true });
67
+ return true;
68
+ }
41
69
  if (path === "/api/sfmc/qq/bind/request" && method === "POST") {
42
70
  const data = await body(req);
43
71
  const openid = String(data.openid ?? data.qq_user_openid ?? "").trim();