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

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 (62) hide show
  1. package/README.md +14 -3
  2. package/dist/commands/db-api.js +89 -0
  3. package/dist/commands/db-api.js.map +7 -0
  4. package/dist/commands/handlers.js +660 -0
  5. package/dist/commands/handlers.js.map +7 -0
  6. package/dist/commands/index.js +55 -0
  7. package/dist/commands/index.js.map +7 -0
  8. package/dist/commands/interaction-router.js +149 -0
  9. package/dist/commands/interaction-router.js.map +7 -0
  10. package/dist/commands/join-settings-ui.js +108 -0
  11. package/dist/commands/join-settings-ui.js.map +7 -0
  12. package/dist/commands/join-settings-ui.test.js +43 -0
  13. package/dist/commands/join-settings-ui.test.js.map +7 -0
  14. package/dist/commands/menu-format.js +45 -0
  15. package/dist/commands/menu-format.js.map +7 -0
  16. package/dist/commands/menu-panel-sync.js +130 -0
  17. package/dist/commands/menu-panel-sync.js.map +7 -0
  18. package/dist/commands/menu-panel-sync.test.js +27 -0
  19. package/dist/commands/menu-panel-sync.test.js.map +7 -0
  20. package/dist/commands/pending.js +33 -0
  21. package/dist/commands/pending.js.map +7 -0
  22. package/dist/commands/registry.js +40 -0
  23. package/dist/commands/registry.js.map +7 -0
  24. package/dist/commands/render.js +46 -0
  25. package/dist/commands/render.js.map +7 -0
  26. package/dist/commands/render.test.js +44 -0
  27. package/dist/commands/render.test.js.map +7 -0
  28. package/dist/commands/reply-llbot.js +63 -0
  29. package/dist/commands/reply-llbot.js.map +7 -0
  30. package/dist/commands/reply-official.js +66 -0
  31. package/dist/commands/reply-official.js.map +7 -0
  32. package/dist/commands/router.js +53 -0
  33. package/dist/commands/router.js.map +7 -0
  34. package/dist/commands/router.test.js +92 -0
  35. package/dist/commands/router.test.js.map +7 -0
  36. package/dist/commands/types.js +1 -0
  37. package/dist/commands/types.js.map +7 -0
  38. package/dist/config.js +12 -2
  39. package/dist/config.js.map +2 -2
  40. package/dist/console.js +40 -20
  41. package/dist/console.js.map +2 -2
  42. package/dist/index.js +117 -20
  43. package/dist/index.js.map +2 -2
  44. package/dist/llbot-ws-api.js +90 -0
  45. package/dist/llbot-ws-api.js.map +7 -0
  46. package/dist/llbot-ws-api.test.js +32 -0
  47. package/dist/llbot-ws-api.test.js.map +7 -0
  48. package/dist/official/events.js +136 -0
  49. package/dist/official/events.js.map +7 -0
  50. package/dist/official/events.test.js +123 -0
  51. package/dist/official/events.test.js.map +7 -0
  52. package/dist/official/gateway.js +226 -0
  53. package/dist/official/gateway.js.map +7 -0
  54. package/dist/onebot.js +23 -5
  55. package/dist/onebot.js.map +2 -2
  56. package/dist/project-root.js +2 -2
  57. package/dist/project-root.js.map +2 -2
  58. package/dist/runtime-status.js +45 -0
  59. package/dist/runtime-status.js.map +7 -0
  60. package/dist/ws-server.js +4 -0
  61. package/dist/ws-server.js.map +2 -2
  62. package/package.json +6 -5
@@ -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 \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 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/** \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,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;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;",
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
  }
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
  }
@@ -0,0 +1,90 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { log } from "./log.js";
3
+ const DEFAULT_TIMEOUT_MS = 8e3;
4
+ class LlbotWsApi {
5
+ sock = null;
6
+ pending = /* @__PURE__ */ new Map();
7
+ /** 当前是否有可用的 LLBot reverse-ws 连接 */
8
+ get connected() {
9
+ return !!this.sock && this.sock.readyState === 1;
10
+ }
11
+ attach(sock) {
12
+ if (this.sock && this.sock !== sock) {
13
+ this.rejectAll(new Error("LLBot WebSocket \u88AB\u65B0\u8FDE\u63A5\u66FF\u6362"));
14
+ }
15
+ this.sock = sock;
16
+ log.info("LLBot WS API \u5DF2\u6302\u63A5\uFF08\u53EF\u7528 reverse-ws \u53D1\u6D88\u606F\uFF09");
17
+ }
18
+ detach(sock) {
19
+ if (this.sock !== sock) return;
20
+ this.sock = null;
21
+ this.rejectAll(new Error("LLBot WebSocket \u5DF2\u65AD\u5F00"));
22
+ log.info("LLBot WS API \u5DF2\u5378\u8F7D");
23
+ }
24
+ /**
25
+ * 若帧是 API 回包(有 echo、无 post_type),消费并返回 true。
26
+ * 事件帧返回 false,交给 dispatcher。
27
+ */
28
+ tryHandleResponse(raw) {
29
+ if (!raw || typeof raw !== "object") return false;
30
+ const obj = raw;
31
+ if (obj.post_type != null) return false;
32
+ const echo = obj.echo;
33
+ if (echo == null || echo === "") return false;
34
+ const key = String(echo);
35
+ const p = this.pending.get(key);
36
+ if (!p) return false;
37
+ this.pending.delete(key);
38
+ clearTimeout(p.timer);
39
+ p.resolve(obj);
40
+ return true;
41
+ }
42
+ async call(action, params, timeoutMs = DEFAULT_TIMEOUT_MS) {
43
+ if (!this.connected || !this.sock) {
44
+ throw new Error("LLBot reverse-ws \u672A\u8FDE\u63A5\uFF0C\u65E0\u6CD5\u8C03\u7528 API");
45
+ }
46
+ const echo = `sfmc_${Date.now().toString(36)}_${randomBytes(4).toString("hex")}`;
47
+ const payload = JSON.stringify({ action, params, echo });
48
+ return new Promise((resolve, reject) => {
49
+ const timer = setTimeout(() => {
50
+ this.pending.delete(echo);
51
+ reject(new Error(`LLBot WS API \u8D85\u65F6 (${action})`));
52
+ }, timeoutMs);
53
+ this.pending.set(echo, { resolve, reject, timer });
54
+ try {
55
+ this.sock.send(payload);
56
+ } catch (e) {
57
+ this.pending.delete(echo);
58
+ clearTimeout(timer);
59
+ reject(e instanceof Error ? e : new Error(String(e)));
60
+ }
61
+ });
62
+ }
63
+ async sendGroupMsg(groupId, text) {
64
+ const gid = typeof groupId === "number" ? groupId : parseInt(String(groupId), 10);
65
+ if (!Number.isFinite(gid)) throw new Error(`\u65E0\u6548 group_id: ${groupId}`);
66
+ const result = await this.call("send_group_msg", {
67
+ group_id: gid,
68
+ message: [{ type: "text", data: { text } }]
69
+ });
70
+ const ret = result.retcode ?? (result.status === "ok" ? 0 : -1);
71
+ if (ret !== 0) {
72
+ throw new Error(
73
+ `LLBot send_group_msg retcode=${ret}: ${result.message || result.wording || result.status || "fail"}`
74
+ );
75
+ }
76
+ }
77
+ rejectAll(err) {
78
+ for (const [k, p] of this.pending) {
79
+ clearTimeout(p.timer);
80
+ p.reject(err);
81
+ this.pending.delete(k);
82
+ }
83
+ }
84
+ }
85
+ const llbotWsApi = new LlbotWsApi();
86
+ export {
87
+ LlbotWsApi,
88
+ llbotWsApi
89
+ };
90
+ //# sourceMappingURL=llbot-ws-api.js.map