@sfmc-bds/qq-bridge 0.1.1-beta.4 → 0.2.0-beta.5

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 (64) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +14 -3
  3. package/dist/commands/db-api.js +89 -0
  4. package/dist/commands/db-api.js.map +7 -0
  5. package/dist/commands/handlers.js +660 -0
  6. package/dist/commands/handlers.js.map +7 -0
  7. package/dist/commands/index.js +55 -0
  8. package/dist/commands/index.js.map +7 -0
  9. package/dist/commands/interaction-router.js +149 -0
  10. package/dist/commands/interaction-router.js.map +7 -0
  11. package/dist/commands/join-settings-ui.js +108 -0
  12. package/dist/commands/join-settings-ui.js.map +7 -0
  13. package/dist/commands/join-settings-ui.test.js +43 -0
  14. package/dist/commands/join-settings-ui.test.js.map +7 -0
  15. package/dist/commands/menu-format.js +45 -0
  16. package/dist/commands/menu-format.js.map +7 -0
  17. package/dist/commands/menu-panel-sync.js +130 -0
  18. package/dist/commands/menu-panel-sync.js.map +7 -0
  19. package/dist/commands/menu-panel-sync.test.js +27 -0
  20. package/dist/commands/menu-panel-sync.test.js.map +7 -0
  21. package/dist/commands/pending.js +33 -0
  22. package/dist/commands/pending.js.map +7 -0
  23. package/dist/commands/registry.js +40 -0
  24. package/dist/commands/registry.js.map +7 -0
  25. package/dist/commands/render.js +46 -0
  26. package/dist/commands/render.js.map +7 -0
  27. package/dist/commands/render.test.js +44 -0
  28. package/dist/commands/render.test.js.map +7 -0
  29. package/dist/commands/reply-llbot.js +63 -0
  30. package/dist/commands/reply-llbot.js.map +7 -0
  31. package/dist/commands/reply-official.js +66 -0
  32. package/dist/commands/reply-official.js.map +7 -0
  33. package/dist/commands/router.js +53 -0
  34. package/dist/commands/router.js.map +7 -0
  35. package/dist/commands/router.test.js +92 -0
  36. package/dist/commands/router.test.js.map +7 -0
  37. package/dist/commands/types.js +1 -0
  38. package/dist/commands/types.js.map +7 -0
  39. package/dist/config.js +12 -2
  40. package/dist/config.js.map +2 -2
  41. package/dist/console.js +40 -20
  42. package/dist/console.js.map +2 -2
  43. package/dist/dbserver.js.map +2 -2
  44. package/dist/index.js +117 -20
  45. package/dist/index.js.map +2 -2
  46. package/dist/llbot-ws-api.js +90 -0
  47. package/dist/llbot-ws-api.js.map +7 -0
  48. package/dist/llbot-ws-api.test.js +32 -0
  49. package/dist/llbot-ws-api.test.js.map +7 -0
  50. package/dist/official/events.js +136 -0
  51. package/dist/official/events.js.map +7 -0
  52. package/dist/official/events.test.js +123 -0
  53. package/dist/official/events.test.js.map +7 -0
  54. package/dist/official/gateway.js +226 -0
  55. package/dist/official/gateway.js.map +7 -0
  56. package/dist/onebot.js +23 -5
  57. package/dist/onebot.js.map +2 -2
  58. package/dist/project-root.js +2 -2
  59. package/dist/project-root.js.map +2 -2
  60. package/dist/runtime-status.js +45 -0
  61. package/dist/runtime-status.js.map +7 -0
  62. package/dist/ws-server.js +4 -0
  63. package/dist/ws-server.js.map +2 -2
  64. package/package.json +18 -15
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/commands/router.ts"],
4
+ "sourcesContent": ["/**\n * commands/router.ts \u2014 \u89E3\u6790\u89E6\u53D1\u8BCD / \u7F16\u53F7\u4F1A\u8BDD\uFF0C\u6267\u884C handler\n */\n\nimport { log } from \"../log.js\";\nimport type { PendingChoiceStore } from \"./pending.js\";\nimport { normalizeTrigger, type CommandRegistry } from \"./registry.js\";\nimport type {\n CommandContext,\n CommandResult,\n InboundMessage,\n ReplyPort,\n ReplyTarget,\n} from \"./types.js\";\n\nexport type CommandRouterOptions = {\n registry: CommandRegistry;\n pending: PendingChoiceStore;\n reply: ReplyPort;\n startedAt: number;\n runtimeInfo: CommandContext[\"runtimeInfo\"];\n};\n\n/**\n * \u82E5\u6587\u672C\u4E3A\u7EAF\u6570\u5B57 1\u20139\uFF0C\u5C1D\u8BD5 pending\uFF1B\u5426\u5219\u6309\u89E6\u53D1\u8BCD\u89E3\u6790\u3002\n * \u547D\u4E2D\u5E76\u56DE\u590D\u6210\u529F\u8FD4\u56DE true\uFF08\u8C03\u7528\u65B9\u5E94\u62E6\u622A\u8F6C\u53D1\uFF09\u3002\n */\nexport class CommandRouter {\n private readonly opts: CommandRouterOptions;\n\n constructor(opts: CommandRouterOptions) {\n this.opts = opts;\n }\n\n /** \u4F9B menu/panel sync \u8BFB\u53D6\u6743\u5A01\u547D\u4EE4\u8868 */\n get registry(): CommandRegistry {\n return this.opts.registry;\n }\n\n async handle(inbound: InboundMessage): Promise<boolean> {\n const raw = String(inbound.text ?? \"\").trim();\n if (!raw) return false;\n\n // \u7F16\u53F7\u4F1A\u8BDD\uFF1A\u7528\u7F13\u5B58\u7684\u5B8C\u6574\u547D\u4EE4\u66FF\u6362\u6B63\u6587\uFF08\u542B\u53C2\u6570\uFF0C\u5982\u300C\u914D\u7F6E \u767D\u540D\u5355 \u5173\u300D\uFF09\n let effective: InboundMessage = inbound;\n const digit = /^[1-9]$/.exec(raw);\n if (digit) {\n const chosen = this.opts.pending.take(inbound.backend, inbound.groupId, inbound.userId, Number(digit[0]));\n if (!chosen) return false;\n effective = { ...inbound, text: chosen };\n }\n\n const resolved = this.opts.registry.resolve(effective.text);\n if (!resolved) return false;\n\n const ctx: CommandContext = {\n inbound: effective,\n startedAt: this.opts.startedAt,\n runtimeInfo: this.opts.runtimeInfo,\n };\n const result: CommandResult = await resolved.handler(ctx);\n\n // llbot\uFF1A\u6709\u6309\u94AE\u65F6\u5199\u5165\u7F16\u53F7\u4F1A\u8BDD\n if (effective.backend === \"llbot\" && result.buttons && result.buttons.length > 0) {\n const choices = new Map<number, string>();\n result.buttons.forEach((b, i) => {\n choices.set(i + 1, b.command);\n });\n this.opts.pending.set(effective.backend, effective.groupId, effective.userId, choices);\n }\n\n const target: ReplyTarget = { groupId: effective.groupId };\n if (effective.msgId) target.msgId = effective.msgId;\n\n try {\n await this.opts.reply.send(target, result, effective);\n log.info(`cmd=${resolved.name} handled user=${effective.userId} group=${effective.groupId}`);\n return true;\n } catch (e) {\n log.warn(`cmd=${resolved.name} \u56DE\u590D\u5931\u8D25: ${(e as Error).message}`);\n return true; // \u4ECD\u89C6\u4E3A\u5DF2\u5904\u7406\uFF0C\u907F\u514D\u518D\u8F6C\u53BB MC\n }\n }\n}\n\nexport { normalizeTrigger };\n"],
5
+ "mappings": "AAIA,SAAS,WAAW;AAEpB,SAAS,wBAA8C;AAqBhD,MAAM,cAAc;AAAA,EACR;AAAA,EAEjB,YAAY,MAA4B;AACtC,SAAK,OAAO;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,WAA4B;AAC9B,WAAO,KAAK,KAAK;AAAA,EACnB;AAAA,EAEA,MAAM,OAAO,SAA2C;AACtD,UAAM,MAAM,OAAO,QAAQ,QAAQ,EAAE,EAAE,KAAK;AAC5C,QAAI,CAAC,IAAK,QAAO;AAGjB,QAAI,YAA4B;AAChC,UAAM,QAAQ,UAAU,KAAK,GAAG;AAChC,QAAI,OAAO;AACT,YAAM,SAAS,KAAK,KAAK,QAAQ,KAAK,QAAQ,SAAS,QAAQ,SAAS,QAAQ,QAAQ,OAAO,MAAM,CAAC,CAAC,CAAC;AACxG,UAAI,CAAC,OAAQ,QAAO;AACpB,kBAAY,EAAE,GAAG,SAAS,MAAM,OAAO;AAAA,IACzC;AAEA,UAAM,WAAW,KAAK,KAAK,SAAS,QAAQ,UAAU,IAAI;AAC1D,QAAI,CAAC,SAAU,QAAO;AAEtB,UAAM,MAAsB;AAAA,MAC1B,SAAS;AAAA,MACT,WAAW,KAAK,KAAK;AAAA,MACrB,aAAa,KAAK,KAAK;AAAA,IACzB;AACA,UAAM,SAAwB,MAAM,SAAS,QAAQ,GAAG;AAGxD,QAAI,UAAU,YAAY,WAAW,OAAO,WAAW,OAAO,QAAQ,SAAS,GAAG;AAChF,YAAM,UAAU,oBAAI,IAAoB;AACxC,aAAO,QAAQ,QAAQ,CAAC,GAAG,MAAM;AAC/B,gBAAQ,IAAI,IAAI,GAAG,EAAE,OAAO;AAAA,MAC9B,CAAC;AACD,WAAK,KAAK,QAAQ,IAAI,UAAU,SAAS,UAAU,SAAS,UAAU,QAAQ,OAAO;AAAA,IACvF;AAEA,UAAM,SAAsB,EAAE,SAAS,UAAU,QAAQ;AACzD,QAAI,UAAU,MAAO,QAAO,QAAQ,UAAU;AAE9C,QAAI;AACF,YAAM,KAAK,KAAK,MAAM,KAAK,QAAQ,QAAQ,SAAS;AACpD,UAAI,KAAK,OAAO,SAAS,IAAI,iBAAiB,UAAU,MAAM,UAAU,UAAU,OAAO,EAAE;AAC3F,aAAO;AAAA,IACT,SAAS,GAAG;AACV,UAAI,KAAK,OAAO,SAAS,IAAI,8BAAW,EAAY,OAAO,EAAE;AAC7D,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,92 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { registerBuiltinCommands } from "./handlers.js";
4
+ import { PendingChoiceStore } from "./pending.js";
5
+ import { CommandRegistry } from "./registry.js";
6
+ import { CommandRouter } from "./router.js";
7
+ function makeReplyCapture() {
8
+ const sent = [];
9
+ return {
10
+ sent,
11
+ port: {
12
+ async send(_t, result) {
13
+ sent.push(result);
14
+ }
15
+ }
16
+ };
17
+ }
18
+ function makeRouter(reply) {
19
+ const registry = new CommandRegistry();
20
+ registerBuiltinCommands(registry);
21
+ return new CommandRouter({
22
+ registry,
23
+ pending: new PendingChoiceStore(6e4),
24
+ reply,
25
+ startedAt: Date.now() - 5e3,
26
+ runtimeInfo: { sandbox: false, appIdHint: "1905\u20266765" }
27
+ });
28
+ }
29
+ test("router \u547D\u4E2D ping", async () => {
30
+ const { port, sent } = makeReplyCapture();
31
+ const router = makeRouter(port);
32
+ const inbound = {
33
+ backend: "official",
34
+ groupId: "G",
35
+ userId: "U",
36
+ userName: "Alice",
37
+ text: "/ping",
38
+ msgId: "m1"
39
+ };
40
+ assert.equal(await router.handle(inbound), true);
41
+ assert.equal(sent.length, 1);
42
+ assert.match(sent[0].text, /^pong/);
43
+ });
44
+ test("registry \u9996\u8BCD\u5339\u914D\u300C\u7533\u8BF7\u5165\u670D Steve\u300D", () => {
45
+ const registry = new CommandRegistry();
46
+ registerBuiltinCommands(registry);
47
+ const cmd = registry.resolve("\u7533\u8BF7\u5165\u670D Steve");
48
+ assert.ok(cmd);
49
+ assert.equal(cmd.name, "join");
50
+ assert.equal(registry.resolve("\u8E22\u4EBA Alex")?.name, "kick");
51
+ });
52
+ test("router \u672A\u547D\u4E2D\u900F\u4F20", async () => {
53
+ const { port, sent } = makeReplyCapture();
54
+ const router = makeRouter(port);
55
+ assert.equal(
56
+ await router.handle({
57
+ backend: "official",
58
+ groupId: "G",
59
+ userId: "U",
60
+ userName: "A",
61
+ text: "hello world"
62
+ }),
63
+ false
64
+ );
65
+ assert.equal(sent.length, 0);
66
+ });
67
+ test("router llbot \u7F16\u53F7\u4F1A\u8BDD", async () => {
68
+ const { port, sent } = makeReplyCapture();
69
+ const router = makeRouter(port);
70
+ const base = {
71
+ backend: "llbot",
72
+ groupId: "123",
73
+ userId: "99",
74
+ userName: "Bob",
75
+ text: "\u83DC\u5355"
76
+ };
77
+ assert.equal(await router.handle(base), true);
78
+ assert.equal(sent.length, 1);
79
+ assert.ok(sent[0].buttons && sent[0].buttons.length >= 2);
80
+ assert.equal(
81
+ await router.handle({ ...base, text: "1" }),
82
+ true
83
+ );
84
+ assert.equal(sent.length, 2);
85
+ assert.match(sent[1].text, /^pong/);
86
+ });
87
+ test("registry normalizeTrigger", async () => {
88
+ const { normalizeTrigger } = await import("./registry.js");
89
+ assert.equal(normalizeTrigger(" /Ping "), "ping");
90
+ assert.equal(normalizeTrigger("\u83DC\u5355"), "\u83DC\u5355");
91
+ });
92
+ //# sourceMappingURL=router.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/commands/router.test.ts"],
4
+ "sourcesContent": ["/**\n * commands/router.test.ts\n */\nimport assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport { registerBuiltinCommands } from \"./handlers.js\";\nimport { PendingChoiceStore } from \"./pending.js\";\nimport { CommandRegistry } from \"./registry.js\";\nimport { CommandRouter } from \"./router.js\";\nimport type { CommandResult, InboundMessage, ReplyPort } from \"./types.js\";\n\nfunction makeReplyCapture(): { port: ReplyPort; sent: CommandResult[] } {\n const sent: CommandResult[] = [];\n return {\n sent,\n port: {\n async send(_t, result) {\n sent.push(result);\n },\n },\n };\n}\n\nfunction makeRouter(reply: ReplyPort): CommandRouter {\n const registry = new CommandRegistry();\n registerBuiltinCommands(registry);\n return new CommandRouter({\n registry,\n pending: new PendingChoiceStore(60_000),\n reply,\n startedAt: Date.now() - 5000,\n runtimeInfo: { sandbox: false, appIdHint: \"1905\u20266765\" },\n });\n}\n\ntest(\"router \u547D\u4E2D ping\", async () => {\n const { port, sent } = makeReplyCapture();\n const router = makeRouter(port);\n const inbound: InboundMessage = {\n backend: \"official\",\n groupId: \"G\",\n userId: \"U\",\n userName: \"Alice\",\n text: \"/ping\",\n msgId: \"m1\",\n };\n assert.equal(await router.handle(inbound), true);\n assert.equal(sent.length, 1);\n assert.match(sent[0]!.text, /^pong/);\n});\n\ntest(\"registry \u9996\u8BCD\u5339\u914D\u300C\u7533\u8BF7\u5165\u670D Steve\u300D\", () => {\n const registry = new CommandRegistry();\n registerBuiltinCommands(registry);\n const cmd = registry.resolve(\"\u7533\u8BF7\u5165\u670D Steve\");\n assert.ok(cmd);\n assert.equal(cmd!.name, \"join\");\n assert.equal(registry.resolve(\"\u8E22\u4EBA Alex\")?.name, \"kick\");\n});\n\ntest(\"router \u672A\u547D\u4E2D\u900F\u4F20\", async () => {\n const { port, sent } = makeReplyCapture();\n const router = makeRouter(port);\n assert.equal(\n await router.handle({\n backend: \"official\",\n groupId: \"G\",\n userId: \"U\",\n userName: \"A\",\n text: \"hello world\",\n }),\n false\n );\n assert.equal(sent.length, 0);\n});\n\ntest(\"router llbot \u7F16\u53F7\u4F1A\u8BDD\", async () => {\n const { port, sent } = makeReplyCapture();\n const router = makeRouter(port);\n const base: InboundMessage = {\n backend: \"llbot\",\n groupId: \"123\",\n userId: \"99\",\n userName: \"Bob\",\n text: \"\u83DC\u5355\",\n };\n assert.equal(await router.handle(base), true);\n assert.equal(sent.length, 1);\n assert.ok(sent[0]!.buttons && sent[0]!.buttons.length >= 2);\n\n assert.equal(\n await router.handle({ ...base, text: \"1\" }),\n true\n );\n assert.equal(sent.length, 2);\n // \u7B2C 1 \u4E2A\u6309\u94AE\u5BF9\u5E94 ping\uFF08\u4E3B\u83DC\u5355\u5DF2\u53BB\u6389\u81EA\u6307 menu\uFF09\n assert.match(sent[1]!.text, /^pong/);\n});\n\ntest(\"registry normalizeTrigger\", async () => {\n const { normalizeTrigger } = await import(\"./registry.js\");\n assert.equal(normalizeTrigger(\" /Ping \"), \"ping\");\n assert.equal(normalizeTrigger(\"\u83DC\u5355\"), \"\u83DC\u5355\");\n});\n"],
5
+ "mappings": "AAGA,OAAO,YAAY;AACnB,OAAO,UAAU;AACjB,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAG9B,SAAS,mBAA+D;AACtE,QAAM,OAAwB,CAAC;AAC/B,SAAO;AAAA,IACL;AAAA,IACA,MAAM;AAAA,MACJ,MAAM,KAAK,IAAI,QAAQ;AACrB,aAAK,KAAK,MAAM;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,WAAW,OAAiC;AACnD,QAAM,WAAW,IAAI,gBAAgB;AACrC,0BAAwB,QAAQ;AAChC,SAAO,IAAI,cAAc;AAAA,IACvB;AAAA,IACA,SAAS,IAAI,mBAAmB,GAAM;AAAA,IACtC;AAAA,IACA,WAAW,KAAK,IAAI,IAAI;AAAA,IACxB,aAAa,EAAE,SAAS,OAAO,WAAW,iBAAY;AAAA,EACxD,CAAC;AACH;AAEA,KAAK,4BAAkB,YAAY;AACjC,QAAM,EAAE,MAAM,KAAK,IAAI,iBAAiB;AACxC,QAAM,SAAS,WAAW,IAAI;AAC9B,QAAM,UAA0B;AAAA,IAC9B,SAAS;AAAA,IACT,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AACA,SAAO,MAAM,MAAM,OAAO,OAAO,OAAO,GAAG,IAAI;AAC/C,SAAO,MAAM,KAAK,QAAQ,CAAC;AAC3B,SAAO,MAAM,KAAK,CAAC,EAAG,MAAM,OAAO;AACrC,CAAC;AAED,KAAK,+EAA6B,MAAM;AACtC,QAAM,WAAW,IAAI,gBAAgB;AACrC,0BAAwB,QAAQ;AAChC,QAAM,MAAM,SAAS,QAAQ,gCAAY;AACzC,SAAO,GAAG,GAAG;AACb,SAAO,MAAM,IAAK,MAAM,MAAM;AAC9B,SAAO,MAAM,SAAS,QAAQ,mBAAS,GAAG,MAAM,MAAM;AACxD,CAAC;AAED,KAAK,yCAAgB,YAAY;AAC/B,QAAM,EAAE,MAAM,KAAK,IAAI,iBAAiB;AACxC,QAAM,SAAS,WAAW,IAAI;AAC9B,SAAO;AAAA,IACL,MAAM,OAAO,OAAO;AAAA,MAClB,SAAS;AAAA,MACT,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,MAAM;AAAA,IACR,CAAC;AAAA,IACD;AAAA,EACF;AACA,SAAO,MAAM,KAAK,QAAQ,CAAC;AAC7B,CAAC;AAED,KAAK,yCAAqB,YAAY;AACpC,QAAM,EAAE,MAAM,KAAK,IAAI,iBAAiB;AACxC,QAAM,SAAS,WAAW,IAAI;AAC9B,QAAM,OAAuB;AAAA,IAC3B,SAAS;AAAA,IACT,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,EACR;AACA,SAAO,MAAM,MAAM,OAAO,OAAO,IAAI,GAAG,IAAI;AAC5C,SAAO,MAAM,KAAK,QAAQ,CAAC;AAC3B,SAAO,GAAG,KAAK,CAAC,EAAG,WAAW,KAAK,CAAC,EAAG,QAAQ,UAAU,CAAC;AAE1D,SAAO;AAAA,IACL,MAAM,OAAO,OAAO,EAAE,GAAG,MAAM,MAAM,IAAI,CAAC;AAAA,IAC1C;AAAA,EACF;AACA,SAAO,MAAM,KAAK,QAAQ,CAAC;AAE3B,SAAO,MAAM,KAAK,CAAC,EAAG,MAAM,OAAO;AACrC,CAAC;AAED,KAAK,6BAA6B,YAAY;AAC5C,QAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,eAAe;AACzD,SAAO,MAAM,iBAAiB,SAAS,GAAG,MAAM;AAChD,SAAO,MAAM,iBAAiB,cAAI,GAAG,cAAI;AAC3C,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
package/dist/config.js CHANGED
@@ -11,15 +11,25 @@ const CFG_PATH = configPath(ROOT_DIR, "qq_config.json");
11
11
  function applyDefaults(raw) {
12
12
  const merged = { ...DEFAULT_QQ_CONFIG, ...raw };
13
13
  const stripped = stripConfigMeta(merged);
14
+ const backendRaw = String(stripped.qq_backend ?? DEFAULT_QQ_CONFIG.qq_backend ?? "official");
15
+ const qq_backend = backendRaw === "llbot" ? "llbot" : "official";
14
16
  return {
17
+ ...stripped,
15
18
  qq_enabled: stripped.qq_enabled !== false,
19
+ qq_backend,
20
+ qq_app_id: String(stripped.qq_app_id ?? DEFAULT_QQ_CONFIG.qq_app_id ?? ""),
21
+ qq_app_secret: String(stripped.qq_app_secret ?? DEFAULT_QQ_CONFIG.qq_app_secret ?? ""),
22
+ qq_sandbox: stripped.qq_sandbox === true,
23
+ qq_group_openid: String(stripped.qq_group_openid ?? DEFAULT_QQ_CONFIG.qq_group_openid ?? ""),
24
+ qq_group_panel_id: String(stripped.qq_group_panel_id ?? DEFAULT_QQ_CONFIG.qq_group_panel_id ?? ""),
25
+ qq_sync_menu_panel: stripped.qq_sync_menu_panel !== false,
26
+ qq_admin_openids: Array.isArray(stripped.qq_admin_openids) ? stripped.qq_admin_openids.map((x) => String(x).trim()).filter(Boolean) : [],
16
27
  qq_ws_port: parseInt(String(stripped.qq_ws_port ?? DEFAULT_QQ_CONFIG.qq_ws_port ?? 3002), 10),
17
28
  qq_group_id: String(stripped.qq_group_id ?? DEFAULT_QQ_CONFIG.qq_group_id ?? "0"),
18
29
  bridge_channel_id: String(stripped.bridge_channel_id ?? DEFAULT_QQ_CONFIG.bridge_channel_id ?? ""),
19
30
  db_host: String(stripped.db_host ?? "127.0.0.1"),
20
31
  db_port: parseInt(String(stripped.db_port ?? "3001"), 10),
21
- mctoqq_prefix: String(stripped.mctoqq_prefix ?? DEFAULT_QQ_CONFIG.mctoqq_prefix ?? "[MC]"),
22
- ...stripped
32
+ mctoqq_prefix: String(stripped.mctoqq_prefix ?? DEFAULT_QQ_CONFIG.mctoqq_prefix ?? "[MC]")
23
33
  };
24
34
  }
25
35
  function readFromDisk() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/config.ts"],
4
- "sourcesContent": ["/**\n * config.ts \u2014 \u52A0\u8F7D configs/qq_config.json\n *\n * \u884C\u4E3A\u4E0E\u65E7 index.js \u4FDD\u6301\u5B8C\u5168\u4E00\u81F4:\n * - \u6587\u4EF6\u4E0D\u5B58\u5728\u6216\u89E3\u6790\u5931\u8D25: \u8FDB\u7A0B\u9000\u51FA (\u65E7\u7248 process.exit(1))\n * - \u5B57\u6BB5\u7F3A\u5931: \u8D70\u9ED8\u8BA4\u503C\n * - reload \u65F6\u4EC5\u8986\u76D6\u539F\u5BF9\u8C61 (mutate), \u4FDD\u7559\u8FD0\u884C\u65F6\u5F15\u7528\u7684\u540C\u4E00\u4EFD\u5BF9\u8C61\n */\n\nimport {\n configPath,\n DEFAULT_QQ_CONFIG,\n loadEnsuredConfig,\n stripConfigMeta,\n} from \"@sfmc-bds/sdk/node/config\";\nimport { log } from \"./log.js\";\nimport { PROJECT_ROOT } from \"./project-root.js\";\nimport type { QQBridgeConfig } from \"./types.js\";\n\n/** \u7EDF\u4E00\u901A\u8FC7 SDK \u89E3\u6790\u9879\u76EE\u6839:env SFMC_ROOT > project-root \u4E0A\u6EAF\u3002 */\nexport const ROOT_DIR: string = PROJECT_ROOT;\nexport const CFG_PATH: string = configPath(ROOT_DIR, \"qq_config.json\");\n\nfunction applyDefaults(raw: Partial<QQBridgeConfig>): QQBridgeConfig {\n /* \u4EE5 SDK DEFAULT_QQ_CONFIG \u4E3A\u552F\u4E00\u7F3A\u7701\u6743\u5A01\uFF08DRY/LSP\uFF09\uFF0C\u518D\u53E0\u8FD0\u884C\u65F6\u6D3E\u751F\u5B57\u6BB5 */\n const merged = { ...DEFAULT_QQ_CONFIG, ...raw } as Partial<QQBridgeConfig> & Record<string, unknown>;\n const stripped = stripConfigMeta(merged);\n return {\n qq_enabled: stripped.qq_enabled !== false,\n qq_ws_port: parseInt(String(stripped.qq_ws_port ?? DEFAULT_QQ_CONFIG.qq_ws_port ?? 3002), 10),\n qq_group_id: String(stripped.qq_group_id ?? DEFAULT_QQ_CONFIG.qq_group_id ?? \"0\"),\n bridge_channel_id: String(stripped.bridge_channel_id ?? DEFAULT_QQ_CONFIG.bridge_channel_id ?? \"\"),\n db_host: String(stripped.db_host ?? \"127.0.0.1\"),\n db_port: parseInt(String(stripped.db_port ?? \"3001\"), 10),\n mctoqq_prefix: String(stripped.mctoqq_prefix ?? DEFAULT_QQ_CONFIG.mctoqq_prefix ?? \"[MC]\"),\n ...stripped,\n };\n}\n\nfunction readFromDisk(): QQBridgeConfig {\n const raw = loadEnsuredConfig(\n ROOT_DIR,\n \"qq_config.json\",\n \"qq_config\",\n { ...DEFAULT_QQ_CONFIG } as Record<string, unknown>\n );\n return applyDefaults(raw as Partial<QQBridgeConfig>);\n}\n\n/** \u8FDB\u7A0B\u542F\u52A8\u65F6\u52A0\u8F7D\u4E00\u6B21\u3002\u5931\u8D25\u76F4\u63A5\u9000\u51FA,\u4E0E\u65E7\u5B9E\u73B0\u4E00\u81F4\u3002 */\nexport function loadInitialConfig(): QQBridgeConfig {\n try {\n return readFromDisk();\n } catch (e) {\n log.error(`\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E: ${CFG_PATH}: ${(e as Error).message}`);\n process.exit(1);\n }\n}\n\n/**\n * \u91CD\u65B0\u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6,\u5408\u5E76\u5230\u4F20\u5165\u5BF9\u8C61\u4E0A (mutate)\u3002\n * \u65E7\u5B9E\u73B0\u662F Object.assign(cfg, newCfg),\u4FDD\u7559\u8FD0\u884C\u65F6\u5BF9\u539F cfg \u5BF9\u8C61\u7684\u5F15\u7528 \u2014 \u540C\u6837\u884C\u4E3A\u3002\n */\nexport function reloadInto(cfg: QQBridgeConfig): void {\n try {\n const fresh = readFromDisk();\n Object.assign(cfg, fresh);\n } catch (e) {\n // reload \u5931\u8D25\u4E0D\u629B,\u65E7\u5B9E\u73B0\u4E5F\u662F\u53EA log\n log.error(`\u91CD\u8F7D\u914D\u7F6E\u5931\u8D25: ${(e as Error).message}`);\n }\n}\n"],
5
- "mappings": "AASA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,WAAW;AACpB,SAAS,oBAAoB;AAItB,MAAM,WAAmB;AACzB,MAAM,WAAmB,WAAW,UAAU,gBAAgB;AAErE,SAAS,cAAc,KAA8C;AAEnE,QAAM,SAAS,EAAE,GAAG,mBAAmB,GAAG,IAAI;AAC9C,QAAM,WAAW,gBAAgB,MAAM;AACvC,SAAO;AAAA,IACL,YAAY,SAAS,eAAe;AAAA,IACpC,YAAY,SAAS,OAAO,SAAS,cAAc,kBAAkB,cAAc,IAAI,GAAG,EAAE;AAAA,IAC5F,aAAa,OAAO,SAAS,eAAe,kBAAkB,eAAe,GAAG;AAAA,IAChF,mBAAmB,OAAO,SAAS,qBAAqB,kBAAkB,qBAAqB,EAAE;AAAA,IACjG,SAAS,OAAO,SAAS,WAAW,WAAW;AAAA,IAC/C,SAAS,SAAS,OAAO,SAAS,WAAW,MAAM,GAAG,EAAE;AAAA,IACxD,eAAe,OAAO,SAAS,iBAAiB,kBAAkB,iBAAiB,MAAM;AAAA,IACzF,GAAG;AAAA,EACL;AACF;AAEA,SAAS,eAA+B;AACtC,QAAM,MAAM;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA,EAAE,GAAG,kBAAkB;AAAA,EACzB;AACA,SAAO,cAAc,GAA8B;AACrD;AAGO,SAAS,oBAAoC;AAClD,MAAI;AACF,WAAO,aAAa;AAAA,EACtB,SAAS,GAAG;AACV,QAAI,MAAM,yCAAW,QAAQ,KAAM,EAAY,OAAO,EAAE;AACxD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAMO,SAAS,WAAW,KAA2B;AACpD,MAAI;AACF,UAAM,QAAQ,aAAa;AAC3B,WAAO,OAAO,KAAK,KAAK;AAAA,EAC1B,SAAS,GAAG;AAEV,QAAI,MAAM,yCAAY,EAAY,OAAO,EAAE;AAAA,EAC7C;AACF;",
4
+ "sourcesContent": ["/**\n * config.ts \u2014 QQ \u6865\u63A5\u670D\u52A1\u914D\u7F6E\u52A0\u8F7D\u4E0E\u70ED\u91CD\u8F7D\u7BA1\u7406\n *\n * \u914D\u7F6E\u6E90\uFF1A`configs/qq_config.json`\n * \u6838\u5FC3\u673A\u5236\uFF1A\n * - \u8FDB\u7A0B\u542F\u52A8\u65F6\u8C03\u7528 `loadInitialConfig()` \u52A0\u8F7D\u5E76\u6821\u9A8C\u914D\u7F6E\uFF1B\u82E5\u914D\u7F6E\u6587\u4EF6\u7F3A\u5931\u6216\u635F\u574F\u5219\u9000\u51FA\n * - \u7F3A\u7701\u5B57\u6BB5\u81EA\u52A8\u56DE\u9000\u81F3 SDK `DEFAULT_QQ_CONFIG` \u6743\u5A01\u9ED8\u8BA4\u503C\n * - \u652F\u6301 `reloadInto(cfg)` \u5C31\u5730\u5408\u5E76\u66F4\u65B0\u8FD0\u884C\u65F6\u5BF9\u8C61\uFF0C\u4FDD\u6301\u5185\u5B58\u5F15\u7528\u4E00\u81F4\n */\n\nimport {\n configPath,\n DEFAULT_QQ_CONFIG,\n loadEnsuredConfig,\n stripConfigMeta,\n} from \"@sfmc-bds/sdk/node/config\";\nimport { log } from \"./log.js\";\nimport { PROJECT_ROOT } from \"./project-root.js\";\nimport type { QQBridgeConfig } from \"./types.js\";\n\n/** \u7EDF\u4E00\u901A\u8FC7 SDK \u89E3\u6790\u9879\u76EE\u6839\u76EE\u5F55\uFF08SFMC_ROOT \u4F18\u5148\u4E8E\u4E0A\u6EAF\u67E5\u627E\uFF09\u3002 */\nexport const ROOT_DIR: string = PROJECT_ROOT;\nexport const CFG_PATH: string = configPath(ROOT_DIR, \"qq_config.json\");\n\nfunction applyDefaults(raw: Partial<QQBridgeConfig>): QQBridgeConfig {\n /* \u4EE5 SDK DEFAULT_QQ_CONFIG \u4E3A\u552F\u4E00\u7F3A\u7701\u6743\u5A01\uFF08DRY/LSP\uFF09\uFF0C\u518D\u53E0\u8FD0\u884C\u65F6\u6D3E\u751F\u5B57\u6BB5 */\n const merged = { ...DEFAULT_QQ_CONFIG, ...raw } as Partial<QQBridgeConfig> & Record<string, unknown>;\n const stripped = stripConfigMeta(merged);\n const backendRaw = String(stripped.qq_backend ?? DEFAULT_QQ_CONFIG.qq_backend ?? \"official\");\n const qq_backend = backendRaw === \"llbot\" ? (\"llbot\" as const) : (\"official\" as const);\n return {\n ...stripped,\n qq_enabled: stripped.qq_enabled !== false,\n qq_backend,\n qq_app_id: String(stripped.qq_app_id ?? DEFAULT_QQ_CONFIG.qq_app_id ?? \"\"),\n qq_app_secret: String(stripped.qq_app_secret ?? DEFAULT_QQ_CONFIG.qq_app_secret ?? \"\"),\n qq_sandbox: stripped.qq_sandbox === true,\n qq_group_openid: String(stripped.qq_group_openid ?? DEFAULT_QQ_CONFIG.qq_group_openid ?? \"\"),\n qq_group_panel_id: String(stripped.qq_group_panel_id ?? DEFAULT_QQ_CONFIG.qq_group_panel_id ?? \"\"),\n qq_sync_menu_panel: stripped.qq_sync_menu_panel !== false,\n qq_admin_openids: Array.isArray(stripped.qq_admin_openids)\n ? (stripped.qq_admin_openids as unknown[]).map((x) => String(x).trim()).filter(Boolean)\n : [],\n qq_ws_port: parseInt(String(stripped.qq_ws_port ?? DEFAULT_QQ_CONFIG.qq_ws_port ?? 3002), 10),\n qq_group_id: String(stripped.qq_group_id ?? DEFAULT_QQ_CONFIG.qq_group_id ?? \"0\"),\n bridge_channel_id: String(stripped.bridge_channel_id ?? DEFAULT_QQ_CONFIG.bridge_channel_id ?? \"\"),\n db_host: String(stripped.db_host ?? \"127.0.0.1\"),\n db_port: parseInt(String(stripped.db_port ?? \"3001\"), 10),\n mctoqq_prefix: String(stripped.mctoqq_prefix ?? DEFAULT_QQ_CONFIG.mctoqq_prefix ?? \"[MC]\"),\n };\n}\n\nfunction readFromDisk(): QQBridgeConfig {\n const raw = loadEnsuredConfig(\n ROOT_DIR,\n \"qq_config.json\",\n \"qq_config\",\n { ...DEFAULT_QQ_CONFIG } as Record<string, unknown>\n );\n return applyDefaults(raw as Partial<QQBridgeConfig>);\n}\n\n/**\n * \u8FDB\u7A0B\u542F\u52A8\u65F6\u52A0\u8F7D\u521D\u59CB\u914D\u7F6E\u3002\n *\n * @returns \u89C4\u6574\u5316\u540E\u7684 QQ \u6865\u63A5\u914D\u7F6E\u5BF9\u8C61\u3002\n */\nexport function loadInitialConfig(): QQBridgeConfig {\n try {\n return readFromDisk();\n } catch (e) {\n log.error(`\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E: ${CFG_PATH}: ${(e as Error).message}`);\n process.exit(1);\n }\n}\n\n/**\n * \u91CD\u65B0\u4ECE\u78C1\u76D8\u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\uFF0C\u5E76\u5C31\u5730\u5408\u5E76\u5230\u5F53\u524D\u914D\u7F6E\u5BF9\u8C61\u4E2D\uFF08\u4FDD\u6301\u8FD0\u884C\u65F6\u5BF9\u8C61\u5F15\u7528\u4E0D\u53D8\uFF09\u3002\n *\n * @param cfg \u9700\u540C\u6B65\u66F4\u65B0\u7684\u76EE\u6807\u914D\u7F6E\u5BF9\u8C61\u3002\n */\nexport function reloadInto(cfg: QQBridgeConfig): void {\n try {\n const fresh = readFromDisk();\n Object.assign(cfg, fresh);\n } catch (e) {\n log.error(`\u91CD\u8F7D\u914D\u7F6E\u5931\u8D25: ${(e as Error).message}`);\n }\n}\n\n"],
5
+ "mappings": "AAUA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,WAAW;AACpB,SAAS,oBAAoB;AAItB,MAAM,WAAmB;AACzB,MAAM,WAAmB,WAAW,UAAU,gBAAgB;AAErE,SAAS,cAAc,KAA8C;AAEnE,QAAM,SAAS,EAAE,GAAG,mBAAmB,GAAG,IAAI;AAC9C,QAAM,WAAW,gBAAgB,MAAM;AACvC,QAAM,aAAa,OAAO,SAAS,cAAc,kBAAkB,cAAc,UAAU;AAC3F,QAAM,aAAa,eAAe,UAAW,UAAqB;AAClE,SAAO;AAAA,IACL,GAAG;AAAA,IACH,YAAY,SAAS,eAAe;AAAA,IACpC;AAAA,IACA,WAAW,OAAO,SAAS,aAAa,kBAAkB,aAAa,EAAE;AAAA,IACzE,eAAe,OAAO,SAAS,iBAAiB,kBAAkB,iBAAiB,EAAE;AAAA,IACrF,YAAY,SAAS,eAAe;AAAA,IACpC,iBAAiB,OAAO,SAAS,mBAAmB,kBAAkB,mBAAmB,EAAE;AAAA,IAC3F,mBAAmB,OAAO,SAAS,qBAAqB,kBAAkB,qBAAqB,EAAE;AAAA,IACjG,oBAAoB,SAAS,uBAAuB;AAAA,IACpD,kBAAkB,MAAM,QAAQ,SAAS,gBAAgB,IACpD,SAAS,iBAA+B,IAAI,CAAC,MAAM,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,IACpF,CAAC;AAAA,IACL,YAAY,SAAS,OAAO,SAAS,cAAc,kBAAkB,cAAc,IAAI,GAAG,EAAE;AAAA,IAC5F,aAAa,OAAO,SAAS,eAAe,kBAAkB,eAAe,GAAG;AAAA,IAChF,mBAAmB,OAAO,SAAS,qBAAqB,kBAAkB,qBAAqB,EAAE;AAAA,IACjG,SAAS,OAAO,SAAS,WAAW,WAAW;AAAA,IAC/C,SAAS,SAAS,OAAO,SAAS,WAAW,MAAM,GAAG,EAAE;AAAA,IACxD,eAAe,OAAO,SAAS,iBAAiB,kBAAkB,iBAAiB,MAAM;AAAA,EAC3F;AACF;AAEA,SAAS,eAA+B;AACtC,QAAM,MAAM;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA,EAAE,GAAG,kBAAkB;AAAA,EACzB;AACA,SAAO,cAAc,GAA8B;AACrD;AAOO,SAAS,oBAAoC;AAClD,MAAI;AACF,WAAO,aAAa;AAAA,EACtB,SAAS,GAAG;AACV,QAAI,MAAM,yCAAW,QAAQ,KAAM,EAAY,OAAO,EAAE;AACxD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAOO,SAAS,WAAW,KAA2B;AACpD,MAAI;AACF,UAAM,QAAQ,aAAa;AAC3B,WAAO,OAAO,KAAK,KAAK;AAAA,EAC1B,SAAS,GAAG;AACV,QAAI,MAAM,yCAAY,EAAY,OAAO,EAAE;AAAA,EAC7C;AACF;",
6
6
  "names": []
7
7
  }
package/dist/console.js CHANGED
@@ -1,48 +1,69 @@
1
1
  import { createInterface } from "node:readline";
2
2
  import { log } from "./log.js";
3
3
  import { reloadInto } from "./config.js";
4
- function showHelp() {
4
+ function showHelp(backend) {
5
5
  console.log("\u53EF\u7528\u547D\u4EE4:");
6
- console.log(" help \u2014 \u663E\u793A\u672C\u5E2E\u52A9");
7
- console.log(" reload \u2014 \u91CD\u65B0\u8BFB\u53D6 qq_config.json");
8
- console.log(" status \u2014 \u663E\u793A\u5F53\u524D\u72B6\u6001");
9
- console.log(" stop \u2014 \u505C\u6B62\u670D\u52A1");
6
+ console.log(" help \u2014 \u663E\u793A\u672C\u5E2E\u52A9");
7
+ console.log(" reload \u2014 \u91CD\u65B0\u8BFB\u53D6 qq_config.json");
8
+ console.log(" status \u2014 \u663E\u793A\u5F53\u524D\u72B6\u6001");
9
+ if (backend === "official") {
10
+ console.log(" sync-menu \u2014 \u91CD\u63A8\u5B98\u65B9\u81EA\u5B9A\u4E49\u83DC\u5355 / \u7FA4\u6307\u4EE4\u9762\u677F");
11
+ }
12
+ console.log(" stop \u2014 \u505C\u6B62\u670D\u52A1");
10
13
  }
11
14
  function showStatus(opts) {
12
- const { config: cfg, initialEnabled, wsPort, botSelfIdRef } = opts;
15
+ const { config: cfg, initialEnabled, wsPort, botSelfIdRef, backend } = opts;
13
16
  console.log("=== QQBridge \u72B6\u6001 ===");
14
17
  console.log(` \u542F\u7528: ${initialEnabled}`);
15
- console.log(` WS \u7AEF\u53E3: ${wsPort}`);
16
- console.log(` LLBot HTTP: (db-server \u76F4\u8FDE,\u4E0D\u7ECF\u8FC7\u672C\u8FDB\u7A0B)`);
17
- console.log(` QQ \u7FA4: ${cfg.qq_group_id || "\u672A\u914D\u7F6E"}`);
18
+ console.log(` \u540E\u7AEF: ${backend}`);
19
+ if (backend === "official") {
20
+ console.log(` \u6C99\u7BB1: ${cfg.qq_sandbox}`);
21
+ console.log(` AppID: ${cfg.qq_app_id || "\u672A\u914D\u7F6E"}`);
22
+ console.log(` \u7FA4 openid: ${cfg.qq_group_openid || "\u672A\u914D\u7F6E"}`);
23
+ console.log(` \u9762\u677F id: ${cfg.qq_group_panel_id || "\u672A\u914D\u7F6E"}`);
24
+ console.log(` sync \u83DC\u5355: ${cfg.qq_sync_menu_panel !== false}`);
25
+ } else {
26
+ console.log(` WS \u7AEF\u53E3: ${wsPort}`);
27
+ console.log(` QQ \u7FA4\u53F7: ${cfg.qq_group_id || "\u672A\u914D\u7F6E"}`);
28
+ console.log(` Bot self_id: ${botSelfIdRef.value || "\u672A\u6355\u83B7"}`);
29
+ }
18
30
  console.log(` Channel: ${cfg.bridge_channel_id || "\u672A\u914D\u7F6E"}`);
19
31
  console.log(` db-server: ${cfg.db_host}:${cfg.db_port}`);
20
- console.log(` Bot self_id: ${botSelfIdRef.value || "\u672A\u6355\u83B7"}`);
21
32
  console.log(` MC\u2192QQ \u524D\u7F00: ${cfg.mctoqq_prefix}`);
33
+ console.log(` MC\u2192QQ \u51FA\u7AD9: (\u7531 db-server \u6309 qq_backend \u76F4\u8FDE)`);
22
34
  }
23
35
  function handleCommand(line, opts, rl) {
24
36
  const cmd = line.trim().toLowerCase();
25
37
  switch (cmd) {
26
38
  case "help":
27
- showHelp();
39
+ showHelp(opts.backend);
28
40
  return false;
29
41
  case "reload": {
30
- const before = {
31
- qq_group_id: opts.config.qq_group_id,
32
- bridge_channel_id: opts.config.bridge_channel_id,
33
- db_host: opts.config.db_host,
34
- db_port: opts.config.db_port
35
- };
36
42
  reloadInto(opts.config);
37
43
  log.info(
38
- `\u914D\u7F6E\u5DF2\u91CD\u8F7D (qq_group_id=${opts.config.qq_group_id}, channel=${opts.config.bridge_channel_id}, db=${opts.config.db_host}:${opts.config.db_port})`
44
+ `\u914D\u7F6E\u5DF2\u91CD\u8F7D (backend=${opts.config.qq_backend}, channel=${opts.config.bridge_channel_id}, db=${opts.config.db_host}:${opts.config.db_port})`
39
45
  );
40
- void before;
46
+ log.warn("reload \u53EA\u66F4\u65B0\u5185\u5B58\u914D\u7F6E\u5F15\u7528\uFF1B\u5207\u6362 backend / AppID \u9700\u91CD\u542F\u8FDB\u7A0B");
41
47
  return false;
42
48
  }
43
49
  case "status":
44
50
  showStatus(opts);
45
51
  return false;
52
+ case "sync-menu": {
53
+ if (opts.backend !== "official") {
54
+ log.info("sync-menu \u4EC5 official \u540E\u7AEF\u53EF\u7528");
55
+ return false;
56
+ }
57
+ if (!opts.onSyncMenu) {
58
+ log.warn("sync-menu \u672A\u63A5\u7EBF");
59
+ return false;
60
+ }
61
+ void opts.onSyncMenu().then(
62
+ () => log.info("sync-menu \u5B8C\u6210"),
63
+ (e) => log.warn(`sync-menu \u5931\u8D25: ${e.message}`)
64
+ );
65
+ return false;
66
+ }
46
67
  case "stop":
47
68
  case "exit":
48
69
  case "quit":
@@ -50,7 +71,6 @@ function handleCommand(line, opts, rl) {
50
71
  rl.close();
51
72
  process.exit(0);
52
73
  return true;
53
- // 不可达,但满足 noImplicitReturns
54
74
  case "":
55
75
  return false;
56
76
  default:
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/console.ts"],
4
- "sourcesContent": ["/**\n * console.ts \u2014 \u4EA4\u4E92\u5F0F\u63A7\u5236\u53F0 (readline)\n *\n * \u547D\u4EE4:\n * help \u663E\u793A\u5E2E\u52A9\n * reload \u91CD\u65B0\u8BFB\u53D6 configs/qq_config.json\n * status \u663E\u793A\u5F53\u524D\u72B6\u6001\n * stop \u9000\u51FA\u8FDB\u7A0B\n *\n * \u884C\u4E3A\u4E0E\u65E7\u5B9E\u73B0\u5B8C\u5168\u4E00\u81F4\u3002\n */\n\nimport { createInterface, type Interface as RLInterface } from \"node:readline\";\nimport { log } from \"./log.js\";\nimport { reloadInto } from \"./config.js\";\nimport type { QQBridgeConfig } from \"./types.js\";\n\nexport interface ConsoleOptions {\n config: QQBridgeConfig;\n initialEnabled: boolean;\n wsPort: number;\n botSelfIdRef: { value: string | null };\n}\n\nfunction showHelp(): void {\n console.log(\"\u53EF\u7528\u547D\u4EE4:\");\n console.log(\" help \u2014 \u663E\u793A\u672C\u5E2E\u52A9\");\n console.log(\" reload \u2014 \u91CD\u65B0\u8BFB\u53D6 qq_config.json\");\n console.log(\" status \u2014 \u663E\u793A\u5F53\u524D\u72B6\u6001\");\n console.log(\" stop \u2014 \u505C\u6B62\u670D\u52A1\");\n}\n\nfunction showStatus(opts: ConsoleOptions): void {\n const { config: cfg, initialEnabled, wsPort, botSelfIdRef } = opts;\n console.log(\"=== QQBridge \u72B6\u6001 ===\");\n console.log(` \u542F\u7528: ${initialEnabled}`);\n console.log(` WS \u7AEF\u53E3: ${wsPort}`);\n console.log(` LLBot HTTP: (db-server \u76F4\u8FDE,\u4E0D\u7ECF\u8FC7\u672C\u8FDB\u7A0B)`);\n console.log(` QQ \u7FA4: ${cfg.qq_group_id || \"\u672A\u914D\u7F6E\"}`);\n console.log(` Channel: ${cfg.bridge_channel_id || \"\u672A\u914D\u7F6E\"}`);\n console.log(` db-server: ${cfg.db_host}:${cfg.db_port}`);\n console.log(` Bot self_id: ${botSelfIdRef.value || \"\u672A\u6355\u83B7\"}`);\n console.log(` MC\u2192QQ \u524D\u7F00: ${cfg.mctoqq_prefix}`);\n}\n\nfunction handleCommand(line: string, opts: ConsoleOptions, rl: RLInterface): boolean {\n const cmd = line.trim().toLowerCase();\n switch (cmd) {\n case \"help\":\n showHelp();\n return false;\n case \"reload\": {\n const before = {\n qq_group_id: opts.config.qq_group_id,\n bridge_channel_id: opts.config.bridge_channel_id,\n db_host: opts.config.db_host,\n db_port: opts.config.db_port,\n };\n reloadInto(opts.config);\n log.info(\n `\u914D\u7F6E\u5DF2\u91CD\u8F7D (qq_group_id=${opts.config.qq_group_id}, channel=${opts.config.bridge_channel_id}, db=${opts.config.db_host}:${opts.config.db_port})`\n );\n // \u9759\u9ED8\u6D88\u8D39\u672A\u4F7F\u7528\u53D8\u91CF\u8B66\u544A\n void before;\n return false;\n }\n case \"status\":\n showStatus(opts);\n return false;\n case \"stop\":\n case \"exit\":\n case \"quit\":\n log.info(\"\u6B63\u5728\u505C\u6B62...\");\n rl.close();\n process.exit(0);\n return true; // \u4E0D\u53EF\u8FBE,\u4F46\u6EE1\u8DB3 noImplicitReturns\n case \"\":\n return false;\n default:\n log.info(`\u672A\u77E5\u547D\u4EE4: ${cmd} (\u8F93\u5165 help \u67E5\u770B\u5E2E\u52A9)`);\n return false;\n }\n}\n\nexport function startConsole(opts: ConsoleOptions): void {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.on(\"line\", (line) => {\n handleCommand(line, opts, rl);\n });\n}\n"],
5
- "mappings": "AAYA,SAAS,uBAAsD;AAC/D,SAAS,WAAW;AACpB,SAAS,kBAAkB;AAU3B,SAAS,WAAiB;AACxB,UAAQ,IAAI,2BAAO;AACnB,UAAQ,IAAI,iDAAmB;AAC/B,UAAQ,IAAI,0DAAiC;AAC7C,UAAQ,IAAI,uDAAoB;AAChC,UAAQ,IAAI,2CAAkB;AAChC;AAEA,SAAS,WAAW,MAA4B;AAC9C,QAAM,EAAE,QAAQ,KAAK,gBAAgB,QAAQ,aAAa,IAAI;AAC9D,UAAQ,IAAI,+BAAqB;AACjC,UAAQ,IAAI,0BAAgB,cAAc,EAAE;AAC5C,UAAQ,IAAI,0BAAgB,MAAM,EAAE;AACpC,UAAQ,IAAI,8EAAsC;AAClD,UAAQ,IAAI,sBAAiB,IAAI,eAAe,oBAAK,EAAE;AACvD,UAAQ,IAAI,kBAAkB,IAAI,qBAAqB,oBAAK,EAAE;AAC9D,UAAQ,IAAI,kBAAkB,IAAI,OAAO,IAAI,IAAI,OAAO,EAAE;AAC1D,UAAQ,IAAI,kBAAkB,aAAa,SAAS,oBAAK,EAAE;AAC3D,UAAQ,IAAI,+BAAgB,IAAI,aAAa,EAAE;AACjD;AAEA,SAAS,cAAc,MAAc,MAAsB,IAA0B;AACnF,QAAM,MAAM,KAAK,KAAK,EAAE,YAAY;AACpC,UAAQ,KAAK;AAAA,IACX,KAAK;AACH,eAAS;AACT,aAAO;AAAA,IACT,KAAK,UAAU;AACb,YAAM,SAAS;AAAA,QACb,aAAa,KAAK,OAAO;AAAA,QACzB,mBAAmB,KAAK,OAAO;AAAA,QAC/B,SAAS,KAAK,OAAO;AAAA,QACrB,SAAS,KAAK,OAAO;AAAA,MACvB;AACA,iBAAW,KAAK,MAAM;AACtB,UAAI;AAAA,QACF,+CAAsB,KAAK,OAAO,WAAW,aAAa,KAAK,OAAO,iBAAiB,QAAQ,KAAK,OAAO,OAAO,IAAI,KAAK,OAAO,OAAO;AAAA,MAC3I;AAEA,WAAK;AACL,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AACH,iBAAW,IAAI;AACf,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,UAAI,KAAK,6BAAS;AAClB,SAAG,MAAM;AACT,cAAQ,KAAK,CAAC;AACd,aAAO;AAAA;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,UAAI,KAAK,6BAAS,GAAG,+CAAiB;AACtC,aAAO;AAAA,EACX;AACF;AAEO,SAAS,aAAa,MAA4B;AACvD,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,KAAG,GAAG,QAAQ,CAAC,SAAS;AACtB,kBAAc,MAAM,MAAM,EAAE;AAAA,EAC9B,CAAC;AACH;",
4
+ "sourcesContent": ["/**\n * console.ts \u2014 \u4EA4\u4E92\u5F0F\u63A7\u5236\u53F0 (readline)\n *\n * \u547D\u4EE4:\n * help \u663E\u793A\u5E2E\u52A9\n * reload \u91CD\u65B0\u8BFB\u53D6 configs/qq_config.json\n * status \u663E\u793A\u5F53\u524D\u72B6\u6001\n * sync-menu \u91CD\u63A8\u5B98\u65B9\u83DC\u5355/\u6307\u4EE4\u9762\u677F\uFF08\u4EC5 official\uFF09\n * stop \u9000\u51FA\u8FDB\u7A0B\n */\n\nimport { createInterface, type Interface as RLInterface } from \"node:readline\";\nimport { log } from \"./log.js\";\nimport { reloadInto } from \"./config.js\";\nimport type { QQBridgeConfig } from \"./types.js\";\n\nexport interface ConsoleOptions {\n config: QQBridgeConfig;\n initialEnabled: boolean;\n wsPort: number;\n botSelfIdRef: { value: string | null };\n backend: \"official\" | \"llbot\";\n onSyncMenu?: () => Promise<void>;\n}\n\nfunction showHelp(backend: \"official\" | \"llbot\"): void {\n console.log(\"\u53EF\u7528\u547D\u4EE4:\");\n console.log(\" help \u2014 \u663E\u793A\u672C\u5E2E\u52A9\");\n console.log(\" reload \u2014 \u91CD\u65B0\u8BFB\u53D6 qq_config.json\");\n console.log(\" status \u2014 \u663E\u793A\u5F53\u524D\u72B6\u6001\");\n if (backend === \"official\") {\n console.log(\" sync-menu \u2014 \u91CD\u63A8\u5B98\u65B9\u81EA\u5B9A\u4E49\u83DC\u5355 / \u7FA4\u6307\u4EE4\u9762\u677F\");\n }\n console.log(\" stop \u2014 \u505C\u6B62\u670D\u52A1\");\n}\n\nfunction showStatus(opts: ConsoleOptions): void {\n const { config: cfg, initialEnabled, wsPort, botSelfIdRef, backend } = opts;\n console.log(\"=== QQBridge \u72B6\u6001 ===\");\n console.log(` \u542F\u7528: ${initialEnabled}`);\n console.log(` \u540E\u7AEF: ${backend}`);\n if (backend === \"official\") {\n console.log(` \u6C99\u7BB1: ${cfg.qq_sandbox}`);\n console.log(` AppID: ${cfg.qq_app_id || \"\u672A\u914D\u7F6E\"}`);\n console.log(` \u7FA4 openid: ${cfg.qq_group_openid || \"\u672A\u914D\u7F6E\"}`);\n console.log(` \u9762\u677F id: ${cfg.qq_group_panel_id || \"\u672A\u914D\u7F6E\"}`);\n console.log(` sync \u83DC\u5355: ${cfg.qq_sync_menu_panel !== false}`);\n } else {\n console.log(` WS \u7AEF\u53E3: ${wsPort}`);\n console.log(` QQ \u7FA4\u53F7: ${cfg.qq_group_id || \"\u672A\u914D\u7F6E\"}`);\n console.log(` Bot self_id: ${botSelfIdRef.value || \"\u672A\u6355\u83B7\"}`);\n }\n console.log(` Channel: ${cfg.bridge_channel_id || \"\u672A\u914D\u7F6E\"}`);\n console.log(` db-server: ${cfg.db_host}:${cfg.db_port}`);\n console.log(` MC\u2192QQ \u524D\u7F00: ${cfg.mctoqq_prefix}`);\n console.log(` MC\u2192QQ \u51FA\u7AD9: (\u7531 db-server \u6309 qq_backend \u76F4\u8FDE)`);\n}\n\nfunction handleCommand(line: string, opts: ConsoleOptions, rl: RLInterface): boolean {\n const cmd = line.trim().toLowerCase();\n switch (cmd) {\n case \"help\":\n showHelp(opts.backend);\n return false;\n case \"reload\": {\n reloadInto(opts.config);\n log.info(\n `\u914D\u7F6E\u5DF2\u91CD\u8F7D (backend=${opts.config.qq_backend}, channel=${opts.config.bridge_channel_id}, db=${opts.config.db_host}:${opts.config.db_port})`\n );\n log.warn(\"reload \u53EA\u66F4\u65B0\u5185\u5B58\u914D\u7F6E\u5F15\u7528\uFF1B\u5207\u6362 backend / AppID \u9700\u91CD\u542F\u8FDB\u7A0B\");\n return false;\n }\n case \"status\":\n showStatus(opts);\n return false;\n case \"sync-menu\": {\n if (opts.backend !== \"official\") {\n log.info(\"sync-menu \u4EC5 official \u540E\u7AEF\u53EF\u7528\");\n return false;\n }\n if (!opts.onSyncMenu) {\n log.warn(\"sync-menu \u672A\u63A5\u7EBF\");\n return false;\n }\n void opts.onSyncMenu().then(\n () => log.info(\"sync-menu \u5B8C\u6210\"),\n (e) => log.warn(`sync-menu \u5931\u8D25: ${(e as Error).message}`)\n );\n return false;\n }\n case \"stop\":\n case \"exit\":\n case \"quit\":\n log.info(\"\u6B63\u5728\u505C\u6B62...\");\n rl.close();\n process.exit(0);\n return true;\n case \"\":\n return false;\n default:\n log.info(`\u672A\u77E5\u547D\u4EE4: ${cmd} (\u8F93\u5165 help \u67E5\u770B\u5E2E\u52A9)`);\n return false;\n }\n}\n\nexport function startConsole(opts: ConsoleOptions): void {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.on(\"line\", (line) => {\n handleCommand(line, opts, rl);\n });\n}\n"],
5
+ "mappings": "AAWA,SAAS,uBAAsD;AAC/D,SAAS,WAAW;AACpB,SAAS,kBAAkB;AAY3B,SAAS,SAAS,SAAqC;AACrD,UAAQ,IAAI,2BAAO;AACnB,UAAQ,IAAI,oDAAsB;AAClC,UAAQ,IAAI,6DAAoC;AAChD,UAAQ,IAAI,0DAAuB;AACnC,MAAI,YAAY,YAAY;AAC1B,YAAQ,IAAI,6GAAkC;AAAA,EAChD;AACA,UAAQ,IAAI,8CAAqB;AACnC;AAEA,SAAS,WAAW,MAA4B;AAC9C,QAAM,EAAE,QAAQ,KAAK,gBAAgB,QAAQ,cAAc,QAAQ,IAAI;AACvE,UAAQ,IAAI,+BAAqB;AACjC,UAAQ,IAAI,0BAAgB,cAAc,EAAE;AAC5C,UAAQ,IAAI,0BAAgB,OAAO,EAAE;AACrC,MAAI,YAAY,YAAY;AAC1B,YAAQ,IAAI,0BAAgB,IAAI,UAAU,EAAE;AAC5C,YAAQ,IAAI,kBAAkB,IAAI,aAAa,oBAAK,EAAE;AACtD,YAAQ,IAAI,sBAAiB,IAAI,mBAAmB,oBAAK,EAAE;AAC3D,YAAQ,IAAI,0BAAgB,IAAI,qBAAqB,oBAAK,EAAE;AAC5D,YAAQ,IAAI,0BAAgB,IAAI,uBAAuB,KAAK,EAAE;AAAA,EAChE,OAAO;AACL,YAAQ,IAAI,0BAAgB,MAAM,EAAE;AACpC,YAAQ,IAAI,0BAAgB,IAAI,eAAe,oBAAK,EAAE;AACtD,YAAQ,IAAI,kBAAkB,aAAa,SAAS,oBAAK,EAAE;AAAA,EAC7D;AACA,UAAQ,IAAI,kBAAkB,IAAI,qBAAqB,oBAAK,EAAE;AAC9D,UAAQ,IAAI,kBAAkB,IAAI,OAAO,IAAI,IAAI,OAAO,EAAE;AAC1D,UAAQ,IAAI,+BAAgB,IAAI,aAAa,EAAE;AAC/C,UAAQ,IAAI,+EAA4C;AAC1D;AAEA,SAAS,cAAc,MAAc,MAAsB,IAA0B;AACnF,QAAM,MAAM,KAAK,KAAK,EAAE,YAAY;AACpC,UAAQ,KAAK;AAAA,IACX,KAAK;AACH,eAAS,KAAK,OAAO;AACrB,aAAO;AAAA,IACT,KAAK,UAAU;AACb,iBAAW,KAAK,MAAM;AACtB,UAAI;AAAA,QACF,2CAAkB,KAAK,OAAO,UAAU,aAAa,KAAK,OAAO,iBAAiB,QAAQ,KAAK,OAAO,OAAO,IAAI,KAAK,OAAO,OAAO;AAAA,MACtI;AACA,UAAI,KAAK,gIAA2C;AACpD,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AACH,iBAAW,IAAI;AACf,aAAO;AAAA,IACT,KAAK,aAAa;AAChB,UAAI,KAAK,YAAY,YAAY;AAC/B,YAAI,KAAK,oDAA2B;AACpC,eAAO;AAAA,MACT;AACA,UAAI,CAAC,KAAK,YAAY;AACpB,YAAI,KAAK,8BAAe;AACxB,eAAO;AAAA,MACT;AACA,WAAK,KAAK,WAAW,EAAE;AAAA,QACrB,MAAM,IAAI,KAAK,wBAAc;AAAA,QAC7B,CAAC,MAAM,IAAI,KAAK,2BAAkB,EAAY,OAAO,EAAE;AAAA,MACzD;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,UAAI,KAAK,6BAAS;AAClB,SAAG,MAAM;AACT,cAAQ,KAAK,CAAC;AACd,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,UAAI,KAAK,6BAAS,GAAG,+CAAiB;AACtC,aAAO;AAAA,EACX;AACF;AAEO,SAAS,aAAa,MAA4B;AACvD,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,KAAG,GAAG,QAAQ,CAAC,SAAS;AACtB,kBAAc,MAAM,MAAM,EAAE;AAAA,EAC9B,CAAC;AACH;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/dbserver.ts"],
4
- "sourcesContent": ["/**\n * dbserver.ts \u2014 \u8F6C\u53D1 OneBot \u7FA4\u6D88\u606F\u5230 db-server /api/sfmc/messages\n *\n * \u4E0E\u65E7\u5B9E\u73B0\u4FDD\u6301\u884C\u4E3A\u4E00\u81F4: \u4EC5 POST 200 \u89C6\u4E3A\u6210\u529F,\u5426\u5219\u629B\u9519; \u5931\u8D25\u4E0D\u91CD\u8BD5 (\u4E3B\u6D41\u7A0B\u4E0D\u963B\u585E)\u3002\n * \u8D85\u65F6: \u4E0E db-server \u5185\u90E8 HTTP \u98CE\u683C\u4E00\u81F4,\u4F7F\u7528 req.on(\"timeout\") + req.destroy\u3002\n */\n\nimport { request as httpRequest, type RequestOptions } from \"node:http\";\nimport { log } from \"./log.js\";\nimport type { IncomingChatMessage, PostMessagesBody } from \"./types.js\";\n\nexport interface DBServerConfig {\n host: string;\n port: number;\n channelId: string;\n}\n\nconst REQUEST_TIMEOUT_MS = 5_000;\n\nfunction postJSON<T>(url: URL, body: unknown, timeoutMs: number): Promise<T> {\n const payload = JSON.stringify(body);\n const options: RequestOptions = {\n hostname: url.hostname,\n port: url.port || \"3001\",\n path: url.pathname,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(payload),\n },\n timeout: timeoutMs,\n };\n return new Promise<T>((resolve, reject) => {\n const req = httpRequest(options, (res) => {\n let buf = \"\";\n res.setEncoding(\"utf-8\");\n res.on(\"data\", (chunk: string) => {\n buf += chunk;\n });\n res.on(\"end\", () => {\n const code = res.statusCode ?? 0;\n if (code >= 200 && code < 300) {\n try {\n resolve(JSON.parse(buf) as T);\n } catch {\n // \u5141\u8BB8\u8FD4\u56DE\u975E JSON: \u4ECD\u89C6\u4E3A\u6210\u529F,\u4F5C\u4E3A void \u629B\u51FA\n resolve(buf as unknown as T);\n }\n } else {\n reject(new Error(`db-server ${code}: ${buf.slice(0, 200)}`));\n }\n });\n });\n req.on(\"timeout\", () => {\n req.destroy(new Error(`db-server \u8D85\u65F6 ${timeoutMs}ms`));\n });\n req.on(\"error\", reject);\n req.write(payload);\n req.end();\n });\n}\n\n/**\n * \u6784\u9020\u4E00\u6761 IncomingChatMessage \u5E76 POST \u5230 db-server\u3002\n * fromid \u5F62\u5982 `qq_<user_id>`,\u4E0E\u65E7\u5B9E\u73B0\u4FDD\u6301\u5B8C\u5168\u4E00\u81F4\u3002\n */\nexport async function forwardGroupMessage(\n cfg: DBServerConfig,\n fromId: string,\n fromName: string,\n content: string,\n now: number = Date.now()\n): Promise<void> {\n const message: IncomingChatMessage = {\n id: `${fromId}_${now}`,\n channelId: cfg.channelId,\n fromid: fromId,\n fromName,\n type: \"text\",\n content,\n showTimestamp: true,\n timestamp: now,\n };\n const body: PostMessagesBody = { messages: [message] };\n const url = new URL(`http://${cfg.host}:${cfg.port}/api/sfmc/messages`);\n await postJSON<unknown>(url, body, REQUEST_TIMEOUT_MS);\n}\n\n/** \u5305\u88C5 try/catch,\u5931\u8D25\u4EC5 log (\u4E0E\u65E7\u5B9E\u73B0\u4E00\u81F4,\u4E0D\u629B\u7ED9\u4E3B\u5FAA\u73AF)\u3002 */\nexport async function tryForward(\n cfg: DBServerConfig,\n fromId: string,\n fromName: string,\n content: string\n): Promise<void> {\n try {\n await forwardGroupMessage(cfg, fromId, fromName, content);\n log.info(`QQ \u2192 MC: ${fromName}: ${content.slice(0, 60)}`);\n } catch (e) {\n log.error(`\u8F6C\u53D1\u5230 db-server \u5931\u8D25: ${(e as Error).message}`);\n }\n}\n"],
5
- "mappings": "AAOA,SAAS,WAAW,mBAAwC;AAC5D,SAAS,WAAW;AASpB,MAAM,qBAAqB;AAE3B,SAAS,SAAY,KAAU,MAAe,WAA+B;AAC3E,QAAM,UAAU,KAAK,UAAU,IAAI;AACnC,QAAM,UAA0B;AAAA,IAC9B,UAAU,IAAI;AAAA,IACd,MAAM,IAAI,QAAQ;AAAA,IAClB,MAAM,IAAI;AAAA,IACV,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,kBAAkB,OAAO,WAAW,OAAO;AAAA,IAC7C;AAAA,IACA,SAAS;AAAA,EACX;AACA,SAAO,IAAI,QAAW,CAAC,SAAS,WAAW;AACzC,UAAM,MAAM,YAAY,SAAS,CAAC,QAAQ;AACxC,UAAI,MAAM;AACV,UAAI,YAAY,OAAO;AACvB,UAAI,GAAG,QAAQ,CAAC,UAAkB;AAChC,eAAO;AAAA,MACT,CAAC;AACD,UAAI,GAAG,OAAO,MAAM;AAClB,cAAM,OAAO,IAAI,cAAc;AAC/B,YAAI,QAAQ,OAAO,OAAO,KAAK;AAC7B,cAAI;AACF,oBAAQ,KAAK,MAAM,GAAG,CAAM;AAAA,UAC9B,QAAQ;AAEN,oBAAQ,GAAmB;AAAA,UAC7B;AAAA,QACF,OAAO;AACL,iBAAO,IAAI,MAAM,aAAa,IAAI,KAAK,IAAI,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC;AAAA,QAC7D;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AACD,QAAI,GAAG,WAAW,MAAM;AACtB,UAAI,QAAQ,IAAI,MAAM,0BAAgB,SAAS,IAAI,CAAC;AAAA,IACtD,CAAC;AACD,QAAI,GAAG,SAAS,MAAM;AACtB,QAAI,MAAM,OAAO;AACjB,QAAI,IAAI;AAAA,EACV,CAAC;AACH;AAMA,eAAsB,oBACpB,KACA,QACA,UACA,SACA,MAAc,KAAK,IAAI,GACR;AACf,QAAM,UAA+B;AAAA,IACnC,IAAI,GAAG,MAAM,IAAI,GAAG;AAAA,IACpB,WAAW,IAAI;AAAA,IACf,QAAQ;AAAA,IACR;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA,eAAe;AAAA,IACf,WAAW;AAAA,EACb;AACA,QAAM,OAAyB,EAAE,UAAU,CAAC,OAAO,EAAE;AACrD,QAAM,MAAM,IAAI,IAAI,UAAU,IAAI,IAAI,IAAI,IAAI,IAAI,oBAAoB;AACtE,QAAM,SAAkB,KAAK,MAAM,kBAAkB;AACvD;AAGA,eAAsB,WACpB,KACA,QACA,UACA,SACe;AACf,MAAI;AACF,UAAM,oBAAoB,KAAK,QAAQ,UAAU,OAAO;AACxD,QAAI,KAAK,iBAAY,QAAQ,KAAK,QAAQ,MAAM,GAAG,EAAE,CAAC,EAAE;AAAA,EAC1D,SAAS,GAAG;AACV,QAAI,MAAM,8CAAsB,EAAY,OAAO,EAAE;AAAA,EACvD;AACF;",
4
+ "sourcesContent": ["/**\n * dbserver.ts \u2014 \u8F6C\u53D1 QQ \u6D88\u606F\u81F3 db-server \u6D88\u606F\u805A\u5408\u7AEF\u70B9\uFF08/api/sfmc/messages\uFF09\n *\n * \u6838\u5FC3\u673A\u5236\uFF1A\n * - \u6D88\u606F\u6807\u51C6\u5316\uFF1A\u5C06\u5916\u90E8\u7FA4\u6D88\u606F\u6784\u9020\u4E3A\u6807\u51C6 `IncomingChatMessage` \u5BF9\u8C61\uFF08\u53D1\u9001\u8005\u6807\u8BC6\u7EDF\u4E00\u52A0\u4E0A `qq_` \u524D\u7F00\uFF09\n * - \u5FEB\u901F\u54CD\u5E94\u4E0E\u5BB9\u9519\uFF1A\u8BF7\u6C42\u8D85\u65F6\uFF085 \u79D2\uFF09\u540E\u81EA\u52A8\u7194\u65AD\uFF1B\u8F6C\u53D1\u5931\u8D25\u4EC5\u8BB0\u5F55\u544A\u8B66\u65E5\u5FD7\uFF0C\u4E0D\u963B\u585E\u4E3B\u4E8B\u4EF6\u5FAA\u73AF\n */\n\nimport { request as httpRequest, type RequestOptions } from \"node:http\";\nimport { log } from \"./log.js\";\nimport type { IncomingChatMessage, PostMessagesBody } from \"./types.js\";\n\nexport interface DBServerConfig {\n host: string;\n port: number;\n channelId: string;\n}\n\nconst REQUEST_TIMEOUT_MS = 5_000;\n\nfunction postJSON<T>(url: URL, body: unknown, timeoutMs: number): Promise<T> {\n const payload = JSON.stringify(body);\n const options: RequestOptions = {\n hostname: url.hostname,\n port: url.port || \"3001\",\n path: url.pathname,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(payload),\n },\n timeout: timeoutMs,\n };\n return new Promise<T>((resolve, reject) => {\n const req = httpRequest(options, (res) => {\n let buf = \"\";\n res.setEncoding(\"utf-8\");\n res.on(\"data\", (chunk: string) => {\n buf += chunk;\n });\n res.on(\"end\", () => {\n const code = res.statusCode ?? 0;\n if (code >= 200 && code < 300) {\n try {\n resolve(JSON.parse(buf) as T);\n } catch {\n // \u5141\u8BB8\u8FD4\u56DE\u975E JSON\uFF1A\u4ECD\u89C6\u4E3A\u6210\u529F\n resolve(buf as unknown as T);\n }\n } else {\n reject(new Error(`db-server ${code}: ${buf.slice(0, 200)}`));\n }\n });\n });\n req.on(\"timeout\", () => {\n req.destroy(new Error(`db-server \u8D85\u65F6 ${timeoutMs}ms`));\n });\n req.on(\"error\", reject);\n req.write(payload);\n req.end();\n });\n}\n\n/**\n * \u6784\u9020\u6807\u51C6\u804A\u5929\u6D88\u606F\u7ED3\u6784\u4F53\u5E76 POST \u53D1\u9001\u81F3 db-server \u6D88\u606F\u7AEF\u70B9\u3002\n *\n * @param cfg db-server \u76EE\u6807\u8FDE\u63A5\u4E0E\u9891\u9053\u914D\u7F6E\u3002\n * @param fromId \u53D1\u9001\u65B9\u552F\u4E00\u6807\u8BC6\u7B26\uFF08\u901A\u5E38\u5F62\u5982 `qq_<user_id>`\uFF09\u3002\n * @param fromName \u53D1\u9001\u65B9\u5C55\u793A\u6635\u79F0\u3002\n * @param content \u6D88\u606F\u7EAF\u6587\u672C\u5185\u5BB9\u3002\n * @param now \u6D88\u606F\u65F6\u95F4\u6233\uFF08\u6BEB\u79D2\uFF0C\u9ED8\u8BA4\u53D6\u5F53\u524D\u65F6\u95F4\uFF09\u3002\n */\nexport async function forwardGroupMessage(\n cfg: DBServerConfig,\n fromId: string,\n fromName: string,\n content: string,\n now: number = Date.now()\n): Promise<void> {\n const message: IncomingChatMessage = {\n id: `${fromId}_${now}`,\n channelId: cfg.channelId,\n fromid: fromId,\n fromName,\n type: \"text\",\n content,\n showTimestamp: true,\n timestamp: now,\n };\n const body: PostMessagesBody = { messages: [message] };\n const url = new URL(`http://${cfg.host}:${cfg.port}/api/sfmc/messages`);\n await postJSON<unknown>(url, body, REQUEST_TIMEOUT_MS);\n}\n\n/**\n * \u5B89\u5168\u8F6C\u53D1\u6D88\u606F\u81F3 db-server\uFF08\u5185\u90E8\u6355\u83B7\u5F02\u5E38\u5E76\u8BB0\u5F55\u65E5\u5FD7\uFF0C\u907F\u514D\u629B\u51FA\u7ED9\u4E3B\u4E8B\u4EF6\u5FAA\u73AF\uFF09\u3002\n *\n * @param cfg db-server \u8FDE\u63A5\u914D\u7F6E\u3002\n * @param fromId \u53D1\u9001\u65B9\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param fromName \u53D1\u9001\u65B9\u6635\u79F0\u3002\n * @param content \u6D88\u606F\u6587\u672C\u5185\u5BB9\u3002\n */\nexport async function tryForward(\n cfg: DBServerConfig,\n fromId: string,\n fromName: string,\n content: string\n): Promise<void> {\n try {\n await forwardGroupMessage(cfg, fromId, fromName, content);\n log.info(`QQ \u2192 MC: ${fromName}: ${content.slice(0, 60)}`);\n } catch (e) {\n log.error(`\u8F6C\u53D1\u5230 db-server \u5931\u8D25: ${(e as Error).message}`);\n }\n}\n\n"],
5
+ "mappings": "AAQA,SAAS,WAAW,mBAAwC;AAC5D,SAAS,WAAW;AASpB,MAAM,qBAAqB;AAE3B,SAAS,SAAY,KAAU,MAAe,WAA+B;AAC3E,QAAM,UAAU,KAAK,UAAU,IAAI;AACnC,QAAM,UAA0B;AAAA,IAC9B,UAAU,IAAI;AAAA,IACd,MAAM,IAAI,QAAQ;AAAA,IAClB,MAAM,IAAI;AAAA,IACV,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,kBAAkB,OAAO,WAAW,OAAO;AAAA,IAC7C;AAAA,IACA,SAAS;AAAA,EACX;AACA,SAAO,IAAI,QAAW,CAAC,SAAS,WAAW;AACzC,UAAM,MAAM,YAAY,SAAS,CAAC,QAAQ;AACxC,UAAI,MAAM;AACV,UAAI,YAAY,OAAO;AACvB,UAAI,GAAG,QAAQ,CAAC,UAAkB;AAChC,eAAO;AAAA,MACT,CAAC;AACD,UAAI,GAAG,OAAO,MAAM;AAClB,cAAM,OAAO,IAAI,cAAc;AAC/B,YAAI,QAAQ,OAAO,OAAO,KAAK;AAC7B,cAAI;AACF,oBAAQ,KAAK,MAAM,GAAG,CAAM;AAAA,UAC9B,QAAQ;AAEN,oBAAQ,GAAmB;AAAA,UAC7B;AAAA,QACF,OAAO;AACL,iBAAO,IAAI,MAAM,aAAa,IAAI,KAAK,IAAI,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC;AAAA,QAC7D;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AACD,QAAI,GAAG,WAAW,MAAM;AACtB,UAAI,QAAQ,IAAI,MAAM,0BAAgB,SAAS,IAAI,CAAC;AAAA,IACtD,CAAC;AACD,QAAI,GAAG,SAAS,MAAM;AACtB,QAAI,MAAM,OAAO;AACjB,QAAI,IAAI;AAAA,EACV,CAAC;AACH;AAWA,eAAsB,oBACpB,KACA,QACA,UACA,SACA,MAAc,KAAK,IAAI,GACR;AACf,QAAM,UAA+B;AAAA,IACnC,IAAI,GAAG,MAAM,IAAI,GAAG;AAAA,IACpB,WAAW,IAAI;AAAA,IACf,QAAQ;AAAA,IACR;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA,eAAe;AAAA,IACf,WAAW;AAAA,EACb;AACA,QAAM,OAAyB,EAAE,UAAU,CAAC,OAAO,EAAE;AACrD,QAAM,MAAM,IAAI,IAAI,UAAU,IAAI,IAAI,IAAI,IAAI,IAAI,oBAAoB;AACtE,QAAM,SAAkB,KAAK,MAAM,kBAAkB;AACvD;AAUA,eAAsB,WACpB,KACA,QACA,UACA,SACe;AACf,MAAI;AACF,UAAM,oBAAoB,KAAK,QAAQ,UAAU,OAAO;AACxD,QAAI,KAAK,iBAAY,QAAQ,KAAK,QAAQ,MAAM,GAAG,EAAE,CAAC,EAAE;AAAA,EAC1D,SAAS,GAAG;AACV,QAAI,MAAM,8CAAsB,EAAY,OAAO,EAAE;AAAA,EACvD;AACF;",
6
6
  "names": []
7
7
  }
package/dist/index.js CHANGED
@@ -1,37 +1,134 @@
1
1
  #!/usr/bin/env node
2
+ import { createLlbotCommandRouter, createOfficialCommandRouter } from "./commands/index.js";
3
+ import { createInteractionRouter } from "./commands/interaction-router.js";
4
+ import { persistPanelIdToConfig, syncMenuAndPanel } from "./commands/menu-panel-sync.js";
2
5
  import { loadInitialConfig } from "./config.js";
3
6
  import { OneBotDispatcher } from "./onebot.js";
4
7
  import { startWsServer } from "./ws-server.js";
5
8
  import { startConsole } from "./console.js";
9
+ import { OfficialAtMessageDispatcher } from "./official/events.js";
10
+ import { startOfficialGateway } from "./official/gateway.js";
11
+ import { installQqRuntimeStatusHooks } from "./runtime-status.js";
6
12
  import { log } from "./log.js";
7
13
  async function main() {
8
14
  const cfg = loadInitialConfig();
15
+ const startedAt = Date.now();
9
16
  if (!cfg.qq_enabled) {
10
17
  log.info("\u5DF2\u7981\u7528 (qq_enabled = false)");
11
18
  process.exit(0);
12
19
  }
13
20
  const botSelfIdRef = { value: null };
14
- const dispatcher = new OneBotDispatcher({
15
- qqGroupId: cfg.qq_group_id,
16
- db: {
17
- host: cfg.db_host,
18
- port: cfg.db_port,
19
- channelId: cfg.bridge_channel_id
21
+ const db = {
22
+ host: cfg.db_host,
23
+ port: cfg.db_port,
24
+ channelId: cfg.bridge_channel_id
25
+ };
26
+ if (cfg.qq_backend === "official") {
27
+ if (!cfg.qq_app_id || !cfg.qq_app_secret) {
28
+ log.error("\u5B98\u65B9\u540E\u7AEF\u9700\u8981\u914D\u7F6E qq_app_id \u4E0E qq_app_secret");
29
+ process.exit(1);
20
30
  }
21
- });
22
- Object.defineProperty(botSelfIdRef, "value", {
23
- get: () => dispatcher.selfId
24
- });
25
- await startWsServer({ port: cfg.qq_ws_port, dispatcher });
26
- log.info(
27
- `\u7B49\u5F85 LLBot \u8FDE\u63A5 (\u4E3B\u7FA4: ${cfg.qq_group_id || "\u672A\u914D\u7F6E"}, channel: ${cfg.bridge_channel_id || "\u672A\u914D\u7F6E"}, db: ${cfg.db_host}:${cfg.db_port})`
28
- );
29
- startConsole({
30
- config: cfg,
31
- initialEnabled: cfg.qq_enabled,
32
- wsPort: cfg.qq_ws_port,
33
- botSelfIdRef
34
- });
31
+ const creds = {
32
+ appId: cfg.qq_app_id,
33
+ appSecret: cfg.qq_app_secret,
34
+ sandbox: cfg.qq_sandbox
35
+ };
36
+ const appId = cfg.qq_app_id;
37
+ const commandRouter = createOfficialCommandRouter(
38
+ creds,
39
+ {
40
+ sandbox: cfg.qq_sandbox,
41
+ appIdHint: appId.length > 8 ? `${appId.slice(0, 4)}\u2026${appId.slice(-4)}` : appId,
42
+ dbHost: cfg.db_host,
43
+ dbPort: cfg.db_port,
44
+ groupOpenid: cfg.qq_group_openid,
45
+ adminOpenids: Array.isArray(cfg.qq_admin_openids) ? cfg.qq_admin_openids : [],
46
+ bridgeChannelId: cfg.bridge_channel_id,
47
+ officialCreds: creds
48
+ },
49
+ startedAt
50
+ );
51
+ const runSyncMenu = async () => {
52
+ if (cfg.qq_sync_menu_panel === false) {
53
+ log.info("\u5DF2\u8DF3\u8FC7 menu/panel sync\uFF08qq_sync_menu_panel=false\uFF09");
54
+ return;
55
+ }
56
+ await syncMenuAndPanel({
57
+ creds,
58
+ registry: commandRouter.registry,
59
+ groupOpenid: cfg.qq_group_openid,
60
+ panelId: String(cfg.qq_group_panel_id ?? ""),
61
+ persistPanelId: (id) => {
62
+ cfg.qq_group_panel_id = id;
63
+ persistPanelIdToConfig(id);
64
+ }
65
+ });
66
+ };
67
+ const dispatcher = new OfficialAtMessageDispatcher({
68
+ groupOpenid: cfg.qq_group_openid,
69
+ db,
70
+ commandRouter
71
+ });
72
+ const interactionRouter = createInteractionRouter({
73
+ creds,
74
+ db: { host: cfg.db_host, port: cfg.db_port },
75
+ adminOpenids: Array.isArray(cfg.qq_admin_openids) ? cfg.qq_admin_openids : []
76
+ });
77
+ await startOfficialGateway({
78
+ creds,
79
+ dispatcher,
80
+ interactionRouter
81
+ });
82
+ void runSyncMenu().catch((e) => log.warn(`menu/panel sync \u5F02\u5E38: ${e.message}`));
83
+ installQqRuntimeStatusHooks("official");
84
+ log.info(
85
+ `\u5B98\u65B9\u540E\u7AEF\u5DF2\u542F\u52A8 (sandbox=${cfg.qq_sandbox}, group_openid=${cfg.qq_group_openid || "\u672A\u914D\u7F6E"}, channel=${cfg.bridge_channel_id || "\u672A\u914D\u7F6E"}, db=${cfg.db_host}:${cfg.db_port})`
86
+ );
87
+ startConsole({
88
+ config: cfg,
89
+ initialEnabled: cfg.qq_enabled,
90
+ wsPort: 0,
91
+ botSelfIdRef,
92
+ backend: "official",
93
+ onSyncMenu: runSyncMenu
94
+ });
95
+ } else {
96
+ const commandRouter = createLlbotCommandRouter(
97
+ {
98
+ host: cfg.llbot_host || "127.0.0.1",
99
+ port: cfg.llbot_port || 3004,
100
+ token: cfg.llbot_token || "",
101
+ groupId: cfg.qq_group_id || "0"
102
+ },
103
+ {
104
+ dbHost: cfg.db_host,
105
+ dbPort: cfg.db_port,
106
+ adminOpenids: Array.isArray(cfg.qq_admin_openids) ? cfg.qq_admin_openids : [],
107
+ bridgeChannelId: cfg.bridge_channel_id
108
+ },
109
+ startedAt
110
+ );
111
+ const dispatcher = new OneBotDispatcher({
112
+ qqGroupId: cfg.qq_group_id,
113
+ db,
114
+ commandRouter
115
+ });
116
+ Object.defineProperty(botSelfIdRef, "value", {
117
+ get: () => dispatcher.selfId
118
+ });
119
+ await startWsServer({ port: cfg.qq_ws_port, dispatcher });
120
+ installQqRuntimeStatusHooks("llbot");
121
+ log.info(
122
+ `LLBot \u540E\u7AEF\u7B49\u5F85\u8FDE\u63A5 (\u4E3B\u7FA4: ${cfg.qq_group_id || "\u672A\u914D\u7F6E"}, channel: ${cfg.bridge_channel_id || "\u672A\u914D\u7F6E"}, db: ${cfg.db_host}:${cfg.db_port})`
123
+ );
124
+ startConsole({
125
+ config: cfg,
126
+ initialEnabled: cfg.qq_enabled,
127
+ wsPort: cfg.qq_ws_port,
128
+ botSelfIdRef,
129
+ backend: "llbot"
130
+ });
131
+ }
35
132
  log.info("\u542F\u52A8\u5B8C\u6210");
36
133
  }
37
134
  main().catch((e) => {
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n/**\n * index.ts \u2014 QQ \u2194 MC \u6865\u63A5\u8FDB\u7A0B\u5165\u53E3\n *\n * \u7AEF\u53E3: 3002 (\u9ED8\u8BA4) \u2014 WebSocket (LLBot reverse-ws)\n *\n * \u6570\u636E\u6D41:\n * QQ \u2192 MC: LLBot \u2500\u2500WS:3002\u2500\u2500\u2192 qq-bridge \u2500\u2500POST\u2500\u2500\u2192 db-server:3001/api/sfmc/messages\n * MC \u2192 QQ: SAPI \u2500\u2500POST\u2500\u2500\u2192 db-server:3001 \u2500\u2500(\u5185\u90E8)\u2500\u2500\u2192 LLBot:3004/send_group_msg\n *\n * \u6CE8\u610F: \u672C\u8FDB\u7A0B\u53EA\u505A WS \u5165\u53E3,\u4E0D\u518D\u8D77 HTTP server\u3002MC\u2192QQ \u7531 db-server \u76F4\u8FDE LLBot\n * (\u89C1 db-server/src/domain/bridge.ts:forwardToQQBridge)\n *\n * \u5FAA\u73AF\u9632\u62A4:\n * 1. \u8DF3\u8FC7 sender.user_id === botSelfId \u7684\u56DE\u58F0\n * (botSelfId \u901A\u8FC7 LLBot \u7684 lifecycle \u5143\u4E8B\u4EF6\u6355\u83B7)\n * 2. 5 \u79D2\u5185\u540C message_id \u77ED\u671F\u53BB\u91CD (\u9632 race)\n */\n\nimport { loadInitialConfig } from \"./config.js\";\nimport { OneBotDispatcher } from \"./onebot.js\";\nimport { startWsServer } from \"./ws-server.js\";\nimport { startConsole } from \"./console.js\";\nimport { log } from \"./log.js\";\nimport type { QQBridgeConfig } from \"./types.js\";\n\nasync function main(): Promise<void> {\n const cfg: QQBridgeConfig = loadInitialConfig();\n\n if (!cfg.qq_enabled) {\n log.info(\"\u5DF2\u7981\u7528 (qq_enabled = false)\");\n process.exit(0);\n }\n\n // \u8BA9\u63A7\u5236\u53F0 status \u547D\u4EE4\u8BFB\u5230 bot \u5F53\u524D\u7684 self_id\n const botSelfIdRef: { value: string | null } = { value: null };\n\n const dispatcher = new OneBotDispatcher({\n qqGroupId: cfg.qq_group_id,\n db: {\n host: cfg.db_host,\n port: cfg.db_port,\n channelId: cfg.bridge_channel_id,\n },\n });\n\n // botSelfId \u901A\u8FC7 dispatcher \u5185\u90E8\u7EF4\u62A4; \u63A7\u5236\u53F0\u8BFB self_id \u65F6\u53D6\u6700\u65B0\u503C\n Object.defineProperty(botSelfIdRef, \"value\", {\n get: () => dispatcher.selfId,\n });\n\n await startWsServer({ port: cfg.qq_ws_port, dispatcher });\n\n log.info(\n `\u7B49\u5F85 LLBot \u8FDE\u63A5 (\u4E3B\u7FA4: ${cfg.qq_group_id || \"\u672A\u914D\u7F6E\"}, channel: ${cfg.bridge_channel_id || \"\u672A\u914D\u7F6E\"}, db: ${cfg.db_host}:${cfg.db_port})`\n );\n\n startConsole({\n config: cfg,\n initialEnabled: cfg.qq_enabled,\n wsPort: cfg.qq_ws_port,\n botSelfIdRef,\n });\n\n log.info(\"\u542F\u52A8\u5B8C\u6210\");\n}\n\nmain().catch((e) => {\n log.error(`\u672A\u6355\u83B7\u5F02\u5E38: ${(e as Error).message}`);\n process.exit(1);\n});\n"],
5
- "mappings": ";AAmBA,SAAS,yBAAyB;AAClC,SAAS,wBAAwB;AACjC,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,WAAW;AAGpB,eAAe,OAAsB;AACnC,QAAM,MAAsB,kBAAkB;AAE9C,MAAI,CAAC,IAAI,YAAY;AACnB,QAAI,KAAK,yCAA0B;AACnC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,eAAyC,EAAE,OAAO,KAAK;AAE7D,QAAM,aAAa,IAAI,iBAAiB;AAAA,IACtC,WAAW,IAAI;AAAA,IACf,IAAI;AAAA,MACF,MAAM,IAAI;AAAA,MACV,MAAM,IAAI;AAAA,MACV,WAAW,IAAI;AAAA,IACjB;AAAA,EACF,CAAC;AAGD,SAAO,eAAe,cAAc,SAAS;AAAA,IAC3C,KAAK,MAAM,WAAW;AAAA,EACxB,CAAC;AAED,QAAM,cAAc,EAAE,MAAM,IAAI,YAAY,WAAW,CAAC;AAExD,MAAI;AAAA,IACF,kDAAoB,IAAI,eAAe,oBAAK,cAAc,IAAI,qBAAqB,oBAAK,SAAS,IAAI,OAAO,IAAI,IAAI,OAAO;AAAA,EAC7H;AAEA,eAAa;AAAA,IACX,QAAQ;AAAA,IACR,gBAAgB,IAAI;AAAA,IACpB,QAAQ,IAAI;AAAA,IACZ;AAAA,EACF,CAAC;AAED,MAAI,KAAK,0BAAM;AACjB;AAEA,KAAK,EAAE,MAAM,CAAC,MAAM;AAClB,MAAI,MAAM,mCAAW,EAAY,OAAO,EAAE;AAC1C,UAAQ,KAAK,CAAC;AAChB,CAAC;",
4
+ "sourcesContent": ["#!/usr/bin/env node\n/**\n * index.ts \u2014 QQ \u2194 MC \u6865\u63A5\u8FDB\u7A0B\u5165\u53E3\n *\n * \u53CC\u540E\u7AEF:\n * official \u2014 \u8FDE\u63A5 QQ \u5F00\u653E\u5E73\u53F0 Gateway\uFF0C\u53EA\u8F6C\u53D1 GROUP_AT_MESSAGE_CREATE\uFF1BC2C \u8D70\u6307\u4EE4\n * llbot \u2014 \u76D1\u542C WS:3002 \u63A5 LLBot reverse-ws\uFF08OneBot 11\uFF09\n *\n * \u5171\u540C\u51FA\u7AD9\u4FE1\u5C01: POST db-server:3001/api/sfmc/messages\n * MC\u2192QQ \u7531 db-server \u6309\u540C\u4E00 qq_backend \u76F4\u8FDE\u5B98\u65B9 OpenAPI \u6216 LLBot HTTP\n *\n * QQ \u4FA7\u6307\u4EE4\u5728\u6865\u5185\u62E6\u622A\uFF1Bofficial \u53EF sync \u81EA\u5B9A\u4E49\u83DC\u5355 / \u7FA4\u6307\u4EE4\u9762\u677F\u3002\n */\n\nimport { createLlbotCommandRouter, createOfficialCommandRouter } from \"./commands/index.js\";\nimport { createInteractionRouter } from \"./commands/interaction-router.js\";\nimport { persistPanelIdToConfig, syncMenuAndPanel } from \"./commands/menu-panel-sync.js\";\nimport { loadInitialConfig } from \"./config.js\";\nimport { OneBotDispatcher } from \"./onebot.js\";\nimport { startWsServer } from \"./ws-server.js\";\nimport { startConsole } from \"./console.js\";\nimport { OfficialAtMessageDispatcher } from \"./official/events.js\";\nimport { startOfficialGateway } from \"./official/gateway.js\";\nimport { installQqRuntimeStatusHooks } from \"./runtime-status.js\";\nimport { log } from \"./log.js\";\nimport type { QQBridgeConfig } from \"./types.js\";\n\nasync function main(): Promise<void> {\n const cfg: QQBridgeConfig = loadInitialConfig();\n const startedAt = Date.now();\n\n if (!cfg.qq_enabled) {\n log.info(\"\u5DF2\u7981\u7528 (qq_enabled = false)\");\n process.exit(0);\n }\n\n const botSelfIdRef: { value: string | null } = { value: null };\n const db = {\n host: cfg.db_host,\n port: cfg.db_port,\n channelId: cfg.bridge_channel_id,\n };\n\n if (cfg.qq_backend === \"official\") {\n if (!cfg.qq_app_id || !cfg.qq_app_secret) {\n log.error(\"\u5B98\u65B9\u540E\u7AEF\u9700\u8981\u914D\u7F6E qq_app_id \u4E0E qq_app_secret\");\n process.exit(1);\n }\n\n const creds = {\n appId: cfg.qq_app_id,\n appSecret: cfg.qq_app_secret,\n sandbox: cfg.qq_sandbox,\n };\n const appId = cfg.qq_app_id;\n const commandRouter = createOfficialCommandRouter(\n creds,\n {\n sandbox: cfg.qq_sandbox,\n appIdHint: appId.length > 8 ? `${appId.slice(0, 4)}\u2026${appId.slice(-4)}` : appId,\n dbHost: cfg.db_host,\n dbPort: cfg.db_port,\n groupOpenid: cfg.qq_group_openid,\n adminOpenids: Array.isArray(cfg.qq_admin_openids) ? cfg.qq_admin_openids : [],\n bridgeChannelId: cfg.bridge_channel_id,\n officialCreds: creds,\n },\n startedAt\n );\n\n const runSyncMenu = async (): Promise<void> => {\n if (cfg.qq_sync_menu_panel === false) {\n log.info(\"\u5DF2\u8DF3\u8FC7 menu/panel sync\uFF08qq_sync_menu_panel=false\uFF09\");\n return;\n }\n await syncMenuAndPanel({\n creds,\n registry: commandRouter.registry,\n groupOpenid: cfg.qq_group_openid,\n panelId: String(cfg.qq_group_panel_id ?? \"\"),\n persistPanelId: (id) => {\n cfg.qq_group_panel_id = id;\n persistPanelIdToConfig(id);\n },\n });\n };\n\n const dispatcher = new OfficialAtMessageDispatcher({\n groupOpenid: cfg.qq_group_openid,\n db,\n commandRouter,\n });\n\n const interactionRouter = createInteractionRouter({\n creds,\n db: { host: cfg.db_host, port: cfg.db_port },\n adminOpenids: Array.isArray(cfg.qq_admin_openids) ? cfg.qq_admin_openids : [],\n });\n\n await startOfficialGateway({\n creds,\n dispatcher,\n interactionRouter,\n });\n\n // \u4E0D\u963B\u65AD Gateway\uFF1A\u5931\u8D25\u53EA warn\n void runSyncMenu().catch((e) => log.warn(`menu/panel sync \u5F02\u5E38: ${(e as Error).message}`));\n\n installQqRuntimeStatusHooks(\"official\");\n\n log.info(\n `\u5B98\u65B9\u540E\u7AEF\u5DF2\u542F\u52A8 (sandbox=${cfg.qq_sandbox}, group_openid=${cfg.qq_group_openid || \"\u672A\u914D\u7F6E\"}, channel=${cfg.bridge_channel_id || \"\u672A\u914D\u7F6E\"}, db=${cfg.db_host}:${cfg.db_port})`\n );\n\n startConsole({\n config: cfg,\n initialEnabled: cfg.qq_enabled,\n wsPort: 0,\n botSelfIdRef,\n backend: \"official\",\n onSyncMenu: runSyncMenu,\n });\n } else {\n const commandRouter = createLlbotCommandRouter(\n {\n host: cfg.llbot_host || \"127.0.0.1\",\n port: cfg.llbot_port || 3004,\n token: cfg.llbot_token || \"\",\n groupId: cfg.qq_group_id || \"0\",\n },\n {\n dbHost: cfg.db_host,\n dbPort: cfg.db_port,\n adminOpenids: Array.isArray(cfg.qq_admin_openids) ? cfg.qq_admin_openids : [],\n bridgeChannelId: cfg.bridge_channel_id,\n },\n startedAt\n );\n\n const dispatcher = new OneBotDispatcher({\n qqGroupId: cfg.qq_group_id,\n db,\n commandRouter,\n });\n\n Object.defineProperty(botSelfIdRef, \"value\", {\n get: () => dispatcher.selfId,\n });\n\n await startWsServer({ port: cfg.qq_ws_port, dispatcher });\n\n installQqRuntimeStatusHooks(\"llbot\");\n\n log.info(\n `LLBot \u540E\u7AEF\u7B49\u5F85\u8FDE\u63A5 (\u4E3B\u7FA4: ${cfg.qq_group_id || \"\u672A\u914D\u7F6E\"}, channel: ${cfg.bridge_channel_id || \"\u672A\u914D\u7F6E\"}, db: ${cfg.db_host}:${cfg.db_port})`\n );\n\n startConsole({\n config: cfg,\n initialEnabled: cfg.qq_enabled,\n wsPort: cfg.qq_ws_port,\n botSelfIdRef,\n backend: \"llbot\",\n });\n }\n\n log.info(\"\u542F\u52A8\u5B8C\u6210\");\n}\n\nmain().catch((e) => {\n log.error(`\u672A\u6355\u83B7\u5F02\u5E38: ${(e as Error).message}`);\n process.exit(1);\n});\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }