@sfmc-bds/qq-bridge 0.2.0-beta.11 → 0.2.0-beta.13

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 (70) hide show
  1. package/README.md +68 -1
  2. package/deploy/install-nginx-webhook.sh +34 -0
  3. package/deploy/nginx-webhook-location.conf +10 -0
  4. package/deploy/qq-bridge.service +23 -0
  5. package/deploy/qq-config-sync.ps1 +106 -0
  6. package/deploy/qq-tunnel.ps1 +15 -0
  7. package/deploy/sync-qq-config.mjs +229 -0
  8. package/dist/commands/account-format.js +41 -0
  9. package/dist/commands/account-format.js.map +7 -0
  10. package/dist/commands/content-format.js +114 -0
  11. package/dist/commands/content-format.js.map +7 -0
  12. package/dist/commands/db-api.js +21 -6
  13. package/dist/commands/db-api.js.map +2 -2
  14. package/dist/commands/handlers.js +392 -524
  15. package/dist/commands/handlers.js.map +2 -2
  16. package/dist/commands/index.js +5 -5
  17. package/dist/commands/index.js.map +3 -3
  18. package/dist/commands/interaction-router.js +38 -133
  19. package/dist/commands/interaction-router.js.map +2 -2
  20. package/dist/commands/join-settings-ui.js +1 -24
  21. package/dist/commands/join-settings-ui.js.map +2 -2
  22. package/dist/commands/menu-format.js +36 -33
  23. package/dist/commands/menu-format.js.map +2 -2
  24. package/dist/commands/menu-panel-sync.js +39 -13
  25. package/dist/commands/menu-panel-sync.js.map +2 -2
  26. package/dist/commands/message-text.js +91 -0
  27. package/dist/commands/message-text.js.map +7 -0
  28. package/dist/commands/pending.js +23 -16
  29. package/dist/commands/pending.js.map +2 -2
  30. package/dist/commands/permissions.js +17 -0
  31. package/dist/commands/permissions.js.map +7 -0
  32. package/dist/commands/registry.js +1 -1
  33. package/dist/commands/registry.js.map +2 -2
  34. package/dist/commands/render.js +7 -16
  35. package/dist/commands/render.js.map +2 -2
  36. package/dist/commands/reply-llbot.js +18 -7
  37. package/dist/commands/reply-llbot.js.map +2 -2
  38. package/dist/commands/reply-official.js +55 -39
  39. package/dist/commands/reply-official.js.map +3 -3
  40. package/dist/commands/router.js +116 -23
  41. package/dist/commands/router.js.map +2 -2
  42. package/dist/config.js +35 -27
  43. package/dist/config.js.map +2 -2
  44. package/dist/console.js +3 -3
  45. package/dist/console.js.map +2 -2
  46. package/dist/index.js +30 -16
  47. package/dist/index.js.map +2 -2
  48. package/dist/llbot-ws-api.js +2 -2
  49. package/dist/llbot-ws-api.js.map +2 -2
  50. package/dist/official/events.js +32 -7
  51. package/dist/official/events.js.map +2 -2
  52. package/dist/official/gateway.js +10 -1
  53. package/dist/official/gateway.js.map +2 -2
  54. package/dist/official/webhook.js +165 -0
  55. package/dist/official/webhook.js.map +7 -0
  56. package/dist/onebot.js +0 -4
  57. package/dist/onebot.js.map +2 -2
  58. package/package.json +4 -4
  59. package/dist/commands/join-settings-ui.test.js +0 -43
  60. package/dist/commands/join-settings-ui.test.js.map +0 -7
  61. package/dist/commands/menu-panel-sync.test.js +0 -27
  62. package/dist/commands/menu-panel-sync.test.js.map +0 -7
  63. package/dist/commands/render.test.js +0 -44
  64. package/dist/commands/render.test.js.map +0 -7
  65. package/dist/commands/router.test.js +0 -92
  66. package/dist/commands/router.test.js.map +0 -7
  67. package/dist/llbot-ws-api.test.js +0 -32
  68. package/dist/llbot-ws-api.test.js.map +0 -7
  69. package/dist/official/events.test.js +0 -123
  70. package/dist/official/events.test.js.map +0 -7
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/commands/join-settings-ui.ts"],
4
- "sourcesContent": ["/**\n * commands/join-settings-ui.ts \u2014 \u5165\u670D\u914D\u7F6E\u9762\u677F\u6587\u6848\u4E0E\u4E92\u52A8\u6309\u94AE\uFF08DRY\uFF09\n *\n * official\uFF1AINTERACTION \u56DE\u8C03 data = cfg:allowlist|approval:on|off\n * llbot\uFF1A\u7F16\u53F7\u4F1A\u8BDD command = \u300C\u914D\u7F6E \u767D\u540D\u5355/\u5BA1\u6279 \u5F00|\u5173\u300D\n */\n\nimport type { JoinSettingsResponse } from \"./db-api.js\";\nimport type { CommandButton, CommandResult, QqBackendKind } from \"./types.js\";\n\nexport type JoinSettingsView = {\n allowlist_enabled?: boolean;\n require_approval?: boolean;\n treat_group_admins_as_admins?: boolean;\n};\n\nfunction onOffLabel(on: boolean): string {\n return on ? \"\u5F00\" : \"\u5173\";\n}\n\n/** \u7EAF\u6587\u672C\u6458\u8981\uFF08llbot / \u964D\u7EA7\uFF09 */\nexport function formatJoinSettingsText(s: JoinSettingsView, prefix = \"\"): string {\n const al = onOffLabel(s.allowlist_enabled !== false);\n const ap = onOffLabel(s.require_approval !== false);\n const ga = onOffLabel(s.treat_group_admins_as_admins === true);\n const head = prefix ? `${prefix}\\n` : \"\";\n return (\n `${head}\u5165\u670D\u63D2\u4EF6\u914D\u7F6E\uFF08configs/qq_link.json\uFF09\\n` +\n `\u00B7 \u767D\u540D\u5355\uFF1A${al}\\n` +\n `\u00B7 \u5BA1\u6279\uFF1A${ap}\\n` +\n `\u00B7 \u7FA4\u7BA1\u89C6\u4F5C\u7BA1\u7406\u5458\uFF1A${ga}\uFF08\u53EA\u8BFB\uFF0C\u6539\u6587\u4EF6\uFF09`\n );\n}\n\nexport function formatJoinSettingsMarkdown(s: JoinSettingsView, prefix?: string): string {\n const al = onOffLabel(s.allowlist_enabled !== false);\n const ap = onOffLabel(s.require_approval !== false);\n const ga = onOffLabel(s.treat_group_admins_as_admins === true);\n const lines = [\n \"## \u5165\u670D\u914D\u7F6E\",\n ...(prefix ? [\"\", prefix] : []),\n \"\",\n `| \u9879 | \u72B6\u6001 |`,\n `| --- | --- |`,\n `| \u767D\u540D\u5355 | **${al}** |`,\n `| \u5BA1\u6279 | **${ap}** |`,\n `| \u7FA4\u7BA1\u89C6\u4F5C\u7BA1\u7406\u5458 | ${ga}\uFF08\u53EA\u8BFB\uFF09 |`,\n \"\",\n \"_\u70B9\u6309\u94AE\u5207\u6362\u767D\u540D\u5355/\u5BA1\u6279\uFF1B\u7FA4\u7BA1\u5F00\u5173\u8BF7\u6539\u914D\u7F6E\u6587\u4EF6_\",\n ];\n return lines.join(\"\\n\");\n}\n\n/**\n * \u751F\u6210\u5207\u6362\u6309\u94AE\uFF1A\u5C55\u793A\u300C\u70B9\u4E00\u4E0B\u53D8\u6210\u7684\u76EE\u6807\u72B6\u6001\u300D\u3002\n * official \u7528\u56DE\u8C03\uFF1Bllbot \u7528\u6587\u672C\u547D\u4EE4\u3002\n */\nexport function buildJoinSettingsButtons(\n s: JoinSettingsView,\n backend: QqBackendKind,\n adminOpenids: string[] = []\n): CommandButton[] {\n const alOn = s.allowlist_enabled !== false;\n const apOn = s.require_approval !== false;\n // \u70B9\u4E00\u4E0B\u5207\u5230\u53CD\u72B6\u6001\n const alTarget = !alOn;\n const apTarget = !apOn;\n const alWord = alTarget ? \"\u5F00\" : \"\u5173\";\n const apWord = apTarget ? \"\u5F00\" : \"\u5173\";\n\n const permission =\n adminOpenids.length > 0\n ? { type: 0 as const, specify_user_ids: adminOpenids }\n : { type: 2 as const };\n\n if (backend === \"official\") {\n return [\n {\n id: \"cfg_al\",\n label: `\u767D\u540D\u5355\u00B7${alWord}`,\n command: `cfg:allowlist:${alTarget ? \"on\" : \"off\"}`,\n actionType: 1,\n style: alTarget ? 1 : 0,\n visitedLabel: `\u767D\u540D\u5355\u00B7${alWord}`,\n permission,\n },\n {\n id: \"cfg_ap\",\n label: `\u5BA1\u6279\u00B7${apWord}`,\n command: `cfg:approval:${apTarget ? \"on\" : \"off\"}`,\n actionType: 1,\n style: apTarget ? 1 : 0,\n visitedLabel: `\u5BA1\u6279\u00B7${apWord}`,\n permission,\n },\n ];\n }\n\n return [\n {\n id: \"cfg_al\",\n label: `\u767D\u540D\u5355 ${alWord}`,\n command: `\u914D\u7F6E \u767D\u540D\u5355 ${alWord}`,\n },\n {\n id: \"cfg_ap\",\n label: `\u5BA1\u6279 ${apWord}`,\n command: `\u914D\u7F6E \u5BA1\u6279 ${apWord}`,\n },\n ];\n}\n\nexport function buildJoinSettingsPanel(opts: {\n settings: JoinSettingsView;\n backend: QqBackendKind;\n adminOpenids?: string[];\n /** \u5982\u300C\u5DF2\u4FDD\u5B58\u300D */\n prefix?: string;\n}): CommandResult {\n const { settings, backend, adminOpenids = [], prefix } = opts;\n const text = formatJoinSettingsText(settings, prefix);\n return {\n text,\n markdown: formatJoinSettingsMarkdown(settings, prefix),\n buttons: buildJoinSettingsButtons(settings, backend, adminOpenids),\n };\n}\n\n/** \u89E3\u6790 cfg:allowlist|approval:on|off */\nexport function parseCfgInteractionData(\n data: string\n): { field: \"allowlist_enabled\" | \"require_approval\"; value: boolean } | null {\n const m = /^cfg:(allowlist|approval):(on|off|true|false|1|0)$/i.exec(data.trim());\n if (!m) return null;\n const field = m[1]!.toLowerCase() === \"allowlist\" ? \"allowlist_enabled\" : \"require_approval\";\n const raw = m[2]!.toLowerCase();\n const value = raw === \"on\" || raw === \"true\" || raw === \"1\";\n return { field, value };\n}\n\nexport function settingsFromResponse(data: JoinSettingsResponse): JoinSettingsView {\n return {\n allowlist_enabled: data.allowlist_enabled,\n require_approval: data.require_approval,\n treat_group_admins_as_admins: data.treat_group_admins_as_admins,\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * commands/join-settings-ui.ts \u2014 \u5165\u670D\u914D\u7F6E\u9762\u677F\u6587\u6848\u4E0E\u4E92\u52A8\u6309\u94AE\uFF08DRY\uFF09\n *\n * official \u4E0E llbot \u5747\u4F7F\u7528\u6587\u5B57\u547D\u4EE4\uFF0C\u7ECF\u8FC7\u7EDF\u4E00\u8DEF\u7531\u4E0E\u786E\u8BA4\n * llbot\uFF1A\u7F16\u53F7\u4F1A\u8BDD command = \u300C\u914D\u7F6E \u767D\u540D\u5355/\u5BA1\u6279 \u5F00|\u5173\u300D\n */\n\nimport type { JoinSettingsResponse } from \"./db-api.js\";\nimport type { CommandButton, CommandResult, QqBackendKind } from \"./types.js\";\n\nexport type JoinSettingsView = {\n allowlist_enabled?: boolean | undefined;\n require_approval?: boolean | undefined;\n treat_group_admins_as_admins?: boolean | undefined;\n};\n\nfunction onOffLabel(on: boolean): string {\n return on ? \"\u5F00\" : \"\u5173\";\n}\n\n/** \u7EAF\u6587\u672C\u6458\u8981\uFF08llbot / \u964D\u7EA7\uFF09 */\nexport function formatJoinSettingsText(s: JoinSettingsView, prefix = \"\"): string {\n const al = onOffLabel(s.allowlist_enabled !== false);\n const ap = onOffLabel(s.require_approval !== false);\n const ga = onOffLabel(s.treat_group_admins_as_admins === true);\n const head = prefix ? `${prefix}\\n` : \"\";\n return (\n `${head}\u5165\u670D\u63D2\u4EF6\u914D\u7F6E\uFF08configs/qq_link.json\uFF09\\n` +\n `\u00B7 \u767D\u540D\u5355\uFF1A${al}\\n` +\n `\u00B7 \u5BA1\u6279\uFF1A${ap}\\n` +\n `\u00B7 \u7FA4\u7BA1\u89C6\u4F5C\u7BA1\u7406\u5458\uFF1A${ga}\uFF08\u53EA\u8BFB\uFF0C\u6539\u6587\u4EF6\uFF09`\n );\n}\n\nexport function formatJoinSettingsMarkdown(s: JoinSettingsView, prefix?: string): string {\n const al = onOffLabel(s.allowlist_enabled !== false);\n const ap = onOffLabel(s.require_approval !== false);\n const ga = onOffLabel(s.treat_group_admins_as_admins === true);\n const lines = [\n \"## \u5165\u670D\u914D\u7F6E\",\n ...(prefix ? [\"\", prefix] : []),\n \"\",\n `| \u9879 | \u72B6\u6001 |`,\n `| --- | --- |`,\n `| \u767D\u540D\u5355 | **${al}** |`,\n `| \u5BA1\u6279 | **${ap}** |`,\n `| \u7FA4\u7BA1\u89C6\u4F5C\u7BA1\u7406\u5458 | ${ga}\uFF08\u53EA\u8BFB\uFF09 |`,\n \"\",\n \"_\u70B9\u6309\u94AE\u5207\u6362\u767D\u540D\u5355/\u5BA1\u6279\uFF1B\u7FA4\u7BA1\u5F00\u5173\u8BF7\u6539\u914D\u7F6E\u6587\u4EF6_\",\n ];\n return lines.join(\"\\n\");\n}\n\n/**\n * \u751F\u6210\u5207\u6362\u6309\u94AE\uFF1A\u5C55\u793A\u300C\u70B9\u4E00\u4E0B\u53D8\u6210\u7684\u76EE\u6807\u72B6\u6001\u300D\u3002\n * \u4E24\u7AEF\u4F7F\u7528\u76F8\u540C\u7684\u6587\u672C\u547D\u4EE4\u3002\n */\nexport function buildJoinSettingsButtons(\n s: JoinSettingsView,\n _backend: QqBackendKind,\n _adminOpenids: string[] = []\n): CommandButton[] {\n const alOn = s.allowlist_enabled !== false;\n const apOn = s.require_approval !== false;\n // \u70B9\u4E00\u4E0B\u5207\u5230\u53CD\u72B6\u6001\n const alTarget = !alOn;\n const apTarget = !apOn;\n const alWord = alTarget ? \"\u5F00\" : \"\u5173\";\n const apWord = apTarget ? \"\u5F00\" : \"\u5173\";\n\n return [\n {\n id: \"cfg_al\",\n label: `\u767D\u540D\u5355 ${alWord}`,\n command: `\u914D\u7F6E \u767D\u540D\u5355 ${alWord}`,\n },\n {\n id: \"cfg_ap\",\n label: `\u5BA1\u6279 ${apWord}`,\n command: `\u914D\u7F6E \u5BA1\u6279 ${apWord}`,\n },\n ];\n}\n\nexport function buildJoinSettingsPanel(opts: {\n settings: JoinSettingsView;\n backend: QqBackendKind;\n adminOpenids?: string[];\n /** \u5982\u300C\u5DF2\u4FDD\u5B58\u300D */\n prefix?: string;\n}): CommandResult {\n const { settings, backend, adminOpenids = [], prefix } = opts;\n const text = formatJoinSettingsText(settings, prefix);\n return {\n text,\n markdown: formatJoinSettingsMarkdown(settings, prefix),\n buttons: buildJoinSettingsButtons(settings, backend, adminOpenids),\n };\n}\n\n/** \u89E3\u6790 cfg:allowlist|approval:on|off */\nexport function parseCfgInteractionData(\n data: string\n): { field: \"allowlist_enabled\" | \"require_approval\"; value: boolean } | null {\n const m = /^cfg:(allowlist|approval):(on|off|true|false|1|0)$/i.exec(data.trim());\n if (!m) return null;\n const field = m[1]!.toLowerCase() === \"allowlist\" ? \"allowlist_enabled\" : \"require_approval\";\n const raw = m[2]!.toLowerCase();\n const value = raw === \"on\" || raw === \"true\" || raw === \"1\";\n return { field, value };\n}\n\nexport function settingsFromResponse(data: JoinSettingsResponse): JoinSettingsView {\n return {\n allowlist_enabled: data.allowlist_enabled,\n require_approval: data.require_approval,\n treat_group_admins_as_admins: data.treat_group_admins_as_admins,\n };\n}\n"],
5
+ "mappings": "AAgBA,SAAS,WAAW,IAAqB;AACvC,SAAO,KAAK,WAAM;AACpB;AAGO,SAAS,uBAAuB,GAAqB,SAAS,IAAY;AAC/E,QAAM,KAAK,WAAW,EAAE,sBAAsB,KAAK;AACnD,QAAM,KAAK,WAAW,EAAE,qBAAqB,KAAK;AAClD,QAAM,KAAK,WAAW,EAAE,iCAAiC,IAAI;AAC7D,QAAM,OAAO,SAAS,GAAG,MAAM;AAAA,IAAO;AACtC,SACE,GAAG,IAAI;AAAA,+BACE,EAAE;AAAA,yBACH,EAAE;AAAA,uDACG,EAAE;AAEnB;AAEO,SAAS,2BAA2B,GAAqB,QAAyB;AACvF,QAAM,KAAK,WAAW,EAAE,sBAAsB,KAAK;AACnD,QAAM,KAAK,WAAW,EAAE,qBAAqB,KAAK;AAClD,QAAM,KAAK,WAAW,EAAE,iCAAiC,IAAI;AAC7D,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,GAAI,SAAS,CAAC,IAAI,MAAM,IAAI,CAAC;AAAA,IAC7B;AAAA,IACA;AAAA,IACA;AAAA,IACA,4BAAa,EAAE;AAAA,IACf,sBAAY,EAAE;AAAA,IACd,kDAAe,EAAE;AAAA,IACjB;AAAA,IACA;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAMO,SAAS,yBACd,GACA,UACA,gBAA0B,CAAC,GACV;AACjB,QAAM,OAAO,EAAE,sBAAsB;AACrC,QAAM,OAAO,EAAE,qBAAqB;AAEpC,QAAM,WAAW,CAAC;AAClB,QAAM,WAAW,CAAC;AAClB,QAAM,SAAS,WAAW,WAAM;AAChC,QAAM,SAAS,WAAW,WAAM;AAEhC,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,sBAAO,MAAM;AAAA,MACpB,SAAS,mCAAU,MAAM;AAAA,IAC3B;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,gBAAM,MAAM;AAAA,MACnB,SAAS,6BAAS,MAAM;AAAA,IAC1B;AAAA,EACF;AACF;AAEO,SAAS,uBAAuB,MAMrB;AAChB,QAAM,EAAE,UAAU,SAAS,eAAe,CAAC,GAAG,OAAO,IAAI;AACzD,QAAM,OAAO,uBAAuB,UAAU,MAAM;AACpD,SAAO;AAAA,IACL;AAAA,IACA,UAAU,2BAA2B,UAAU,MAAM;AAAA,IACrD,SAAS,yBAAyB,UAAU,SAAS,YAAY;AAAA,EACnE;AACF;AAGO,SAAS,wBACd,MAC4E;AAC5E,QAAM,IAAI,sDAAsD,KAAK,KAAK,KAAK,CAAC;AAChF,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,QAAQ,EAAE,CAAC,EAAG,YAAY,MAAM,cAAc,sBAAsB;AAC1E,QAAM,MAAM,EAAE,CAAC,EAAG,YAAY;AAC9B,QAAM,QAAQ,QAAQ,QAAQ,QAAQ,UAAU,QAAQ;AACxD,SAAO,EAAE,OAAO,MAAM;AACxB;AAEO,SAAS,qBAAqB,MAA8C;AACjF,SAAO;AAAA,IACL,mBAAmB,KAAK;AAAA,IACxB,kBAAkB,KAAK;AAAA,IACvB,8BAA8B,KAAK;AAAA,EACrC;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,44 +1,47 @@
1
1
  function pickDisplayLabel(cmd, maxChars = 8) {
2
- const candidates = [cmd.name, ...cmd.aliases].map((a) => String(a ?? "").replace(/^[//]+/, "").trim()).filter((a) => a.length > 0);
3
- const zh = candidates.find((a) => /[\u4e00-\u9fff]/.test(a));
4
- const preferred = zh ?? candidates.find((a) => !a.startsWith("/")) ?? candidates[0] ?? cmd.name;
5
- return preferred.slice(0, maxChars);
2
+ const labels = {
3
+ server: "\u670D\u52A1\u5668\u4FE1\u606F",
4
+ account: "\u6211\u7684\u8D26\u53F7",
5
+ entry: "\u8FDB\u670D\u8BF4\u660E",
6
+ help: "\u4F7F\u7528\u5E2E\u52A9",
7
+ admin: "\u670D\u52A1\u5668\u7BA1\u7406",
8
+ online: "\u5728\u7EBF\u73A9\u5BB6",
9
+ ip: "\u8FDE\u63A5\u5730\u5740",
10
+ channel: "\u804A\u5929\u4E92\u901A",
11
+ ping: "\u8FDE\u63A5\u68C0\u67E5",
12
+ modules: "\u6A21\u5757\u5217\u8868",
13
+ packs: "\u4E16\u754C\u5305"
14
+ };
15
+ const label = labels[cmd.name];
16
+ if (label) return label.slice(0, maxChars);
17
+ const candidates = [cmd.name, ...cmd.aliases].map((name) => name.replace(/^[//]+/, "").trim()).filter(Boolean);
18
+ return (candidates.find((name) => /[\u4e00-\u9fff]/.test(name)) ?? candidates[0] ?? cmd.name).slice(0, maxChars);
6
19
  }
7
- function buttonsFor(cmds, idPrefix) {
8
- return cmds.map((c) => ({
9
- id: `${idPrefix}_${c.name}`,
10
- label: pickDisplayLabel(c, 8),
11
- command: `/${c.name}`
12
- }));
20
+ function formatCard(title, lines) {
21
+ return {
22
+ text: [title, "", ...lines].join("\n"),
23
+ markdown: [`## ${title}`, "", ...lines].join("\n")
24
+ };
13
25
  }
14
26
  function formatCommandMenu(opts) {
15
- const { title, subtitle, cmds, idPrefix, footerMd, footerText } = opts;
16
- const textLines = cmds.map((c, i) => {
17
- const label = pickDisplayLabel(c, 8);
18
- return `${i + 1}. ${label} \u2014 ${c.description}`;
19
- });
20
- const mdLines = cmds.map((c) => {
21
- const label = pickDisplayLabel(c, 12);
22
- return `- **${label}**\uFF08\`/${c.name}\`\uFF09\uFF1A${c.description}`;
23
- });
27
+ const buttons = opts.cmds.map((cmd) => ({
28
+ id: `${opts.idPrefix}_${cmd.name}`,
29
+ label: pickDisplayLabel(cmd),
30
+ description: cmd.description,
31
+ command: `/${cmd.name}`
32
+ }));
33
+ const card = formatCard(opts.title, [opts.subtitle]);
24
34
  return {
25
- text: `${title}
26
- ${subtitle}
27
-
28
- ${textLines.join("\n")}
29
-
30
- ${footerText}`,
31
- markdown: `## ${title}
32
-
33
- ${subtitle}
34
-
35
- ${mdLines.join("\n")}
36
-
37
- ${footerMd}`,
38
- buttons: buttonsFor(cmds, idPrefix)
35
+ ...card,
36
+ menu: opts.home ? "home" : "section",
37
+ markdown: [card.markdown, "", ...opts.footerMd ? ["", opts.footerMd] : []].join("\n"),
38
+ text: card.text + (opts.footerText ? `
39
+ ${opts.footerText}` : ""),
40
+ buttons
39
41
  };
40
42
  }
41
43
  export {
44
+ formatCard,
42
45
  formatCommandMenu,
43
46
  pickDisplayLabel
44
47
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/commands/menu-format.ts"],
4
- "sourcesContent": ["/**\n * menu-format.ts \u2014 \u4E3B\u83DC\u5355 / \u7BA1\u7406\u5B50\u83DC\u5355\u7EDF\u4E00\u6587\u6848\uFF08official Markdown + llbot \u7EAF\u6587\u672C\uFF09\n *\n * \u4E24\u8FB9\u5171\u7528\u540C\u4E00\u5957 label/description\uFF0C\u4FDD\u8BC1\u7F16\u53F7\u4F1A\u8BDD\u4E0E\u6309\u94AE\u6587\u6848\u4E00\u81F4\u3002\n */\n\nimport type { CommandButton, CommandResult, RegisteredCommand } from \"./types.js\";\n\n/** \u4F18\u5148\u4E2D\u6587\u77ED\u522B\u540D\u4F5C\u6309\u94AE/\u7F16\u53F7\u6807\u7B7E */\nexport function pickDisplayLabel(cmd: RegisteredCommand, maxChars = 8): string {\n const candidates = [cmd.name, ...cmd.aliases]\n .map((a) => String(a ?? \"\").replace(/^[/\uFF0F]+/, \"\").trim())\n .filter((a) => a.length > 0);\n const zh = candidates.find((a) => /[\\u4e00-\\u9fff]/.test(a));\n const preferred = zh ?? candidates.find((a) => !a.startsWith(\"/\")) ?? candidates[0] ?? cmd.name;\n return preferred.slice(0, maxChars);\n}\n\nfunction buttonsFor(cmds: RegisteredCommand[], idPrefix: string): CommandButton[] {\n return cmds.map((c) => ({\n id: `${idPrefix}_${c.name}`,\n label: pickDisplayLabel(c, 8),\n command: `/${c.name}`,\n }));\n}\n\nexport function formatCommandMenu(opts: {\n title: string;\n subtitle: string;\n cmds: RegisteredCommand[];\n idPrefix: string;\n footerMd: string;\n footerText: string;\n}): CommandResult {\n const { title, subtitle, cmds, idPrefix, footerMd, footerText } = opts;\n const textLines = cmds.map((c, i) => {\n const label = pickDisplayLabel(c, 8);\n return `${i + 1}. ${label} \u2014 ${c.description}`;\n });\n const mdLines = cmds.map((c) => {\n const label = pickDisplayLabel(c, 12);\n return `- **${label}**\uFF08\\`/${c.name}\\`\uFF09\uFF1A${c.description}`;\n });\n return {\n text: `${title}\\n${subtitle}\\n\\n${textLines.join(\"\\n\")}\\n\\n${footerText}`,\n markdown: `## ${title}\\n\\n${subtitle}\\n\\n${mdLines.join(\"\\n\")}\\n\\n${footerMd}`,\n buttons: buttonsFor(cmds, idPrefix),\n };\n}\n"],
5
- "mappings": "AASO,SAAS,iBAAiB,KAAwB,WAAW,GAAW;AAC7E,QAAM,aAAa,CAAC,IAAI,MAAM,GAAG,IAAI,OAAO,EACzC,IAAI,CAAC,MAAM,OAAO,KAAK,EAAE,EAAE,QAAQ,UAAU,EAAE,EAAE,KAAK,CAAC,EACvD,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC7B,QAAM,KAAK,WAAW,KAAK,CAAC,MAAM,kBAAkB,KAAK,CAAC,CAAC;AAC3D,QAAM,YAAY,MAAM,WAAW,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,CAAC,KAAK,WAAW,CAAC,KAAK,IAAI;AAC3F,SAAO,UAAU,MAAM,GAAG,QAAQ;AACpC;AAEA,SAAS,WAAW,MAA2B,UAAmC;AAChF,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,IAAI,GAAG,QAAQ,IAAI,EAAE,IAAI;AAAA,IACzB,OAAO,iBAAiB,GAAG,CAAC;AAAA,IAC5B,SAAS,IAAI,EAAE,IAAI;AAAA,EACrB,EAAE;AACJ;AAEO,SAAS,kBAAkB,MAOhB;AAChB,QAAM,EAAE,OAAO,UAAU,MAAM,UAAU,UAAU,WAAW,IAAI;AAClE,QAAM,YAAY,KAAK,IAAI,CAAC,GAAG,MAAM;AACnC,UAAM,QAAQ,iBAAiB,GAAG,CAAC;AACnC,WAAO,GAAG,IAAI,CAAC,KAAK,KAAK,WAAM,EAAE,WAAW;AAAA,EAC9C,CAAC;AACD,QAAM,UAAU,KAAK,IAAI,CAAC,MAAM;AAC9B,UAAM,QAAQ,iBAAiB,GAAG,EAAE;AACpC,WAAO,OAAO,KAAK,cAAS,EAAE,IAAI,iBAAO,EAAE,WAAW;AAAA,EACxD,CAAC;AACD,SAAO;AAAA,IACL,MAAM,GAAG,KAAK;AAAA,EAAK,QAAQ;AAAA;AAAA,EAAO,UAAU,KAAK,IAAI,CAAC;AAAA;AAAA,EAAO,UAAU;AAAA,IACvE,UAAU,MAAM,KAAK;AAAA;AAAA,EAAO,QAAQ;AAAA;AAAA,EAAO,QAAQ,KAAK,IAAI,CAAC;AAAA;AAAA,EAAO,QAAQ;AAAA,IAC5E,SAAS,WAAW,MAAM,QAAQ;AAAA,EACpC;AACF;",
4
+ "sourcesContent": ["/** \u83DC\u5355\u4E0E\u4FE1\u606F\u5361\u7EDF\u4E00\u6392\u7248\uFF0C\u907F\u514D\u6B63\u6587\u548C\u64CD\u4F5C\u533A\u91CD\u590D\u5217\u51FA\u6307\u4EE4\u3002 */\nimport type { CommandButton, CommandResult, RegisteredCommand } from \"./types.js\";\n\nexport function pickDisplayLabel(cmd: RegisteredCommand, maxChars = 8): string {\n const labels: Record<string, string> = {\n server: \"\u670D\u52A1\u5668\u4FE1\u606F\",\n account: \"\u6211\u7684\u8D26\u53F7\",\n entry: \"\u8FDB\u670D\u8BF4\u660E\",\n help: \"\u4F7F\u7528\u5E2E\u52A9\",\n admin: \"\u670D\u52A1\u5668\u7BA1\u7406\",\n online: \"\u5728\u7EBF\u73A9\u5BB6\",\n ip: \"\u8FDE\u63A5\u5730\u5740\",\n channel: \"\u804A\u5929\u4E92\u901A\",\n ping: \"\u8FDE\u63A5\u68C0\u67E5\",\n modules: \"\u6A21\u5757\u5217\u8868\",\n packs: \"\u4E16\u754C\u5305\",\n };\n const label = labels[cmd.name];\n if (label) return label.slice(0, maxChars);\n const candidates = [cmd.name, ...cmd.aliases].map((name) => name.replace(/^[/\uFF0F]+/, \"\").trim()).filter(Boolean);\n return (candidates.find((name) => /[\\u4e00-\\u9fff]/.test(name)) ?? candidates[0] ?? cmd.name).slice(0, maxChars);\n}\n\nexport function formatCard(title: string, lines: string[]): CommandResult {\n return {\n text: [title, \"\", ...lines].join(\"\\n\"),\n markdown: [`## ${title}`, \"\", ...lines].join(\"\\n\"),\n };\n}\n\nexport function formatCommandMenu(opts: {\n title: string;\n subtitle: string;\n cmds: RegisteredCommand[];\n idPrefix: string;\n footerMd: string;\n footerText: string;\n home?: boolean;\n}): CommandResult {\n const buttons: CommandButton[] = opts.cmds.map((cmd) => ({\n id: `${opts.idPrefix}_${cmd.name}`,\n label: pickDisplayLabel(cmd),\n description: cmd.description,\n command: `/${cmd.name}`,\n }));\n const card = formatCard(opts.title, [opts.subtitle]);\n return {\n ...card,\n menu: opts.home ? \"home\" : \"section\",\n markdown: [card.markdown, \"\", ...(opts.footerMd ? [\"\", opts.footerMd] : [])].join(\"\\n\"),\n text: card.text + (opts.footerText ? `\\n${opts.footerText}` : \"\"),\n buttons,\n };\n}\n"],
5
+ "mappings": "AAGO,SAAS,iBAAiB,KAAwB,WAAW,GAAW;AAC7E,QAAM,SAAiC;AAAA,IACrC,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,IAAI;AAAA,IACJ,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AACA,QAAM,QAAQ,OAAO,IAAI,IAAI;AAC7B,MAAI,MAAO,QAAO,MAAM,MAAM,GAAG,QAAQ;AACzC,QAAM,aAAa,CAAC,IAAI,MAAM,GAAG,IAAI,OAAO,EAAE,IAAI,CAAC,SAAS,KAAK,QAAQ,UAAU,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAC7G,UAAQ,WAAW,KAAK,CAAC,SAAS,kBAAkB,KAAK,IAAI,CAAC,KAAK,WAAW,CAAC,KAAK,IAAI,MAAM,MAAM,GAAG,QAAQ;AACjH;AAEO,SAAS,WAAW,OAAe,OAAgC;AACxE,SAAO;AAAA,IACL,MAAM,CAAC,OAAO,IAAI,GAAG,KAAK,EAAE,KAAK,IAAI;AAAA,IACrC,UAAU,CAAC,MAAM,KAAK,IAAI,IAAI,GAAG,KAAK,EAAE,KAAK,IAAI;AAAA,EACnD;AACF;AAEO,SAAS,kBAAkB,MAQhB;AAChB,QAAM,UAA2B,KAAK,KAAK,IAAI,CAAC,SAAS;AAAA,IACvD,IAAI,GAAG,KAAK,QAAQ,IAAI,IAAI,IAAI;AAAA,IAChC,OAAO,iBAAiB,GAAG;AAAA,IAC3B,aAAa,IAAI;AAAA,IACjB,SAAS,IAAI,IAAI,IAAI;AAAA,EACvB,EAAE;AACF,QAAM,OAAO,WAAW,KAAK,OAAO,CAAC,KAAK,QAAQ,CAAC;AACnD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM,KAAK,OAAO,SAAS;AAAA,IAC3B,UAAU,CAAC,KAAK,UAAU,IAAI,GAAI,KAAK,WAAW,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAE,EAAE,KAAK,IAAI;AAAA,IACtF,MAAM,KAAK,QAAQ,KAAK,aAAa;AAAA,EAAK,KAAK,UAAU,KAAK;AAAA,IAC9D;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,12 +1,13 @@
1
+ import { patchJson, readJson } from "@sfmc-bds/sdk/node/config";
1
2
  import {
2
3
  createPanel,
4
+ getPanel,
3
5
  putMenu,
4
6
  updatePanel,
5
7
  updatePanelTarget
6
8
  } from "@sfmc-bds/sdk/node/qq-official";
7
- import { patchJson } from "@sfmc-bds/sdk/node/config";
8
- import { log } from "../log.js";
9
9
  import { CFG_PATH } from "../config.js";
10
+ import { log } from "../log.js";
10
11
  import { pickDisplayLabel } from "./menu-format.js";
11
12
  const MAX_MENU_ITEMS = 10;
12
13
  const MAX_PANEL_ITEMS = 20;
@@ -26,8 +27,7 @@ function buildMenuItems(registry) {
26
27
  }
27
28
  function buildPanelItems(registry) {
28
29
  const user = registry.userMenu();
29
- const admin = registry.adminMenu();
30
- const merged = [...user, ...admin].slice(0, MAX_PANEL_ITEMS);
30
+ const merged = user.slice(0, MAX_PANEL_ITEMS);
31
31
  return merged.map((c) => ({
32
32
  type: "command",
33
33
  // 填入输入框的内容:用 /name 便于 router 命中
@@ -47,6 +47,21 @@ function extractPanelId(json) {
47
47
  }
48
48
  return "";
49
49
  }
50
+ function panelTargetInfo(raw) {
51
+ if (!raw || typeof raw !== "object") return null;
52
+ const root = raw;
53
+ const data = root["data"];
54
+ const panel = data && typeof data === "object" ? data : root;
55
+ const scope = panel["scope"];
56
+ const targetType = panel["target_type"];
57
+ if (typeof scope !== "string" || typeof targetType !== "string") return null;
58
+ const groups = panel["group_openids"];
59
+ return {
60
+ scope,
61
+ targetType,
62
+ groupOpenids: Array.isArray(groups) ? groups.filter((id) => typeof id === "string") : []
63
+ };
64
+ }
50
65
  async function syncMenuAndPanel(opts) {
51
66
  const items = buildMenuItems(opts.registry);
52
67
  const panelItems = buildPanelItems(opts.registry);
@@ -64,10 +79,12 @@ async function syncMenuAndPanel(opts) {
64
79
  remark: "SFMC"
65
80
  };
66
81
  let panelId = opts.panelId.trim();
82
+ let updatedExisting = false;
67
83
  if (panelId) {
68
84
  const upd = await updatePanel(opts.creds, panelId, { panel: panelBody });
69
85
  if (upd.ok) {
70
86
  panelOk = true;
87
+ updatedExisting = true;
71
88
  log.info(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u5DF2\u66F4\u65B0 id=${panelId}`);
72
89
  } else {
73
90
  log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u66F4\u65B0\u5931\u8D25\uFF0C\u5C06\u5C1D\u8BD5\u65B0\u5EFA: ${upd.error}`);
@@ -96,13 +113,21 @@ async function syncMenuAndPanel(opts) {
96
113
  log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u521B\u5EFA\u5931\u8D25: ${created.error}`);
97
114
  }
98
115
  }
99
- if (panelOk && panelId && opts.groupOpenid) {
100
- const tgt = await updatePanelTarget(opts.creds, panelId, {
101
- op: "add",
102
- group_openids: [opts.groupOpenid]
103
- });
104
- if (!tgt.ok) {
105
- log.warn(`\u5B98\u65B9\u9762\u677F\u5173\u8054\u7FA4\u8DF3\u8FC7/\u5931\u8D25: ${tgt.error}`);
116
+ if (updatedExisting && panelId && opts.groupOpenid) {
117
+ const detail = await getPanel(opts.creds, panelId);
118
+ if (!detail.ok) {
119
+ log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u8BE6\u60C5\u67E5\u8BE2\u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7\u5173\u8054\u7FA4: ${detail.error}`);
120
+ } else {
121
+ const target = panelTargetInfo(detail.json ?? safeParse(detail.body));
122
+ if (!target) {
123
+ log.warn("\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u8BE6\u60C5\u7F3A\u5C11 scope/target_type\uFF0C\u5DF2\u8DF3\u8FC7\u5173\u8054\u7FA4");
124
+ } else if (target.scope === "group" && target.targetType === "specific" && !target.groupOpenids.includes(opts.groupOpenid)) {
125
+ const linked = await updatePanelTarget(opts.creds, panelId, {
126
+ op: "add",
127
+ group_openids: [opts.groupOpenid]
128
+ });
129
+ if (!linked.ok) log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u5173\u8054\u7FA4\u5931\u8D25: ${linked.error}`);
130
+ }
106
131
  }
107
132
  }
108
133
  return { menuOk, panelOk };
@@ -116,9 +141,10 @@ function safeParse(body) {
116
141
  }
117
142
  function persistPanelIdToConfig(panelId) {
118
143
  try {
119
- patchJson(CFG_PATH, { qq_group_panel_id: panelId });
144
+ const current = readJson(CFG_PATH) ?? {};
145
+ patchJson(CFG_PATH, { official: { ...current.official, group_panel_id: panelId } });
120
146
  } catch (e) {
121
- log.warn(`\u5199\u56DE qq_group_panel_id \u5931\u8D25: ${e.message}`);
147
+ log.warn(`\u5199\u56DE official.group_panel_id \u5931\u8D25: ${e.message}`);
122
148
  }
123
149
  }
124
150
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/commands/menu-panel-sync.ts"],
4
- "sourcesContent": ["/**\n * commands/menu-panel-sync.ts \u2014 CommandRegistry \u2192 \u5B98\u65B9\u83DC\u5355 / \u7FA4\u6307\u4EE4\u9762\u677F\n *\n * \u5B98\u65B9\u5951\u7EA6\uFF08autogen\uFF09\uFF1A\n * PUT /v2/menu body: { menu: { items } }\n * POST /v2/panels body: { scope, target_type?, group_openids?, panel: { items, remark? } }\n * PUT /v2/panels/{id} body: { panel: { items, remark? } }\n */\n\nimport {\n createPanel,\n putMenu,\n updatePanel,\n updatePanelTarget,\n type QqMenuItem,\n type QqOfficialCredentials,\n type QqPanelItem,\n} from \"@sfmc-bds/sdk/node/qq-official\";\nimport { patchJson } from \"@sfmc-bds/sdk/node/config\";\nimport { log } from \"../log.js\";\nimport { CFG_PATH } from \"../config.js\";\nimport { pickDisplayLabel } from \"./menu-format.js\";\nimport type { CommandRegistry } from \"./registry.js\";\nimport type { RegisteredCommand } from \"./types.js\";\n\nconst MAX_MENU_ITEMS = 10;\nconst MAX_PANEL_ITEMS = 20;\n\nexport type MenuPanelSyncOpts = {\n creds: QqOfficialCredentials;\n registry: CommandRegistry;\n groupOpenid: string;\n /** \u5DF2\u6709\u9762\u677F id\uFF1B\u7A7A\u5219 POST \u65B0\u5EFA */\n panelId: string;\n /** sync \u6210\u529F\u540E\u5199\u56DE panel id */\n persistPanelId?: (panelId: string) => void;\n};\n\n/** \u9009\u4E00\u4E2A\u9002\u5408\u5C55\u793A/\u586B\u5165\u8F93\u5165\u6846\u7684\u77ED\u89E6\u53D1\u8BCD\uFF08\u4F18\u5148\u4E2D\u6587\u522B\u540D\uFF09 */\nfunction pickTriggerLabel(cmd: RegisteredCommand, maxChars: number): string {\n return pickDisplayLabel(cmd, maxChars);\n}\n\n/** \u4ECE registry \u751F\u6210 C2C \u83DC\u5355\u9879\uFF08send_message\uFF09\uFF1B\u4EC5\u7528\u6237\u5411\u6307\u4EE4 */\nexport function buildMenuItems(registry: CommandRegistry): QqMenuItem[] {\n return registry\n .userMenu()\n .slice(0, MAX_MENU_ITEMS)\n .map((c) => {\n const label = pickTriggerLabel(c, 10);\n return {\n type: \"send_message\" as const,\n name: label,\n // \u70B9\u51FB\u540E\u586B\u5165\u8F93\u5165\u6846\uFF1B\u4E0E router \u89E6\u53D1\u8BCD\u5BF9\u9F50\n send_message: `/${c.name}`,\n };\n });\n}\n\n/**\n * \u4ECE registry \u751F\u6210\u7FA4\u6307\u4EE4\u9762\u677F\u9879\u3002\n * \u5B98\u65B9\uFF1Atype=command \u65F6\u70B9\u51FB\u540E\u628A name \u586B\u5165\u8F93\u5165\u6846\uFF08\u65E0\u72EC\u7ACB command \u5B57\u6BB5\uFF09\u3002\n * \u7528\u6237\u5411\u6307\u4EE4\u4F18\u5148\uFF1B\u5269\u4F59\u540D\u989D\u518D\u8865\u7BA1\u7406\u9879\uFF08\u4FBF\u4E8E\u7BA1\u7406\u5458\u70B9\u9009\uFF09\u3002\n */\nexport function buildPanelItems(registry: CommandRegistry): QqPanelItem[] {\n const user = registry.userMenu();\n const admin = registry.adminMenu();\n const merged = [...user, ...admin].slice(0, MAX_PANEL_ITEMS);\n return merged.map((c) => ({\n type: \"command\" as const,\n // \u586B\u5165\u8F93\u5165\u6846\u7684\u5185\u5BB9\uFF1A\u7528 /name \u4FBF\u4E8E router \u547D\u4E2D\n name: `/${c.name}`.slice(0, 14),\n desc: (c.description || c.name).slice(0, 30),\n }));\n}\n\nfunction extractPanelId(json: unknown): string {\n if (!json || typeof json !== \"object\") return \"\";\n const o = json as Record<string, unknown>;\n for (const key of [\"panel_id\", \"id\", \"panelId\"]) {\n const v = o[key];\n if (typeof v === \"string\" && v.length > 0) return v;\n }\n if (o[\"data\"] && typeof o[\"data\"] === \"object\") {\n return extractPanelId(o[\"data\"]);\n }\n return \"\";\n}\n\n/**\n * \u540C\u6B65\u81EA\u5B9A\u4E49\u83DC\u5355 + \u7FA4\u6307\u4EE4\u9762\u677F\u3002\u5931\u8D25\u53EA\u6253\u65E5\u5FD7\uFF0C\u4E0D\u629B\u7ED9\u8C03\u7528\u65B9\u3002\n */\nexport async function syncMenuAndPanel(opts: MenuPanelSyncOpts): Promise<{ menuOk: boolean; panelOk: boolean }> {\n const items = buildMenuItems(opts.registry);\n const panelItems = buildPanelItems(opts.registry);\n let menuOk = false;\n let panelOk = false;\n\n const menuRes = await putMenu(opts.creds, { menu: { items } });\n if (menuRes.ok) {\n menuOk = true;\n log.info(`\u5B98\u65B9\u81EA\u5B9A\u4E49\u83DC\u5355\u5DF2\u540C\u6B65 (${items.length} \u9879)`);\n } else {\n log.warn(`\u5B98\u65B9\u81EA\u5B9A\u4E49\u83DC\u5355\u540C\u6B65\u5931\u8D25: ${menuRes.error}`);\n }\n\n const panelBody = {\n items: panelItems,\n remark: \"SFMC\",\n };\n\n let panelId = opts.panelId.trim();\n if (panelId) {\n const upd = await updatePanel(opts.creds, panelId, { panel: panelBody });\n if (upd.ok) {\n panelOk = true;\n log.info(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u5DF2\u66F4\u65B0 id=${panelId}`);\n } else {\n log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u66F4\u65B0\u5931\u8D25\uFF0C\u5C06\u5C1D\u8BD5\u65B0\u5EFA: ${upd.error}`);\n panelId = \"\";\n }\n }\n\n if (!panelId) {\n const hasGroup = !!opts.groupOpenid;\n const created = await createPanel(opts.creds, {\n scope: \"group\",\n target_type: hasGroup ? \"specific\" : \"all\",\n ...(hasGroup ? { group_openids: [opts.groupOpenid] } : {}),\n panel: panelBody,\n });\n if (created.ok) {\n panelId = extractPanelId(created.json) || extractPanelId(safeParse(created.body));\n if (panelId) {\n panelOk = true;\n opts.persistPanelId?.(panelId);\n log.info(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u5DF2\u521B\u5EFA id=${panelId}`);\n } else {\n log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u521B\u5EFA\u6210\u529F\u4F46\u672A\u89E3\u6790\u5230 id: ${created.body.slice(0, 160)}`);\n panelOk = true;\n }\n } else {\n log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u521B\u5EFA\u5931\u8D25: ${created.error}`);\n }\n }\n\n // create \u65F6\u5DF2\u5E26 group_openids\uFF1B\u4EC5\u5F53\u540E\u7EED\u66F4\u65B0\u573A\u666F\u6216 create \u672A\u5E26\u7FA4\u65F6\u518D\u5173\u8054\n if (panelOk && panelId && opts.groupOpenid) {\n const tgt = await updatePanelTarget(opts.creds, panelId, {\n op: \"add\",\n group_openids: [opts.groupOpenid],\n });\n if (!tgt.ok) {\n // specific \u9762\u677F\u91CD\u590D add \u6216 all \u9762\u677F\u4F1A\u5931\u8D25\uFF1B\u964D\u7EA7\u4E3A debug \u7EA7 warn\n log.warn(`\u5B98\u65B9\u9762\u677F\u5173\u8054\u7FA4\u8DF3\u8FC7/\u5931\u8D25: ${tgt.error}`);\n }\n }\n\n return { menuOk, panelOk };\n}\n\nfunction safeParse(body: string): unknown {\n try {\n return JSON.parse(body) as unknown;\n } catch {\n return null;\n }\n}\n\n/** \u9ED8\u8BA4\uFF1A\u628A panel id \u6D45\u5408\u5E76\u5199\u56DE qq_config.json */\nexport function persistPanelIdToConfig(panelId: string): void {\n try {\n patchJson(CFG_PATH, { qq_group_panel_id: panelId });\n } catch (e) {\n log.warn(`\u5199\u56DE qq_group_panel_id \u5931\u8D25: ${(e as Error).message}`);\n }\n}\n"],
5
- "mappings": "AASA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,iBAAiB;AAC1B,SAAS,WAAW;AACpB,SAAS,gBAAgB;AACzB,SAAS,wBAAwB;AAIjC,MAAM,iBAAiB;AACvB,MAAM,kBAAkB;AAaxB,SAAS,iBAAiB,KAAwB,UAA0B;AAC1E,SAAO,iBAAiB,KAAK,QAAQ;AACvC;AAGO,SAAS,eAAe,UAAyC;AACtE,SAAO,SACJ,SAAS,EACT,MAAM,GAAG,cAAc,EACvB,IAAI,CAAC,MAAM;AACV,UAAM,QAAQ,iBAAiB,GAAG,EAAE;AACpC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA;AAAA,MAEN,cAAc,IAAI,EAAE,IAAI;AAAA,IAC1B;AAAA,EACF,CAAC;AACL;AAOO,SAAS,gBAAgB,UAA0C;AACxE,QAAM,OAAO,SAAS,SAAS;AAC/B,QAAM,QAAQ,SAAS,UAAU;AACjC,QAAM,SAAS,CAAC,GAAG,MAAM,GAAG,KAAK,EAAE,MAAM,GAAG,eAAe;AAC3D,SAAO,OAAO,IAAI,CAAC,OAAO;AAAA,IACxB,MAAM;AAAA;AAAA,IAEN,MAAM,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,EAAE;AAAA,IAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,GAAG,EAAE;AAAA,EAC7C,EAAE;AACJ;AAEA,SAAS,eAAe,MAAuB;AAC7C,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,QAAM,IAAI;AACV,aAAW,OAAO,CAAC,YAAY,MAAM,SAAS,GAAG;AAC/C,UAAM,IAAI,EAAE,GAAG;AACf,QAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,QAAO;AAAA,EACpD;AACA,MAAI,EAAE,MAAM,KAAK,OAAO,EAAE,MAAM,MAAM,UAAU;AAC9C,WAAO,eAAe,EAAE,MAAM,CAAC;AAAA,EACjC;AACA,SAAO;AACT;AAKA,eAAsB,iBAAiB,MAAyE;AAC9G,QAAM,QAAQ,eAAe,KAAK,QAAQ;AAC1C,QAAM,aAAa,gBAAgB,KAAK,QAAQ;AAChD,MAAI,SAAS;AACb,MAAI,UAAU;AAEd,QAAM,UAAU,MAAM,QAAQ,KAAK,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC7D,MAAI,QAAQ,IAAI;AACd,aAAS;AACT,QAAI,KAAK,iEAAe,MAAM,MAAM,UAAK;AAAA,EAC3C,OAAO;AACL,QAAI,KAAK,uEAAgB,QAAQ,KAAK,EAAE;AAAA,EAC1C;AAEA,QAAM,YAAY;AAAA,IAChB,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAEA,MAAI,UAAU,KAAK,QAAQ,KAAK;AAChC,MAAI,SAAS;AACX,UAAM,MAAM,MAAM,YAAY,KAAK,OAAO,SAAS,EAAE,OAAO,UAAU,CAAC;AACvE,QAAI,IAAI,IAAI;AACV,gBAAU;AACV,UAAI,KAAK,mEAAiB,OAAO,EAAE;AAAA,IACrC,OAAO;AACL,UAAI,KAAK,2GAAsB,IAAI,KAAK,EAAE;AAC1C,gBAAU;AAAA,IACZ;AAAA,EACF;AAEA,MAAI,CAAC,SAAS;AACZ,UAAM,WAAW,CAAC,CAAC,KAAK;AACxB,UAAM,UAAU,MAAM,YAAY,KAAK,OAAO;AAAA,MAC5C,OAAO;AAAA,MACP,aAAa,WAAW,aAAa;AAAA,MACrC,GAAI,WAAW,EAAE,eAAe,CAAC,KAAK,WAAW,EAAE,IAAI,CAAC;AAAA,MACxD,OAAO;AAAA,IACT,CAAC;AACD,QAAI,QAAQ,IAAI;AACd,gBAAU,eAAe,QAAQ,IAAI,KAAK,eAAe,UAAU,QAAQ,IAAI,CAAC;AAChF,UAAI,SAAS;AACX,kBAAU;AACV,aAAK,iBAAiB,OAAO;AAC7B,YAAI,KAAK,mEAAiB,OAAO,EAAE;AAAA,MACrC,OAAO;AACL,YAAI,KAAK,wGAAwB,QAAQ,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAC7D,kBAAU;AAAA,MACZ;AAAA,IACF,OAAO;AACL,UAAI,KAAK,uEAAgB,QAAQ,KAAK,EAAE;AAAA,IAC1C;AAAA,EACF;AAGA,MAAI,WAAW,WAAW,KAAK,aAAa;AAC1C,UAAM,MAAM,MAAM,kBAAkB,KAAK,OAAO,SAAS;AAAA,MACvD,IAAI;AAAA,MACJ,eAAe,CAAC,KAAK,WAAW;AAAA,IAClC,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AAEX,UAAI,KAAK,wEAAiB,IAAI,KAAK,EAAE;AAAA,IACvC;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,QAAQ;AAC3B;AAEA,SAAS,UAAU,MAAuB;AACxC,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,uBAAuB,SAAuB;AAC5D,MAAI;AACF,cAAU,UAAU,EAAE,mBAAmB,QAAQ,CAAC;AAAA,EACpD,SAAS,GAAG;AACV,QAAI,KAAK,gDAA6B,EAAY,OAAO,EAAE;AAAA,EAC7D;AACF;",
4
+ "sourcesContent": ["/**\n * commands/menu-panel-sync.ts \u2014 CommandRegistry \u2192 \u5B98\u65B9\u83DC\u5355 / \u7FA4\u6307\u4EE4\u9762\u677F\n *\n * \u5B98\u65B9\u5951\u7EA6\uFF08autogen\uFF09\uFF1A\n * PUT /v2/menu body: { menu: { items } }\n * POST /v2/panels body: { scope, target_type?, group_openids?, panel: { items, remark? } }\n * PUT /v2/panels/{id} body: { panel: { items, remark? } }\n */\n\nimport { patchJson, readJson } from \"@sfmc-bds/sdk/node/config\";\nimport {\n createPanel,\n getPanel,\n putMenu,\n updatePanel,\n updatePanelTarget,\n type QqMenuItem,\n type QqOfficialCredentials,\n type QqPanelItem,\n} from \"@sfmc-bds/sdk/node/qq-official\";\nimport { CFG_PATH } from \"../config.js\";\nimport { log } from \"../log.js\";\nimport { pickDisplayLabel } from \"./menu-format.js\";\nimport type { CommandRegistry } from \"./registry.js\";\nimport type { RegisteredCommand } from \"./types.js\";\n\nconst MAX_MENU_ITEMS = 10;\nconst MAX_PANEL_ITEMS = 20;\n\nexport type MenuPanelSyncOpts = {\n creds: QqOfficialCredentials;\n registry: CommandRegistry;\n groupOpenid: string;\n /** \u5DF2\u6709\u9762\u677F id\uFF1B\u7A7A\u5219 POST \u65B0\u5EFA */\n panelId: string;\n /** sync \u6210\u529F\u540E\u5199\u56DE panel id */\n persistPanelId?: (panelId: string) => void;\n};\n\n/** \u9009\u4E00\u4E2A\u9002\u5408\u5C55\u793A/\u586B\u5165\u8F93\u5165\u6846\u7684\u77ED\u89E6\u53D1\u8BCD\uFF08\u4F18\u5148\u4E2D\u6587\u522B\u540D\uFF09 */\nfunction pickTriggerLabel(cmd: RegisteredCommand, maxChars: number): string {\n return pickDisplayLabel(cmd, maxChars);\n}\n\n/** \u4ECE registry \u751F\u6210 C2C \u83DC\u5355\u9879\uFF08send_message\uFF09\uFF1B\u4EC5\u7528\u6237\u5411\u6307\u4EE4 */\nexport function buildMenuItems(registry: CommandRegistry): QqMenuItem[] {\n return registry\n .userMenu()\n .slice(0, MAX_MENU_ITEMS)\n .map((c) => {\n const label = pickTriggerLabel(c, 10);\n return {\n type: \"send_message\" as const,\n name: label,\n // \u70B9\u51FB\u540E\u586B\u5165\u8F93\u5165\u6846\uFF1B\u4E0E router \u89E6\u53D1\u8BCD\u5BF9\u9F50\n send_message: `/${c.name}`,\n };\n });\n}\n\n/**\n * \u4ECE registry \u751F\u6210\u7FA4\u6307\u4EE4\u9762\u677F\u9879\u3002\n * \u5B98\u65B9\uFF1Atype=command \u65F6\u70B9\u51FB\u540E\u628A name \u586B\u5165\u8F93\u5165\u6846\uFF08\u65E0\u72EC\u7ACB command \u5B57\u6BB5\uFF09\u3002\n * \u5168\u7FA4\u5171\u4EAB\u9762\u677F\u53EA\u5C55\u793A\u73A9\u5BB6\u5165\u53E3\uFF1B\u7BA1\u7406\u5165\u53E3\u5728\u4E2A\u4EBA\u83DC\u5355\u9274\u6743\u540E\u663E\u793A\u3002\n */\nexport function buildPanelItems(registry: CommandRegistry): QqPanelItem[] {\n const user = registry.userMenu();\n const merged = user.slice(0, MAX_PANEL_ITEMS);\n return merged.map((c) => ({\n type: \"command\" as const,\n // \u586B\u5165\u8F93\u5165\u6846\u7684\u5185\u5BB9\uFF1A\u7528 /name \u4FBF\u4E8E router \u547D\u4E2D\n name: `/${c.name}`.slice(0, 14),\n desc: (c.description || c.name).slice(0, 30),\n }));\n}\n\nfunction extractPanelId(json: unknown): string {\n if (!json || typeof json !== \"object\") return \"\";\n const o = json as Record<string, unknown>;\n for (const key of [\"panel_id\", \"id\", \"panelId\"]) {\n const v = o[key];\n if (typeof v === \"string\" && v.length > 0) return v;\n }\n if (o[\"data\"] && typeof o[\"data\"] === \"object\") {\n return extractPanelId(o[\"data\"]);\n }\n return \"\";\n}\n\nfunction panelTargetInfo(raw: unknown): { scope: string; targetType: string; groupOpenids: string[] } | null {\n if (!raw || typeof raw !== \"object\") return null;\n const root = raw as Record<string, unknown>;\n const data = root[\"data\"];\n const panel = data && typeof data === \"object\" ? (data as Record<string, unknown>) : root;\n const scope = panel[\"scope\"];\n const targetType = panel[\"target_type\"];\n if (typeof scope !== \"string\" || typeof targetType !== \"string\") return null;\n const groups = panel[\"group_openids\"];\n return {\n scope,\n targetType,\n groupOpenids: Array.isArray(groups) ? groups.filter((id): id is string => typeof id === \"string\") : [],\n };\n}\n\n/**\n * \u540C\u6B65\u81EA\u5B9A\u4E49\u83DC\u5355 + \u7FA4\u6307\u4EE4\u9762\u677F\u3002\u5931\u8D25\u53EA\u6253\u65E5\u5FD7\uFF0C\u4E0D\u629B\u7ED9\u8C03\u7528\u65B9\u3002\n */\nexport async function syncMenuAndPanel(opts: MenuPanelSyncOpts): Promise<{ menuOk: boolean; panelOk: boolean }> {\n const items = buildMenuItems(opts.registry);\n const panelItems = buildPanelItems(opts.registry);\n let menuOk = false;\n let panelOk = false;\n\n const menuRes = await putMenu(opts.creds, { menu: { items } });\n if (menuRes.ok) {\n menuOk = true;\n log.info(`\u5B98\u65B9\u81EA\u5B9A\u4E49\u83DC\u5355\u5DF2\u540C\u6B65 (${items.length} \u9879)`);\n } else {\n log.warn(`\u5B98\u65B9\u81EA\u5B9A\u4E49\u83DC\u5355\u540C\u6B65\u5931\u8D25: ${menuRes.error}`);\n }\n\n const panelBody = {\n items: panelItems,\n remark: \"SFMC\",\n };\n\n let panelId = opts.panelId.trim();\n let updatedExisting = false;\n if (panelId) {\n const upd = await updatePanel(opts.creds, panelId, { panel: panelBody });\n if (upd.ok) {\n panelOk = true;\n updatedExisting = true;\n log.info(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u5DF2\u66F4\u65B0 id=${panelId}`);\n } else {\n log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u66F4\u65B0\u5931\u8D25\uFF0C\u5C06\u5C1D\u8BD5\u65B0\u5EFA: ${upd.error}`);\n panelId = \"\";\n }\n }\n\n if (!panelId) {\n const hasGroup = !!opts.groupOpenid;\n const created = await createPanel(opts.creds, {\n scope: \"group\",\n target_type: hasGroup ? \"specific\" : \"all\",\n ...(hasGroup ? { group_openids: [opts.groupOpenid] } : {}),\n panel: panelBody,\n });\n if (created.ok) {\n panelId = extractPanelId(created.json) || extractPanelId(safeParse(created.body));\n if (panelId) {\n panelOk = true;\n opts.persistPanelId?.(panelId);\n log.info(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u5DF2\u521B\u5EFA id=${panelId}`);\n } else {\n log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u521B\u5EFA\u6210\u529F\u4F46\u672A\u89E3\u6790\u5230 id: ${created.body.slice(0, 160)}`);\n panelOk = true;\n }\n } else {\n log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u521B\u5EFA\u5931\u8D25: ${created.error}`);\n }\n }\n\n // \u65B0\u5EFA specific \u9762\u677F\u5DF2\u5E26\u7FA4\uFF1B\u5DF2\u6709\u9762\u677F\u9700\u5148\u786E\u8BA4\u7C7B\u578B\uFF0C\u4E0D\u80FD\u7ED9 all \u9762\u677F\u8FFD\u52A0\u5173\u8054\u5BF9\u8C61\u3002\n if (updatedExisting && panelId && opts.groupOpenid) {\n const detail = await getPanel(opts.creds, panelId);\n if (!detail.ok) {\n log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u8BE6\u60C5\u67E5\u8BE2\u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7\u5173\u8054\u7FA4: ${detail.error}`);\n } else {\n const target = panelTargetInfo(detail.json ?? safeParse(detail.body));\n if (!target) {\n log.warn(\"\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u8BE6\u60C5\u7F3A\u5C11 scope/target_type\uFF0C\u5DF2\u8DF3\u8FC7\u5173\u8054\u7FA4\");\n } else if (\n target.scope === \"group\" &&\n target.targetType === \"specific\" &&\n !target.groupOpenids.includes(opts.groupOpenid)\n ) {\n const linked = await updatePanelTarget(opts.creds, panelId, {\n op: \"add\",\n group_openids: [opts.groupOpenid],\n });\n if (!linked.ok) log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u5173\u8054\u7FA4\u5931\u8D25: ${linked.error}`);\n }\n }\n }\n\n return { menuOk, panelOk };\n}\n\nfunction safeParse(body: string): unknown {\n try {\n return JSON.parse(body) as unknown;\n } catch {\n return null;\n }\n}\n\n/** \u628A panel id \u5199\u56DE official \u5206\u7EC4\uFF0C\u540C\u65F6\u4FDD\u7559\u5176\u4ED6\u5B98\u65B9\u8BBE\u7F6E\u3002 */\nexport function persistPanelIdToConfig(panelId: string): void {\n try {\n const current = readJson<{ official?: Record<string, unknown> }>(CFG_PATH) ?? {};\n patchJson(CFG_PATH, { official: { ...current.official, group_panel_id: panelId } });\n } catch (e) {\n log.warn(`\u5199\u56DE official.group_panel_id \u5931\u8D25: ${(e as Error).message}`);\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,91 @@
1
+ function officialPlainFallback(result) {
2
+ if (!result.menu || !result.buttons?.length) return result.text;
3
+ const items = result.buttons.filter((button) => button.command !== "/menu").map((button) => `${button.label}${button.description ? ` \xB7 ${button.description}` : ""}`);
4
+ const home = result.buttons.some((button) => button.command === "/menu") ? ["", "\u53D1\u9001\u300C\u83DC\u5355\u300D\u8FD4\u56DE\u9996\u9875\u3002"] : [];
5
+ return [result.text, "", ...items, ...home].join("\n");
6
+ }
7
+ function isTableRow(line) {
8
+ return line.trim().startsWith("|");
9
+ }
10
+ function isTableSeparator(line) {
11
+ const trimmed = line.trim();
12
+ if (!trimmed.includes("|")) return false;
13
+ const cells = trimmed.replace(/^\|/, "").replace(/\|$/, "").split("|");
14
+ return cells.length > 0 && cells.every((cell) => /^\s*:?-{3,}:?\s*$/.test(cell));
15
+ }
16
+ function byteLength(lines) {
17
+ return Buffer.byteLength(lines.join("\n"), "utf8");
18
+ }
19
+ function splitMarkdownMessage(markdown, maxBytes = 3e3) {
20
+ const lines = markdown.replace(/\r\n/g, "\n").split("\n");
21
+ const parts = [];
22
+ let bucket = [];
23
+ let tableHeader = [];
24
+ let sectionTitle = "";
25
+ const flush = () => {
26
+ const text = bucket.join("\n").trim();
27
+ if (text) parts.push(text);
28
+ bucket = [];
29
+ };
30
+ const seedContinuation = () => {
31
+ const seed = [];
32
+ if (sectionTitle) seed.push(`${sectionTitle}\uFF08\u7EED\uFF09`, "");
33
+ if (tableHeader.length) seed.push(...tableHeader);
34
+ bucket = seed;
35
+ };
36
+ for (let index = 0; index < lines.length; index++) {
37
+ const line = lines[index] ?? "";
38
+ const next = lines[index + 1];
39
+ const headerPair = isTableRow(line) && next !== void 0 && isTableSeparator(next) ? [line, next] : null;
40
+ const incoming = headerPair ?? [line];
41
+ if (bucket.length > 0 && byteLength([...bucket, ...incoming]) > maxBytes) {
42
+ flush();
43
+ if (!headerPair && isTableRow(line) && !isTableSeparator(line) && tableHeader.length) seedContinuation();
44
+ }
45
+ if (headerPair) {
46
+ tableHeader = headerPair;
47
+ bucket.push(...headerPair);
48
+ index += 1;
49
+ continue;
50
+ }
51
+ if (/^###\s+/.test(line)) {
52
+ sectionTitle = line;
53
+ tableHeader = [];
54
+ } else if (/^##\s+/.test(line)) {
55
+ sectionTitle = "";
56
+ tableHeader = [];
57
+ }
58
+ bucket.push(line);
59
+ }
60
+ flush();
61
+ return parts.length ? parts : [""];
62
+ }
63
+ function splitMessage(text, maxBytes = 3e3) {
64
+ const parts = [];
65
+ let current = "";
66
+ for (const line of text.split(/(?<=\n)/u)) {
67
+ if (Buffer.byteLength(current + line, "utf8") <= maxBytes) {
68
+ current += line;
69
+ continue;
70
+ }
71
+ if (current) {
72
+ parts.push(current);
73
+ current = "";
74
+ }
75
+ for (const char of line) {
76
+ if (Buffer.byteLength(current + char, "utf8") > maxBytes) {
77
+ parts.push(current);
78
+ current = "";
79
+ }
80
+ current += char;
81
+ }
82
+ }
83
+ if (current) parts.push(current);
84
+ return parts.length ? parts : [""];
85
+ }
86
+ export {
87
+ officialPlainFallback,
88
+ splitMarkdownMessage,
89
+ splitMessage
90
+ };
91
+ //# sourceMappingURL=message-text.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/commands/message-text.ts"],
4
+ "sourcesContent": ["import type { CommandResult } from \"./types.js\";\n\n/** \u5B98\u65B9 Markdown \u53D1\u9001\u5931\u8D25\u65F6\uFF0C\u83DC\u5355\u4EE5\u7B80\u6D01\u6587\u672C\u5448\u73B0\uFF1B\u6309\u94AE\u547D\u4EE4\u4E0D\u91CD\u590D\u5199\u5165\u6B63\u6587\u3002 */\nexport function officialPlainFallback(result: CommandResult): string {\n if (!result.menu || !result.buttons?.length) return result.text;\n const items = result.buttons\n .filter((button) => button.command !== \"/menu\")\n .map((button) => `${button.label}${button.description ? ` \u00B7 ${button.description}` : \"\"}`);\n const home = result.buttons.some((button) => button.command === \"/menu\")\n ? [\"\", \"\u53D1\u9001\u300C\u83DC\u5355\u300D\u8FD4\u56DE\u9996\u9875\u3002\"]\n : [];\n return [result.text, \"\", ...items, ...home].join(\"\\n\");\n}\n\nfunction isTableRow(line: string): boolean {\n return line.trim().startsWith(\"|\");\n}\n\n/** \u8868\u683C\u5206\u9694\u884C\uFF0C\u4F8B\u5982 `| --- | --- |`\u3002\u666E\u901A `---` \u5206\u5272\u7EBF\u4E0D\u7B97\u3002 */\nfunction isTableSeparator(line: string): boolean {\n const trimmed = line.trim();\n if (!trimmed.includes(\"|\")) return false;\n const cells = trimmed.replace(/^\\|/, \"\").replace(/\\|$/, \"\").split(\"|\");\n return cells.length > 0 && cells.every((cell) => /^\\s*:?-{3,}:?\\s*$/.test(cell));\n}\n\nfunction byteLength(lines: string[]): number {\n return Buffer.byteLength(lines.join(\"\\n\"), \"utf8\");\n}\n\n/**\n * \u6309\u884C\u62C6\u5206 Markdown\uFF0C\u5E76\u4FDD\u8BC1\u6BCF\u4E00\u6BB5\u91CC\u7684\u8868\u683C\u4ECD\u7136\u5B8C\u6574\u3002\n * \u4E00\u5F20\u8868\u8D85\u957F\u65F6\uFF0C\u4E0B\u4E00\u6BB5\u91CD\u590D\u8868\u5934\uFF0C\u5E76\u5728\u5C0F\u8282\u6807\u9898\u540E\u6807\u660E\u300C\u7EED\u300D\u3002\n * \u5F53\u524D\u7ED9 QQ \u4E16\u754C\u5305\u8868\u683C\u4F7F\u7528\uFF1A\u5B98\u65B9\u6D88\u606F\u6309\u6BB5\u53D1\u9001\uFF0C\u534A\u5F20\u8868\u65E0\u6CD5\u6E32\u67D3\u3002\n */\nexport function splitMarkdownMessage(markdown: string, maxBytes = 3000): string[] {\n const lines = markdown.replace(/\\r\\n/g, \"\\n\").split(\"\\n\");\n const parts: string[] = [];\n let bucket: string[] = [];\n let tableHeader: string[] = [];\n let sectionTitle = \"\";\n\n const flush = (): void => {\n const text = bucket.join(\"\\n\").trim();\n if (text) parts.push(text);\n bucket = [];\n };\n\n const seedContinuation = (): void => {\n const seed: string[] = [];\n if (sectionTitle) seed.push(`${sectionTitle}\uFF08\u7EED\uFF09`, \"\");\n if (tableHeader.length) seed.push(...tableHeader);\n bucket = seed;\n };\n\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? \"\";\n const next = lines[index + 1];\n const headerPair = isTableRow(line) && next !== undefined && isTableSeparator(next) ? [line, next] : null;\n const incoming = headerPair ?? [line];\n if (bucket.length > 0 && byteLength([...bucket, ...incoming]) > maxBytes) {\n flush();\n if (!headerPair && isTableRow(line) && !isTableSeparator(line) && tableHeader.length) seedContinuation();\n }\n if (headerPair) {\n tableHeader = headerPair;\n bucket.push(...headerPair);\n index += 1;\n continue;\n }\n if (/^###\\s+/.test(line)) {\n sectionTitle = line;\n tableHeader = [];\n } else if (/^##\\s+/.test(line)) {\n sectionTitle = \"\";\n tableHeader = [];\n }\n bucket.push(line);\n }\n flush();\n return parts.length ? parts : [\"\"];\n}\n\n/** \u4FDD\u5B88\u7684 UTF-8 \u5B57\u8282\u9884\u7B97\uFF1B\u4F18\u5148\u6309\u884C\u62C6\u5206\uFF0C\u8D85\u957F\u884C\u6309\u7801\u70B9\u62C6\u5206\u3002 */\nexport function splitMessage(text: string, maxBytes = 3000): string[] {\n const parts: string[] = [];\n let current = \"\";\n for (const line of text.split(/(?<=\\n)/u)) {\n if (Buffer.byteLength(current + line, \"utf8\") <= maxBytes) {\n current += line;\n continue;\n }\n if (current) {\n parts.push(current);\n current = \"\";\n }\n for (const char of line) {\n if (Buffer.byteLength(current + char, \"utf8\") > maxBytes) {\n parts.push(current);\n current = \"\";\n }\n current += char;\n }\n }\n if (current) parts.push(current);\n return parts.length ? parts : [\"\"];\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,27 +1,34 @@
1
- const DEFAULT_TTL_MS = 6e4;
2
1
  class PendingChoiceStore {
3
- map = /* @__PURE__ */ new Map();
4
- ttlMs;
5
- constructor(ttlMs = DEFAULT_TTL_MS) {
2
+ constructor(ttlMs = 6e4) {
6
3
  this.ttlMs = ttlMs;
7
4
  }
5
+ ttlMs;
6
+ map = /* @__PURE__ */ new Map();
8
7
  key(backend, groupId, userId) {
9
- return `${backend}:${groupId}:${userId}`;
8
+ return JSON.stringify([backend, groupId, userId]);
10
9
  }
11
10
  set(backend, groupId, userId, choices) {
12
- this.map.set(this.key(backend, groupId, userId), {
13
- choices,
14
- expiresAt: Date.now() + this.ttlMs
15
- });
11
+ for (const [key, entry] of this.map) {
12
+ if (entry.expiresAt + this.ttlMs < Date.now()) this.map.delete(key);
13
+ }
14
+ this.map.set(this.key(backend, groupId, userId), { choices, expiresAt: Date.now() + this.ttlMs });
15
+ }
16
+ choose(backend, groupId, userId, n) {
17
+ const key = this.key(backend, groupId, userId);
18
+ const entry = this.map.get(key);
19
+ if (!entry) return { kind: "missing" };
20
+ if (Date.now() >= entry.expiresAt) {
21
+ this.map.delete(key);
22
+ return { kind: "expired" };
23
+ }
24
+ const command = entry.choices.get(n);
25
+ if (!command) return { kind: "invalid" };
26
+ this.map.delete(key);
27
+ return { kind: "chosen", command };
16
28
  }
17
- /** 取编号对应命令;命中或过期都会清掉该会话 */
18
29
  take(backend, groupId, userId, n) {
19
- const k = this.key(backend, groupId, userId);
20
- const entry = this.map.get(k);
21
- if (!entry) return null;
22
- this.map.delete(k);
23
- if (Date.now() > entry.expiresAt) return null;
24
- return entry.choices.get(n) ?? null;
30
+ const result = this.choose(backend, groupId, userId, n);
31
+ return result.kind === "chosen" ? result.command : null;
25
32
  }
26
33
  clear(backend, groupId, userId) {
27
34
  this.map.delete(this.key(backend, groupId, userId));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/commands/pending.ts"],
4
- "sourcesContent": ["/**\n * commands/pending.ts \u2014 llbot \u7F16\u53F7\u83DC\u5355\u77ED\u671F\u4F1A\u8BDD\n */\n\nconst DEFAULT_TTL_MS = 60_000;\n\nexport type PendingChoice = {\n /** \u7F16\u53F7 \u2192 \u89C4\u8303\u5316\u547D\u4EE4\uFF08\u5982 `/ping`\uFF09 */\n choices: Map<number, string>;\n expiresAt: number;\n};\n\nexport class PendingChoiceStore {\n private readonly map = new Map<string, PendingChoice>();\n private readonly ttlMs: number;\n\n constructor(ttlMs = DEFAULT_TTL_MS) {\n this.ttlMs = ttlMs;\n }\n\n private key(backend: string, groupId: string, userId: string): string {\n return `${backend}:${groupId}:${userId}`;\n }\n\n set(backend: string, groupId: string, userId: string, choices: Map<number, string>): void {\n this.map.set(this.key(backend, groupId, userId), {\n choices,\n expiresAt: Date.now() + this.ttlMs,\n });\n }\n\n /** \u53D6\u7F16\u53F7\u5BF9\u5E94\u547D\u4EE4\uFF1B\u547D\u4E2D\u6216\u8FC7\u671F\u90FD\u4F1A\u6E05\u6389\u8BE5\u4F1A\u8BDD */\n take(backend: string, groupId: string, userId: string, n: number): string | null {\n const k = this.key(backend, groupId, userId);\n const entry = this.map.get(k);\n if (!entry) return null;\n this.map.delete(k);\n if (Date.now() > entry.expiresAt) return null;\n return entry.choices.get(n) ?? null;\n }\n\n clear(backend: string, groupId: string, userId: string): void {\n this.map.delete(this.key(backend, groupId, userId));\n }\n}\n"],
5
- "mappings": "AAIA,MAAM,iBAAiB;AAQhB,MAAM,mBAAmB;AAAA,EACb,MAAM,oBAAI,IAA2B;AAAA,EACrC;AAAA,EAEjB,YAAY,QAAQ,gBAAgB;AAClC,SAAK,QAAQ;AAAA,EACf;AAAA,EAEQ,IAAI,SAAiB,SAAiB,QAAwB;AACpE,WAAO,GAAG,OAAO,IAAI,OAAO,IAAI,MAAM;AAAA,EACxC;AAAA,EAEA,IAAI,SAAiB,SAAiB,QAAgB,SAAoC;AACxF,SAAK,IAAI,IAAI,KAAK,IAAI,SAAS,SAAS,MAAM,GAAG;AAAA,MAC/C;AAAA,MACA,WAAW,KAAK,IAAI,IAAI,KAAK;AAAA,IAC/B,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,KAAK,SAAiB,SAAiB,QAAgB,GAA0B;AAC/E,UAAM,IAAI,KAAK,IAAI,SAAS,SAAS,MAAM;AAC3C,UAAM,QAAQ,KAAK,IAAI,IAAI,CAAC;AAC5B,QAAI,CAAC,MAAO,QAAO;AACnB,SAAK,IAAI,OAAO,CAAC;AACjB,QAAI,KAAK,IAAI,IAAI,MAAM,UAAW,QAAO;AACzC,WAAO,MAAM,QAAQ,IAAI,CAAC,KAAK;AAAA,EACjC;AAAA,EAEA,MAAM,SAAiB,SAAiB,QAAsB;AAC5D,SAAK,IAAI,OAAO,KAAK,IAAI,SAAS,SAAS,MAAM,CAAC;AAAA,EACpD;AACF;",
4
+ "sourcesContent": ["/** \u7F16\u53F7\u83DC\u5355\u6309\u540E\u7AEF\u3001\u7FA4\u548C\u7528\u6237\u9694\u79BB\uFF1B\u9519\u8BEF\u8F93\u5165\u4E0D\u6D88\u8D39\u83DC\u5355\u3002 */\nexport type PendingChoice = { choices: Map<number, string>; expiresAt: number };\nexport type ChoiceResult = { kind: \"chosen\"; command: string } | { kind: \"missing\" | \"expired\" | \"invalid\" };\nexport class PendingChoiceStore {\n private readonly map = new Map<string, PendingChoice>();\n constructor(private readonly ttlMs = 60_000) {}\n private key(backend: string, groupId: string, userId: string): string {\n return JSON.stringify([backend, groupId, userId]);\n }\n set(backend: string, groupId: string, userId: string, choices: Map<number, string>): void {\n for (const [key, entry] of this.map) {\n if (entry.expiresAt + this.ttlMs < Date.now()) this.map.delete(key);\n }\n this.map.set(this.key(backend, groupId, userId), { choices, expiresAt: Date.now() + this.ttlMs });\n }\n choose(backend: string, groupId: string, userId: string, n: number): ChoiceResult {\n const key = this.key(backend, groupId, userId);\n const entry = this.map.get(key);\n if (!entry) return { kind: \"missing\" };\n if (Date.now() >= entry.expiresAt) {\n this.map.delete(key);\n return { kind: \"expired\" };\n }\n const command = entry.choices.get(n);\n if (!command) return { kind: \"invalid\" };\n this.map.delete(key);\n return { kind: \"chosen\", command };\n }\n take(backend: string, groupId: string, userId: string, n: number): string | null {\n const result = this.choose(backend, groupId, userId, n);\n return result.kind === \"chosen\" ? result.command : null;\n }\n clear(backend: string, groupId: string, userId: string): void {\n this.map.delete(this.key(backend, groupId, userId));\n }\n}\n"],
5
+ "mappings": "AAGO,MAAM,mBAAmB;AAAA,EAE9B,YAA6B,QAAQ,KAAQ;AAAhB;AAAA,EAAiB;AAAA,EAAjB;AAAA,EADZ,MAAM,oBAAI,IAA2B;AAAA,EAE9C,IAAI,SAAiB,SAAiB,QAAwB;AACpE,WAAO,KAAK,UAAU,CAAC,SAAS,SAAS,MAAM,CAAC;AAAA,EAClD;AAAA,EACA,IAAI,SAAiB,SAAiB,QAAgB,SAAoC;AACxF,eAAW,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK;AACnC,UAAI,MAAM,YAAY,KAAK,QAAQ,KAAK,IAAI,EAAG,MAAK,IAAI,OAAO,GAAG;AAAA,IACpE;AACA,SAAK,IAAI,IAAI,KAAK,IAAI,SAAS,SAAS,MAAM,GAAG,EAAE,SAAS,WAAW,KAAK,IAAI,IAAI,KAAK,MAAM,CAAC;AAAA,EAClG;AAAA,EACA,OAAO,SAAiB,SAAiB,QAAgB,GAAyB;AAChF,UAAM,MAAM,KAAK,IAAI,SAAS,SAAS,MAAM;AAC7C,UAAM,QAAQ,KAAK,IAAI,IAAI,GAAG;AAC9B,QAAI,CAAC,MAAO,QAAO,EAAE,MAAM,UAAU;AACrC,QAAI,KAAK,IAAI,KAAK,MAAM,WAAW;AACjC,WAAK,IAAI,OAAO,GAAG;AACnB,aAAO,EAAE,MAAM,UAAU;AAAA,IAC3B;AACA,UAAM,UAAU,MAAM,QAAQ,IAAI,CAAC;AACnC,QAAI,CAAC,QAAS,QAAO,EAAE,MAAM,UAAU;AACvC,SAAK,IAAI,OAAO,GAAG;AACnB,WAAO,EAAE,MAAM,UAAU,QAAQ;AAAA,EACnC;AAAA,EACA,KAAK,SAAiB,SAAiB,QAAgB,GAA0B;AAC/E,UAAM,SAAS,KAAK,OAAO,SAAS,SAAS,QAAQ,CAAC;AACtD,WAAO,OAAO,SAAS,WAAW,OAAO,UAAU;AAAA,EACrD;AAAA,EACA,MAAM,SAAiB,SAAiB,QAAsB;AAC5D,SAAK,IAAI,OAAO,KAAK,IAAI,SAAS,SAAS,MAAM,CAAC;AAAA,EACpD;AACF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,17 @@
1
+ import { log } from "../log.js";
2
+ import { fetchJoinPending } from "./db-api.js";
3
+ async function authorizeAdmin(ctx) {
4
+ const { dbHost: host, dbPort: port } = ctx.runtimeInfo;
5
+ if (!host || !port || !ctx.inbound.userId) return false;
6
+ try {
7
+ const result = await fetchJoinPending({ host, port }, ctx.inbound.userId, ctx.inbound.isGroupAdmin === true);
8
+ return result.success === true && !result.error;
9
+ } catch (error) {
10
+ log.warn(`\u7BA1\u7406\u6743\u9650\u67E5\u8BE2\u5931\u8D25: ${String(error)}`);
11
+ return false;
12
+ }
13
+ }
14
+ export {
15
+ authorizeAdmin
16
+ };
17
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/commands/permissions.ts"],
4
+ "sourcesContent": ["import { log } from \"../log.js\";\nimport { fetchJoinPending } from \"./db-api.js\";\nimport type { CommandContext } from \"./types.js\";\n\n/** \u590D\u7528\u670D\u52A1\u7AEF\u6700\u7EC8\u9274\u6743\uFF0C\u6743\u9650\u4E0D\u53EF\u8FBE\u65F6\u5173\u95ED\u7BA1\u7406\u5165\u53E3\u3002 */\nexport async function authorizeAdmin(ctx: CommandContext): Promise<boolean> {\n const { dbHost: host, dbPort: port } = ctx.runtimeInfo;\n if (!host || !port || !ctx.inbound.userId) return false;\n try {\n const result = await fetchJoinPending({ host, port }, ctx.inbound.userId, ctx.inbound.isGroupAdmin === true);\n return result.success === true && !result.error;\n } catch (error) {\n log.warn(`\u7BA1\u7406\u6743\u9650\u67E5\u8BE2\u5931\u8D25: ${String(error)}`);\n return false;\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,WAAW;AACpB,SAAS,wBAAwB;AAIjC,eAAsB,eAAe,KAAuC;AAC1E,QAAM,EAAE,QAAQ,MAAM,QAAQ,KAAK,IAAI,IAAI;AAC3C,MAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,OAAQ,QAAO;AAClD,MAAI;AACF,UAAM,SAAS,MAAM,iBAAiB,EAAE,MAAM,KAAK,GAAG,IAAI,QAAQ,QAAQ,IAAI,QAAQ,iBAAiB,IAAI;AAC3G,WAAO,OAAO,YAAY,QAAQ,CAAC,OAAO;AAAA,EAC5C,SAAS,OAAO;AACd,QAAI,KAAK,qDAAa,OAAO,KAAK,CAAC,EAAE;AACrC,WAAO;AAAA,EACT;AACF;",
6
+ "names": []
7
+ }
@@ -26,7 +26,7 @@ class CommandRegistry {
26
26
  }
27
27
  /** 主菜单 / 官方快捷菜单:排除管理子菜单项 */
28
28
  userMenu() {
29
- return this.list.filter((c) => !c.adminMenu);
29
+ return this.list.filter((c) => c.group === "home" && c.permission !== "admin");
30
30
  }
31
31
  /** 管理子菜单 */
32
32
  adminMenu() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/commands/registry.ts"],
4
- "sourcesContent": ["/**\n * commands/registry.ts \u2014 \u6307\u4EE4\u6CE8\u518C\u4E0E\u67E5\u627E\n */\n\nimport type { RegisteredCommand } from \"./types.js\";\n\n/** \u53BB\u6389\u524D\u5BFC /\u3001\u7A7A\u767D\uFF0C\u7EDF\u4E00\u5C0F\u5199 */\nexport function normalizeTrigger(raw: string): string {\n return String(raw ?? \"\")\n .trim()\n .replace(/^[/\uFF0F]+/, \"\")\n .toLowerCase();\n}\n\nexport class CommandRegistry {\n private readonly byAlias = new Map<string, RegisteredCommand>();\n private readonly list: RegisteredCommand[] = [];\n\n register(cmd: RegisteredCommand): void {\n this.list.push(cmd);\n const names = new Set([cmd.name, ...cmd.aliases].map((a) => normalizeTrigger(a)));\n for (const alias of names) {\n if (!alias) continue;\n this.byAlias.set(alias, cmd);\n }\n }\n\n resolve(raw: string): RegisteredCommand | null {\n const key = normalizeTrigger(raw);\n if (!key) return null;\n const exact = this.byAlias.get(key);\n if (exact) return exact;\n // \u5E26\u53C2\u6570\uFF1A\u53D6\u9996\u8BCD\u5339\u914D\uFF08\u5982\u300C\u7533\u8BF7\u5165\u670D Steve\u300D\u2192 join\uFF09\n const first = key.split(/\\s+/)[0] ?? \"\";\n if (!first || first === key) return null;\n return this.byAlias.get(first) ?? null;\n }\n\n all(): readonly RegisteredCommand[] {\n return this.list;\n }\n\n /** \u4E3B\u83DC\u5355 / \u5B98\u65B9\u5FEB\u6377\u83DC\u5355\uFF1A\u6392\u9664\u7BA1\u7406\u5B50\u83DC\u5355\u9879 */\n userMenu(): RegisteredCommand[] {\n return this.list.filter((c) => !c.adminMenu);\n }\n\n /** \u7BA1\u7406\u5B50\u83DC\u5355 */\n adminMenu(): RegisteredCommand[] {\n return this.list.filter((c) => !!c.adminMenu);\n }\n}\n"],
5
- "mappings": "AAOO,SAAS,iBAAiB,KAAqB;AACpD,SAAO,OAAO,OAAO,EAAE,EACpB,KAAK,EACL,QAAQ,UAAU,EAAE,EACpB,YAAY;AACjB;AAEO,MAAM,gBAAgB;AAAA,EACV,UAAU,oBAAI,IAA+B;AAAA,EAC7C,OAA4B,CAAC;AAAA,EAE9C,SAAS,KAA8B;AACrC,SAAK,KAAK,KAAK,GAAG;AAClB,UAAM,QAAQ,IAAI,IAAI,CAAC,IAAI,MAAM,GAAG,IAAI,OAAO,EAAE,IAAI,CAAC,MAAM,iBAAiB,CAAC,CAAC,CAAC;AAChF,eAAW,SAAS,OAAO;AACzB,UAAI,CAAC,MAAO;AACZ,WAAK,QAAQ,IAAI,OAAO,GAAG;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,QAAQ,KAAuC;AAC7C,UAAM,MAAM,iBAAiB,GAAG;AAChC,QAAI,CAAC,IAAK,QAAO;AACjB,UAAM,QAAQ,KAAK,QAAQ,IAAI,GAAG;AAClC,QAAI,MAAO,QAAO;AAElB,UAAM,QAAQ,IAAI,MAAM,KAAK,EAAE,CAAC,KAAK;AACrC,QAAI,CAAC,SAAS,UAAU,IAAK,QAAO;AACpC,WAAO,KAAK,QAAQ,IAAI,KAAK,KAAK;AAAA,EACpC;AAAA,EAEA,MAAoC;AAClC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,WAAgC;AAC9B,WAAO,KAAK,KAAK,OAAO,CAAC,MAAM,CAAC,EAAE,SAAS;AAAA,EAC7C;AAAA;AAAA,EAGA,YAAiC;AAC/B,WAAO,KAAK,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,SAAS;AAAA,EAC9C;AACF;",
4
+ "sourcesContent": ["/**\n * commands/registry.ts \u2014 \u6307\u4EE4\u6CE8\u518C\u4E0E\u67E5\u627E\n */\n\nimport type { RegisteredCommand } from \"./types.js\";\n\n/** \u53BB\u6389\u524D\u5BFC /\u3001\u7A7A\u767D\uFF0C\u7EDF\u4E00\u5C0F\u5199 */\nexport function normalizeTrigger(raw: string): string {\n return String(raw ?? \"\")\n .trim()\n .replace(/^[/\uFF0F]+/, \"\")\n .toLowerCase();\n}\n\nexport class CommandRegistry {\n private readonly byAlias = new Map<string, RegisteredCommand>();\n private readonly list: RegisteredCommand[] = [];\n\n register(cmd: RegisteredCommand): void {\n this.list.push(cmd);\n const names = new Set([cmd.name, ...cmd.aliases].map((a) => normalizeTrigger(a)));\n for (const alias of names) {\n if (!alias) continue;\n this.byAlias.set(alias, cmd);\n }\n }\n\n resolve(raw: string): RegisteredCommand | null {\n const key = normalizeTrigger(raw);\n if (!key) return null;\n const exact = this.byAlias.get(key);\n if (exact) return exact;\n // \u5E26\u53C2\u6570\uFF1A\u53D6\u9996\u8BCD\u5339\u914D\uFF08\u5982\u300C\u7533\u8BF7\u5165\u670D Steve\u300D\u2192 join\uFF09\n const first = key.split(/\\s+/)[0] ?? \"\";\n if (!first || first === key) return null;\n return this.byAlias.get(first) ?? null;\n }\n\n all(): readonly RegisteredCommand[] {\n return this.list;\n }\n\n /** \u4E3B\u83DC\u5355 / \u5B98\u65B9\u5FEB\u6377\u83DC\u5355\uFF1A\u6392\u9664\u7BA1\u7406\u5B50\u83DC\u5355\u9879 */\n userMenu(): RegisteredCommand[] {\n return this.list.filter((c) => c.group === \"home\" && c.permission !== \"admin\");\n }\n\n /** \u7BA1\u7406\u5B50\u83DC\u5355 */\n adminMenu(): RegisteredCommand[] {\n return this.list.filter((c) => !!c.adminMenu);\n }\n}\n"],
5
+ "mappings": "AAOO,SAAS,iBAAiB,KAAqB;AACpD,SAAO,OAAO,OAAO,EAAE,EACpB,KAAK,EACL,QAAQ,UAAU,EAAE,EACpB,YAAY;AACjB;AAEO,MAAM,gBAAgB;AAAA,EACV,UAAU,oBAAI,IAA+B;AAAA,EAC7C,OAA4B,CAAC;AAAA,EAE9C,SAAS,KAA8B;AACrC,SAAK,KAAK,KAAK,GAAG;AAClB,UAAM,QAAQ,IAAI,IAAI,CAAC,IAAI,MAAM,GAAG,IAAI,OAAO,EAAE,IAAI,CAAC,MAAM,iBAAiB,CAAC,CAAC,CAAC;AAChF,eAAW,SAAS,OAAO;AACzB,UAAI,CAAC,MAAO;AACZ,WAAK,QAAQ,IAAI,OAAO,GAAG;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,QAAQ,KAAuC;AAC7C,UAAM,MAAM,iBAAiB,GAAG;AAChC,QAAI,CAAC,IAAK,QAAO;AACjB,UAAM,QAAQ,KAAK,QAAQ,IAAI,GAAG;AAClC,QAAI,MAAO,QAAO;AAElB,UAAM,QAAQ,IAAI,MAAM,KAAK,EAAE,CAAC,KAAK;AACrC,QAAI,CAAC,SAAS,UAAU,IAAK,QAAO;AACpC,WAAO,KAAK,QAAQ,IAAI,KAAK,KAAK;AAAA,EACpC;AAAA,EAEA,MAAoC;AAClC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,WAAgC;AAC9B,WAAO,KAAK,KAAK,OAAO,CAAC,MAAM,EAAE,UAAU,UAAU,EAAE,eAAe,OAAO;AAAA,EAC/E;AAAA;AAAA,EAGA,YAAiC;AAC/B,WAAO,KAAK,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,SAAS;AAAA,EAC9C;AACF;",
6
6
  "names": []
7
7
  }
@@ -20,23 +20,14 @@ function renderOfficial(result) {
20
20
  return { msgType: 0, content: result.text };
21
21
  }
22
22
  function renderLlbot(result) {
23
- if (!result.buttons || result.buttons.length === 0) {
24
- return { text: result.text };
25
- }
26
- if (/^\d+\.\s/m.test(result.text)) {
27
- return {
28
- text: `${result.text}
29
-
30
- \u56DE\u590D\u6570\u5B57\u6267\u884C\uFF0860 \u79D2\u5185\u6709\u6548\uFF09`
31
- };
32
- }
33
- const lines = result.buttons.map((b, i) => `[${i + 1}] ${b.label}`);
23
+ if (!result.buttons?.length) return { text: result.text };
24
+ const lines = result.buttons.map((button, index) => {
25
+ const detail = result.menu && button.description ? `
26
+ ${button.description}` : "";
27
+ return `${index + 1}. ${button.label}${detail}`;
28
+ });
34
29
  return {
35
- text: `${result.text}
36
-
37
- ${lines.join("\n")}
38
-
39
- \u56DE\u590D\u6570\u5B57\u6267\u884C\uFF0860 \u79D2\u5185\u6709\u6548\uFF09`
30
+ text: [result.text, "", result.menu ? "" : "\u53EF\u9009\u64CD\u4F5C", lines.join("\n"), "", "\u8BF7\u5728 60 \u79D2\u5185\u56DE\u590D\u7F16\u53F7\u9009\u62E9"].filter((line, index, all) => line !== "" || all[index - 1] !== "").join("\n")
40
31
  };
41
32
  }
42
33
  export {