@sfmc-bds/qq-bridge 0.1.1-beta.3 → 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.
- package/README.md +14 -3
- package/dist/commands/db-api.js +89 -0
- package/dist/commands/db-api.js.map +7 -0
- package/dist/commands/handlers.js +660 -0
- package/dist/commands/handlers.js.map +7 -0
- package/dist/commands/index.js +55 -0
- package/dist/commands/index.js.map +7 -0
- package/dist/commands/interaction-router.js +149 -0
- package/dist/commands/interaction-router.js.map +7 -0
- package/dist/commands/join-settings-ui.js +108 -0
- package/dist/commands/join-settings-ui.js.map +7 -0
- package/dist/commands/join-settings-ui.test.js +43 -0
- package/dist/commands/join-settings-ui.test.js.map +7 -0
- package/dist/commands/menu-format.js +45 -0
- package/dist/commands/menu-format.js.map +7 -0
- package/dist/commands/menu-panel-sync.js +130 -0
- package/dist/commands/menu-panel-sync.js.map +7 -0
- package/dist/commands/menu-panel-sync.test.js +27 -0
- package/dist/commands/menu-panel-sync.test.js.map +7 -0
- package/dist/commands/pending.js +33 -0
- package/dist/commands/pending.js.map +7 -0
- package/dist/commands/registry.js +40 -0
- package/dist/commands/registry.js.map +7 -0
- package/dist/commands/render.js +46 -0
- package/dist/commands/render.js.map +7 -0
- package/dist/commands/render.test.js +44 -0
- package/dist/commands/render.test.js.map +7 -0
- package/dist/commands/reply-llbot.js +63 -0
- package/dist/commands/reply-llbot.js.map +7 -0
- package/dist/commands/reply-official.js +66 -0
- package/dist/commands/reply-official.js.map +7 -0
- package/dist/commands/router.js +53 -0
- package/dist/commands/router.js.map +7 -0
- package/dist/commands/router.test.js +92 -0
- package/dist/commands/router.test.js.map +7 -0
- package/dist/commands/types.js +1 -0
- package/dist/commands/types.js.map +7 -0
- package/dist/config.js +12 -2
- package/dist/config.js.map +2 -2
- package/dist/console.js +40 -20
- package/dist/console.js.map +2 -2
- package/dist/index.js +117 -20
- package/dist/index.js.map +2 -2
- package/dist/llbot-ws-api.js +90 -0
- package/dist/llbot-ws-api.js.map +7 -0
- package/dist/llbot-ws-api.test.js +32 -0
- package/dist/llbot-ws-api.test.js.map +7 -0
- package/dist/official/events.js +136 -0
- package/dist/official/events.js.map +7 -0
- package/dist/official/events.test.js +123 -0
- package/dist/official/events.test.js.map +7 -0
- package/dist/official/gateway.js +226 -0
- package/dist/official/gateway.js.map +7 -0
- package/dist/onebot.js +23 -5
- package/dist/onebot.js.map +2 -2
- package/dist/project-root.js +2 -2
- package/dist/project-root.js.map +2 -2
- package/dist/runtime-status.js +45 -0
- package/dist/runtime-status.js.map +7 -0
- package/dist/ws-server.js +4 -0
- package/dist/ws-server.js.map +2 -2
- package/package.json +6 -5
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createPanel,
|
|
3
|
+
putMenu,
|
|
4
|
+
updatePanel,
|
|
5
|
+
updatePanelTarget
|
|
6
|
+
} from "@sfmc-bds/sdk/node/qq-official";
|
|
7
|
+
import { patchJson } from "@sfmc-bds/sdk/node/config";
|
|
8
|
+
import { log } from "../log.js";
|
|
9
|
+
import { CFG_PATH } from "../config.js";
|
|
10
|
+
import { pickDisplayLabel } from "./menu-format.js";
|
|
11
|
+
const MAX_MENU_ITEMS = 10;
|
|
12
|
+
const MAX_PANEL_ITEMS = 20;
|
|
13
|
+
function pickTriggerLabel(cmd, maxChars) {
|
|
14
|
+
return pickDisplayLabel(cmd, maxChars);
|
|
15
|
+
}
|
|
16
|
+
function buildMenuItems(registry) {
|
|
17
|
+
return registry.userMenu().slice(0, MAX_MENU_ITEMS).map((c) => {
|
|
18
|
+
const label = pickTriggerLabel(c, 10);
|
|
19
|
+
return {
|
|
20
|
+
type: "send_message",
|
|
21
|
+
name: label,
|
|
22
|
+
// 点击后填入输入框;与 router 触发词对齐
|
|
23
|
+
send_message: `/${c.name}`
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function buildPanelItems(registry) {
|
|
28
|
+
const user = registry.userMenu();
|
|
29
|
+
const admin = registry.adminMenu();
|
|
30
|
+
const merged = [...user, ...admin].slice(0, MAX_PANEL_ITEMS);
|
|
31
|
+
return merged.map((c) => ({
|
|
32
|
+
type: "command",
|
|
33
|
+
// 填入输入框的内容:用 /name 便于 router 命中
|
|
34
|
+
name: `/${c.name}`.slice(0, 14),
|
|
35
|
+
desc: (c.description || c.name).slice(0, 30)
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
function extractPanelId(json) {
|
|
39
|
+
if (!json || typeof json !== "object") return "";
|
|
40
|
+
const o = json;
|
|
41
|
+
for (const key of ["panel_id", "id", "panelId"]) {
|
|
42
|
+
const v = o[key];
|
|
43
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
44
|
+
}
|
|
45
|
+
if (o["data"] && typeof o["data"] === "object") {
|
|
46
|
+
return extractPanelId(o["data"]);
|
|
47
|
+
}
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
async function syncMenuAndPanel(opts) {
|
|
51
|
+
const items = buildMenuItems(opts.registry);
|
|
52
|
+
const panelItems = buildPanelItems(opts.registry);
|
|
53
|
+
let menuOk = false;
|
|
54
|
+
let panelOk = false;
|
|
55
|
+
const menuRes = await putMenu(opts.creds, { menu: { items } });
|
|
56
|
+
if (menuRes.ok) {
|
|
57
|
+
menuOk = true;
|
|
58
|
+
log.info(`\u5B98\u65B9\u81EA\u5B9A\u4E49\u83DC\u5355\u5DF2\u540C\u6B65 (${items.length} \u9879)`);
|
|
59
|
+
} else {
|
|
60
|
+
log.warn(`\u5B98\u65B9\u81EA\u5B9A\u4E49\u83DC\u5355\u540C\u6B65\u5931\u8D25: ${menuRes.error}`);
|
|
61
|
+
}
|
|
62
|
+
const panelBody = {
|
|
63
|
+
items: panelItems,
|
|
64
|
+
remark: "SFMC"
|
|
65
|
+
};
|
|
66
|
+
let panelId = opts.panelId.trim();
|
|
67
|
+
if (panelId) {
|
|
68
|
+
const upd = await updatePanel(opts.creds, panelId, { panel: panelBody });
|
|
69
|
+
if (upd.ok) {
|
|
70
|
+
panelOk = true;
|
|
71
|
+
log.info(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u5DF2\u66F4\u65B0 id=${panelId}`);
|
|
72
|
+
} else {
|
|
73
|
+
log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u66F4\u65B0\u5931\u8D25\uFF0C\u5C06\u5C1D\u8BD5\u65B0\u5EFA: ${upd.error}`);
|
|
74
|
+
panelId = "";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!panelId) {
|
|
78
|
+
const hasGroup = !!opts.groupOpenid;
|
|
79
|
+
const created = await createPanel(opts.creds, {
|
|
80
|
+
scope: "group",
|
|
81
|
+
target_type: hasGroup ? "specific" : "all",
|
|
82
|
+
...hasGroup ? { group_openids: [opts.groupOpenid] } : {},
|
|
83
|
+
panel: panelBody
|
|
84
|
+
});
|
|
85
|
+
if (created.ok) {
|
|
86
|
+
panelId = extractPanelId(created.json) || extractPanelId(safeParse(created.body));
|
|
87
|
+
if (panelId) {
|
|
88
|
+
panelOk = true;
|
|
89
|
+
opts.persistPanelId?.(panelId);
|
|
90
|
+
log.info(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u5DF2\u521B\u5EFA id=${panelId}`);
|
|
91
|
+
} else {
|
|
92
|
+
log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u521B\u5EFA\u6210\u529F\u4F46\u672A\u89E3\u6790\u5230 id: ${created.body.slice(0, 160)}`);
|
|
93
|
+
panelOk = true;
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
log.warn(`\u5B98\u65B9\u7FA4\u6307\u4EE4\u9762\u677F\u521B\u5EFA\u5931\u8D25: ${created.error}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
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}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { menuOk, panelOk };
|
|
109
|
+
}
|
|
110
|
+
function safeParse(body) {
|
|
111
|
+
try {
|
|
112
|
+
return JSON.parse(body);
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function persistPanelIdToConfig(panelId) {
|
|
118
|
+
try {
|
|
119
|
+
patchJson(CFG_PATH, { qq_group_panel_id: panelId });
|
|
120
|
+
} catch (e) {
|
|
121
|
+
log.warn(`\u5199\u56DE qq_group_panel_id \u5931\u8D25: ${e.message}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export {
|
|
125
|
+
buildMenuItems,
|
|
126
|
+
buildPanelItems,
|
|
127
|
+
persistPanelIdToConfig,
|
|
128
|
+
syncMenuAndPanel
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=menu-panel-sync.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { CommandRegistry } from "./registry.js";
|
|
4
|
+
import { registerBuiltinCommands } from "./handlers.js";
|
|
5
|
+
import { buildMenuItems, buildPanelItems } from "./menu-panel-sync.js";
|
|
6
|
+
test("buildMenuItems / buildPanelItems \u4E0E registry \u5BF9\u9F50\u4E14 \u226410/20", () => {
|
|
7
|
+
const registry = new CommandRegistry();
|
|
8
|
+
registerBuiltinCommands(registry);
|
|
9
|
+
const userCount = registry.userMenu().length;
|
|
10
|
+
const total = registry.all().length;
|
|
11
|
+
const menu = buildMenuItems(registry);
|
|
12
|
+
const panel = buildPanelItems(registry);
|
|
13
|
+
assert.ok(menu.length >= 5);
|
|
14
|
+
assert.ok(menu.length <= 10);
|
|
15
|
+
assert.equal(menu.length, Math.min(10, userCount));
|
|
16
|
+
assert.equal(panel.length, Math.min(20, total));
|
|
17
|
+
assert.equal(menu[0]?.type, "send_message");
|
|
18
|
+
assert.equal(panel[0]?.type, "command");
|
|
19
|
+
assert.ok(menu.some((i) => i.send_message === "/ping"));
|
|
20
|
+
assert.ok(menu.some((i) => i.send_message === "/admin"));
|
|
21
|
+
assert.ok(!menu.some((i) => i.send_message === "/kick"));
|
|
22
|
+
assert.ok(panel.some((i) => i.name === "/bind"));
|
|
23
|
+
assert.ok(panel.some((i) => i.name === "/kick"));
|
|
24
|
+
assert.ok(panel.every((i) => (i.name?.length ?? 0) <= 14));
|
|
25
|
+
assert.ok(menu.every((i) => (i.name?.length ?? 0) <= 10));
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=menu-panel-sync.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/commands/menu-panel-sync.test.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * commands/menu-panel-sync.test.ts\n */\nimport assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport { CommandRegistry } from \"./registry.js\";\nimport { registerBuiltinCommands } from \"./handlers.js\";\nimport { buildMenuItems, buildPanelItems } from \"./menu-panel-sync.js\";\n\ntest(\"buildMenuItems / buildPanelItems \u4E0E registry \u5BF9\u9F50\u4E14 \u226410/20\", () => {\n const registry = new CommandRegistry();\n registerBuiltinCommands(registry);\n const userCount = registry.userMenu().length;\n const total = registry.all().length;\n const menu = buildMenuItems(registry);\n const panel = buildPanelItems(registry);\n assert.ok(menu.length >= 5);\n assert.ok(menu.length <= 10);\n assert.equal(menu.length, Math.min(10, userCount));\n assert.equal(panel.length, Math.min(20, total));\n assert.equal(menu[0]?.type, \"send_message\");\n assert.equal(panel[0]?.type, \"command\");\n assert.ok(menu.some((i) => i.send_message === \"/ping\"));\n assert.ok(menu.some((i) => i.send_message === \"/admin\"));\n // \u7BA1\u7406\u9879\u4E0D\u8FDB C2C \u4E3B\u83DC\u5355\n assert.ok(!menu.some((i) => i.send_message === \"/kick\"));\n // \u9762\u677F\u65E0\u72EC\u7ACB command \u5B57\u6BB5\uFF1Aname \u5373\u586B\u5165\u8F93\u5165\u6846\u5185\u5BB9\uFF1B\u53EF\u542B\u7BA1\u7406\u9879\n assert.ok(panel.some((i) => i.name === \"/bind\"));\n assert.ok(panel.some((i) => i.name === \"/kick\"));\n assert.ok(panel.every((i) => (i.name?.length ?? 0) <= 14));\n assert.ok(menu.every((i) => (i.name?.length ?? 0) <= 10));\n});\n"],
|
|
5
|
+
"mappings": "AAGA,OAAO,YAAY;AACnB,OAAO,UAAU;AACjB,SAAS,uBAAuB;AAChC,SAAS,+BAA+B;AACxC,SAAS,gBAAgB,uBAAuB;AAEhD,KAAK,mFAA0D,MAAM;AACnE,QAAM,WAAW,IAAI,gBAAgB;AACrC,0BAAwB,QAAQ;AAChC,QAAM,YAAY,SAAS,SAAS,EAAE;AACtC,QAAM,QAAQ,SAAS,IAAI,EAAE;AAC7B,QAAM,OAAO,eAAe,QAAQ;AACpC,QAAM,QAAQ,gBAAgB,QAAQ;AACtC,SAAO,GAAG,KAAK,UAAU,CAAC;AAC1B,SAAO,GAAG,KAAK,UAAU,EAAE;AAC3B,SAAO,MAAM,KAAK,QAAQ,KAAK,IAAI,IAAI,SAAS,CAAC;AACjD,SAAO,MAAM,MAAM,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC;AAC9C,SAAO,MAAM,KAAK,CAAC,GAAG,MAAM,cAAc;AAC1C,SAAO,MAAM,MAAM,CAAC,GAAG,MAAM,SAAS;AACtC,SAAO,GAAG,KAAK,KAAK,CAAC,MAAM,EAAE,iBAAiB,OAAO,CAAC;AACtD,SAAO,GAAG,KAAK,KAAK,CAAC,MAAM,EAAE,iBAAiB,QAAQ,CAAC;AAEvD,SAAO,GAAG,CAAC,KAAK,KAAK,CAAC,MAAM,EAAE,iBAAiB,OAAO,CAAC;AAEvD,SAAO,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO,CAAC;AAC/C,SAAO,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO,CAAC;AAC/C,SAAO,GAAG,MAAM,MAAM,CAAC,OAAO,EAAE,MAAM,UAAU,MAAM,EAAE,CAAC;AACzD,SAAO,GAAG,KAAK,MAAM,CAAC,OAAO,EAAE,MAAM,UAAU,MAAM,EAAE,CAAC;AAC1D,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const DEFAULT_TTL_MS = 6e4;
|
|
2
|
+
class PendingChoiceStore {
|
|
3
|
+
map = /* @__PURE__ */ new Map();
|
|
4
|
+
ttlMs;
|
|
5
|
+
constructor(ttlMs = DEFAULT_TTL_MS) {
|
|
6
|
+
this.ttlMs = ttlMs;
|
|
7
|
+
}
|
|
8
|
+
key(backend, groupId, userId) {
|
|
9
|
+
return `${backend}:${groupId}:${userId}`;
|
|
10
|
+
}
|
|
11
|
+
set(backend, groupId, userId, choices) {
|
|
12
|
+
this.map.set(this.key(backend, groupId, userId), {
|
|
13
|
+
choices,
|
|
14
|
+
expiresAt: Date.now() + this.ttlMs
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** 取编号对应命令;命中或过期都会清掉该会话 */
|
|
18
|
+
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;
|
|
25
|
+
}
|
|
26
|
+
clear(backend, groupId, userId) {
|
|
27
|
+
this.map.delete(this.key(backend, groupId, userId));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
PendingChoiceStore
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=pending.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
function normalizeTrigger(raw) {
|
|
2
|
+
return String(raw ?? "").trim().replace(/^[//]+/, "").toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
class CommandRegistry {
|
|
5
|
+
byAlias = /* @__PURE__ */ new Map();
|
|
6
|
+
list = [];
|
|
7
|
+
register(cmd) {
|
|
8
|
+
this.list.push(cmd);
|
|
9
|
+
const names = new Set([cmd.name, ...cmd.aliases].map((a) => normalizeTrigger(a)));
|
|
10
|
+
for (const alias of names) {
|
|
11
|
+
if (!alias) continue;
|
|
12
|
+
this.byAlias.set(alias, cmd);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
resolve(raw) {
|
|
16
|
+
const key = normalizeTrigger(raw);
|
|
17
|
+
if (!key) return null;
|
|
18
|
+
const exact = this.byAlias.get(key);
|
|
19
|
+
if (exact) return exact;
|
|
20
|
+
const first = key.split(/\s+/)[0] ?? "";
|
|
21
|
+
if (!first || first === key) return null;
|
|
22
|
+
return this.byAlias.get(first) ?? null;
|
|
23
|
+
}
|
|
24
|
+
all() {
|
|
25
|
+
return this.list;
|
|
26
|
+
}
|
|
27
|
+
/** 主菜单 / 官方快捷菜单:排除管理子菜单项 */
|
|
28
|
+
userMenu() {
|
|
29
|
+
return this.list.filter((c) => !c.adminMenu);
|
|
30
|
+
}
|
|
31
|
+
/** 管理子菜单 */
|
|
32
|
+
adminMenu() {
|
|
33
|
+
return this.list.filter((c) => !!c.adminMenu);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
CommandRegistry,
|
|
38
|
+
normalizeTrigger
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
function renderOfficial(result) {
|
|
2
|
+
const buttons = result.buttons?.map((b) => ({
|
|
3
|
+
id: b.id,
|
|
4
|
+
label: b.label,
|
|
5
|
+
data: b.command,
|
|
6
|
+
...b.actionType !== void 0 ? { actionType: b.actionType } : {},
|
|
7
|
+
...b.style !== void 0 ? { style: b.style } : {},
|
|
8
|
+
...b.visitedLabel ? { visitedLabel: b.visitedLabel } : {},
|
|
9
|
+
...b.permission ? { permission: b.permission } : {}
|
|
10
|
+
}));
|
|
11
|
+
const md = result.markdown?.trim() || void 0;
|
|
12
|
+
if (md || buttons && buttons.length > 0) {
|
|
13
|
+
const payload = {
|
|
14
|
+
msgType: 2,
|
|
15
|
+
markdown: md ?? result.text
|
|
16
|
+
};
|
|
17
|
+
if (buttons && buttons.length > 0) payload.keyboardButtons = buttons;
|
|
18
|
+
return payload;
|
|
19
|
+
}
|
|
20
|
+
return { msgType: 0, content: result.text };
|
|
21
|
+
}
|
|
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}`);
|
|
34
|
+
return {
|
|
35
|
+
text: `${result.text}
|
|
36
|
+
|
|
37
|
+
${lines.join("\n")}
|
|
38
|
+
|
|
39
|
+
\u56DE\u590D\u6570\u5B57\u6267\u884C\uFF0860 \u79D2\u5185\u6709\u6548\uFF09`
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
renderLlbot,
|
|
44
|
+
renderOfficial
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/commands/render.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * commands/render.ts \u2014 CommandResult \u2192 \u5404\u540E\u7AEF\u5448\u73B0\u8F7D\u8377\uFF08\u65E0\u526F\u4F5C\u7528\uFF09\n */\n\nimport type { QqKeyboardButton } from \"@sfmc-bds/sdk/node/qq-official\";\nimport type { CommandResult } from \"./types.js\";\n\nexport type OfficialRenderPayload = {\n msgType: 0 | 2;\n content?: string;\n markdown?: string;\n keyboardButtons?: QqKeyboardButton[];\n};\n\nexport type LlbotRenderPayload = {\n text: string;\n};\n\n/** official\uFF1A\u6709 buttons/markdown \u7528 msg_type=2\uFF0C\u5426\u5219\u7EAF\u6587\u672C */\nexport function renderOfficial(result: CommandResult): OfficialRenderPayload {\n const buttons = result.buttons?.map((b) => ({\n id: b.id,\n label: b.label,\n data: b.command,\n ...(b.actionType !== undefined ? { actionType: b.actionType } : {}),\n ...(b.style !== undefined ? { style: b.style } : {}),\n ...(b.visitedLabel ? { visitedLabel: b.visitedLabel } : {}),\n ...(b.permission ? { permission: b.permission } : {}),\n }));\n const md = result.markdown?.trim() || undefined;\n if (md || (buttons && buttons.length > 0)) {\n const payload: OfficialRenderPayload = {\n msgType: 2,\n markdown: md ?? result.text,\n };\n if (buttons && buttons.length > 0) payload.keyboardButtons = buttons;\n return payload;\n }\n return { msgType: 0, content: result.text };\n}\n\n/** llbot\uFF1A\u6B63\u6587\u5DF2\u542B\u7F16\u53F7\u5217\u8868\u65F6\u4E0D\u518D\u91CD\u590D\uFF1B\u5426\u5219\u8865\u7F16\u53F7\u884C */\nexport function renderLlbot(result: CommandResult): LlbotRenderPayload {\n if (!result.buttons || result.buttons.length === 0) {\n return { text: result.text };\n }\n // \u4E3B/\u7BA1\u7406\u83DC\u5355\u6B63\u6587\u5DF2\u5E26\u300C1. \u6807\u7B7E \u2014 \u8BF4\u660E\u300D\uFF0C\u907F\u514D\u518D\u5806\u4E00\u5C42 [1] label\n if (/^\\d+\\.\\s/m.test(result.text)) {\n return {\n text: `${result.text}\\n\\n\u56DE\u590D\u6570\u5B57\u6267\u884C\uFF0860 \u79D2\u5185\u6709\u6548\uFF09`,\n };\n }\n const lines = result.buttons.map((b, i) => `[${i + 1}] ${b.label}`);\n return {\n text: `${result.text}\\n\\n${lines.join(\"\\n\")}\\n\\n\u56DE\u590D\u6570\u5B57\u6267\u884C\uFF0860 \u79D2\u5185\u6709\u6548\uFF09`,\n };\n}\n"],
|
|
5
|
+
"mappings": "AAmBO,SAAS,eAAe,QAA8C;AAC3E,QAAM,UAAU,OAAO,SAAS,IAAI,CAAC,OAAO;AAAA,IAC1C,IAAI,EAAE;AAAA,IACN,OAAO,EAAE;AAAA,IACT,MAAM,EAAE;AAAA,IACR,GAAI,EAAE,eAAe,SAAY,EAAE,YAAY,EAAE,WAAW,IAAI,CAAC;AAAA,IACjE,GAAI,EAAE,UAAU,SAAY,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAAA,IAClD,GAAI,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,IAAI,CAAC;AAAA,IACzD,GAAI,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,IAAI,CAAC;AAAA,EACrD,EAAE;AACF,QAAM,KAAK,OAAO,UAAU,KAAK,KAAK;AACtC,MAAI,MAAO,WAAW,QAAQ,SAAS,GAAI;AACzC,UAAM,UAAiC;AAAA,MACrC,SAAS;AAAA,MACT,UAAU,MAAM,OAAO;AAAA,IACzB;AACA,QAAI,WAAW,QAAQ,SAAS,EAAG,SAAQ,kBAAkB;AAC7D,WAAO;AAAA,EACT;AACA,SAAO,EAAE,SAAS,GAAG,SAAS,OAAO,KAAK;AAC5C;AAGO,SAAS,YAAY,QAA2C;AACrE,MAAI,CAAC,OAAO,WAAW,OAAO,QAAQ,WAAW,GAAG;AAClD,WAAO,EAAE,MAAM,OAAO,KAAK;AAAA,EAC7B;AAEA,MAAI,YAAY,KAAK,OAAO,IAAI,GAAG;AACjC,WAAO;AAAA,MACL,MAAM,GAAG,OAAO,IAAI;AAAA;AAAA;AAAA,IACtB;AAAA,EACF;AACA,QAAM,QAAQ,OAAO,QAAQ,IAAI,CAAC,GAAG,MAAM,IAAI,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE;AAClE,SAAO;AAAA,IACL,MAAM,GAAG,OAAO,IAAI;AAAA;AAAA,EAAO,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA,EAC7C;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { renderLlbot, renderOfficial } from "./render.js";
|
|
4
|
+
const menuResult = {
|
|
5
|
+
text: "SFMC QQ \u6307\u4EE4\n\xB7 menu \u2014 \u663E\u793A\u6307\u4EE4\u83DC\u5355",
|
|
6
|
+
markdown: "## SFMC QQ \u6307\u4EE4\n\n- **menu**\uFF1A\u663E\u793A\u6307\u4EE4\u83DC\u5355",
|
|
7
|
+
buttons: [
|
|
8
|
+
{ id: "cmd_menu", label: "menu", command: "/menu" },
|
|
9
|
+
{ id: "cmd_ping", label: "ping", command: "/ping" }
|
|
10
|
+
]
|
|
11
|
+
};
|
|
12
|
+
test("renderOfficial \u5E26\u952E\u76D8\u7528 markdown", () => {
|
|
13
|
+
const p = renderOfficial(menuResult);
|
|
14
|
+
assert.equal(p.msgType, 2);
|
|
15
|
+
assert.equal(p.markdown, menuResult.markdown);
|
|
16
|
+
assert.equal(p.keyboardButtons?.length, 2);
|
|
17
|
+
assert.equal(p.keyboardButtons?.[1]?.data, "/ping");
|
|
18
|
+
});
|
|
19
|
+
test("renderOfficial \u7EAF\u6587\u672C", () => {
|
|
20
|
+
const p = renderOfficial({ text: "pong \xB7 backend=official" });
|
|
21
|
+
assert.equal(p.msgType, 0);
|
|
22
|
+
assert.equal(p.content, "pong \xB7 backend=official");
|
|
23
|
+
assert.equal(p.keyboardButtons, void 0);
|
|
24
|
+
});
|
|
25
|
+
test("renderLlbot \u7F16\u53F7\u884C", () => {
|
|
26
|
+
const p = renderLlbot(menuResult);
|
|
27
|
+
assert.match(p.text, /\[1\] menu/);
|
|
28
|
+
assert.match(p.text, /\[2\] ping/);
|
|
29
|
+
assert.match(p.text, /60 秒/);
|
|
30
|
+
});
|
|
31
|
+
test("renderLlbot \u6B63\u6587\u5DF2\u7F16\u53F7\u65F6\u4E0D\u91CD\u590D [n]", () => {
|
|
32
|
+
const r = {
|
|
33
|
+
text: "SFMC \u6307\u4EE4\n\u8BF4\u660E\n\n1. ping \u2014 \u8FDE\u901A\n2. \u72B6\u6001 \u2014 \u6458\u8981",
|
|
34
|
+
buttons: [
|
|
35
|
+
{ id: "cmd_ping", label: "ping", command: "/ping" },
|
|
36
|
+
{ id: "cmd_status", label: "\u72B6\u6001", command: "/status" }
|
|
37
|
+
]
|
|
38
|
+
};
|
|
39
|
+
const p = renderLlbot(r);
|
|
40
|
+
assert.match(p.text, /1\. ping/);
|
|
41
|
+
assert.doesNotMatch(p.text, /\[1\]/);
|
|
42
|
+
assert.match(p.text, /60 秒/);
|
|
43
|
+
});
|
|
44
|
+
//# sourceMappingURL=render.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/commands/render.test.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * commands/render.test.ts\n */\nimport assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport { renderLlbot, renderOfficial } from \"./render.js\";\nimport type { CommandResult } from \"./types.js\";\n\nconst menuResult: CommandResult = {\n text: \"SFMC QQ \u6307\u4EE4\\n\u00B7 menu \u2014 \u663E\u793A\u6307\u4EE4\u83DC\u5355\",\n markdown: \"## SFMC QQ \u6307\u4EE4\\n\\n- **menu**\uFF1A\u663E\u793A\u6307\u4EE4\u83DC\u5355\",\n buttons: [\n { id: \"cmd_menu\", label: \"menu\", command: \"/menu\" },\n { id: \"cmd_ping\", label: \"ping\", command: \"/ping\" },\n ],\n};\n\ntest(\"renderOfficial \u5E26\u952E\u76D8\u7528 markdown\", () => {\n const p = renderOfficial(menuResult);\n assert.equal(p.msgType, 2);\n assert.equal(p.markdown, menuResult.markdown);\n assert.equal(p.keyboardButtons?.length, 2);\n assert.equal(p.keyboardButtons?.[1]?.data, \"/ping\");\n});\n\ntest(\"renderOfficial \u7EAF\u6587\u672C\", () => {\n const p = renderOfficial({ text: \"pong \u00B7 backend=official\" });\n assert.equal(p.msgType, 0);\n assert.equal(p.content, \"pong \u00B7 backend=official\");\n assert.equal(p.keyboardButtons, undefined);\n});\n\ntest(\"renderLlbot \u7F16\u53F7\u884C\", () => {\n const p = renderLlbot(menuResult);\n assert.match(p.text, /\\[1\\] menu/);\n assert.match(p.text, /\\[2\\] ping/);\n assert.match(p.text, /60 \u79D2/);\n});\n\ntest(\"renderLlbot \u6B63\u6587\u5DF2\u7F16\u53F7\u65F6\u4E0D\u91CD\u590D [n]\", () => {\n const r: CommandResult = {\n text: \"SFMC \u6307\u4EE4\\n\u8BF4\u660E\\n\\n1. ping \u2014 \u8FDE\u901A\\n2. \u72B6\u6001 \u2014 \u6458\u8981\",\n buttons: [\n { id: \"cmd_ping\", label: \"ping\", command: \"/ping\" },\n { id: \"cmd_status\", label: \"\u72B6\u6001\", command: \"/status\" },\n ],\n };\n const p = renderLlbot(r);\n assert.match(p.text, /1\\. ping/);\n assert.doesNotMatch(p.text, /\\[1\\]/);\n assert.match(p.text, /60 \u79D2/);\n});\n"],
|
|
5
|
+
"mappings": "AAGA,OAAO,YAAY;AACnB,OAAO,UAAU;AACjB,SAAS,aAAa,sBAAsB;AAG5C,MAAM,aAA4B;AAAA,EAChC,MAAM;AAAA,EACN,UAAU;AAAA,EACV,SAAS;AAAA,IACP,EAAE,IAAI,YAAY,OAAO,QAAQ,SAAS,QAAQ;AAAA,IAClD,EAAE,IAAI,YAAY,OAAO,QAAQ,SAAS,QAAQ;AAAA,EACpD;AACF;AAEA,KAAK,oDAAgC,MAAM;AACzC,QAAM,IAAI,eAAe,UAAU;AACnC,SAAO,MAAM,EAAE,SAAS,CAAC;AACzB,SAAO,MAAM,EAAE,UAAU,WAAW,QAAQ;AAC5C,SAAO,MAAM,EAAE,iBAAiB,QAAQ,CAAC;AACzC,SAAO,MAAM,EAAE,kBAAkB,CAAC,GAAG,MAAM,OAAO;AACpD,CAAC;AAED,KAAK,qCAAsB,MAAM;AAC/B,QAAM,IAAI,eAAe,EAAE,MAAM,6BAA0B,CAAC;AAC5D,SAAO,MAAM,EAAE,SAAS,CAAC;AACzB,SAAO,MAAM,EAAE,SAAS,4BAAyB;AACjD,SAAO,MAAM,EAAE,iBAAiB,MAAS;AAC3C,CAAC;AAED,KAAK,kCAAmB,MAAM;AAC5B,QAAM,IAAI,YAAY,UAAU;AAChC,SAAO,MAAM,EAAE,MAAM,YAAY;AACjC,SAAO,MAAM,EAAE,MAAM,YAAY;AACjC,SAAO,MAAM,EAAE,MAAM,MAAM;AAC7B,CAAC;AAED,KAAK,0EAA6B,MAAM;AACtC,QAAM,IAAmB;AAAA,IACvB,MAAM;AAAA,IACN,SAAS;AAAA,MACP,EAAE,IAAI,YAAY,OAAO,QAAQ,SAAS,QAAQ;AAAA,MAClD,EAAE,IAAI,cAAc,OAAO,gBAAM,SAAS,UAAU;AAAA,IACtD;AAAA,EACF;AACA,QAAM,IAAI,YAAY,CAAC;AACvB,SAAO,MAAM,EAAE,MAAM,UAAU;AAC/B,SAAO,aAAa,EAAE,MAAM,OAAO;AACnC,SAAO,MAAM,EAAE,MAAM,MAAM;AAC7B,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { request } from "node:http";
|
|
2
|
+
import { llbotWsApi } from "../llbot-ws-api.js";
|
|
3
|
+
import { log } from "../log.js";
|
|
4
|
+
import { renderLlbot } from "./render.js";
|
|
5
|
+
function createLlbotReplyPort(cfg) {
|
|
6
|
+
return {
|
|
7
|
+
async send(_target, result, _inbound) {
|
|
8
|
+
if (!cfg.groupId || cfg.groupId === "0") {
|
|
9
|
+
throw new Error("llbot qq_group_id \u672A\u914D\u7F6E");
|
|
10
|
+
}
|
|
11
|
+
const { text } = renderLlbot(result);
|
|
12
|
+
if (llbotWsApi.connected) {
|
|
13
|
+
await llbotWsApi.sendGroupMsg(cfg.groupId, text);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
log.warn("LLBot reverse-ws \u672A\u8FDE\u63A5\uFF0C\u56DE\u9000 HTTP send_group_msg");
|
|
17
|
+
await sendGroupMsgHttp(cfg, text);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function sendGroupMsgHttp(cfg, text) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const payload = JSON.stringify({
|
|
24
|
+
group_id: parseInt(cfg.groupId, 10),
|
|
25
|
+
message: [{ type: "text", data: { text } }]
|
|
26
|
+
});
|
|
27
|
+
const headers = {
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
"Content-Length": Buffer.byteLength(payload)
|
|
30
|
+
};
|
|
31
|
+
if (cfg.token) {
|
|
32
|
+
headers["Authorization"] = `Bearer ${cfg.token}`;
|
|
33
|
+
}
|
|
34
|
+
const req = request(
|
|
35
|
+
{
|
|
36
|
+
hostname: cfg.host,
|
|
37
|
+
port: cfg.port,
|
|
38
|
+
path: "/send_group_msg",
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers
|
|
41
|
+
},
|
|
42
|
+
(res) => {
|
|
43
|
+
let body = "";
|
|
44
|
+
res.on("data", (c) => body += c);
|
|
45
|
+
res.on("end", () => {
|
|
46
|
+
if (res.statusCode !== 200) {
|
|
47
|
+
log.warn(`LLBot HTTP send_group_msg \u2192 ${res.statusCode}: ${body.slice(0, 200)}`);
|
|
48
|
+
reject(new Error(`LLBot HTTP ${res.statusCode}`));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
resolve();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
req.on("error", reject);
|
|
56
|
+
req.write(payload);
|
|
57
|
+
req.end();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
createLlbotReplyPort
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=reply-llbot.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/commands/reply-llbot.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * commands/reply-llbot.ts \u2014 LLBot ReplyPort\n *\n * \u4F18\u5148\u7ECF reverse-WS \u8C03 send_group_msg\uFF1BWS \u4E0D\u53EF\u7528\u65F6\u518D\u56DE\u9000 HTTP(llbot_port)\u3002\n */\n\nimport { request } from \"node:http\";\nimport { llbotWsApi } from \"../llbot-ws-api.js\";\nimport { log } from \"../log.js\";\nimport { renderLlbot } from \"./render.js\";\nimport type { CommandResult, InboundMessage, ReplyPort, ReplyTarget } from \"./types.js\";\n\nexport type LlbotReplyConfig = {\n host: string;\n port: number;\n token: string;\n groupId: string;\n};\n\nexport function createLlbotReplyPort(cfg: LlbotReplyConfig): ReplyPort {\n return {\n async send(_target: ReplyTarget, result: CommandResult, _inbound: InboundMessage): Promise<void> {\n if (!cfg.groupId || cfg.groupId === \"0\") {\n throw new Error(\"llbot qq_group_id \u672A\u914D\u7F6E\");\n }\n const { text } = renderLlbot(result);\n\n if (llbotWsApi.connected) {\n await llbotWsApi.sendGroupMsg(cfg.groupId, text);\n return;\n }\n\n log.warn(\"LLBot reverse-ws \u672A\u8FDE\u63A5\uFF0C\u56DE\u9000 HTTP send_group_msg\");\n await sendGroupMsgHttp(cfg, text);\n },\n };\n}\n\nfunction sendGroupMsgHttp(cfg: LlbotReplyConfig, text: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const payload = JSON.stringify({\n group_id: parseInt(cfg.groupId, 10),\n message: [{ type: \"text\", data: { text } }],\n });\n const headers: Record<string, string | number> = {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(payload),\n };\n if (cfg.token) {\n headers[\"Authorization\"] = `Bearer ${cfg.token}`;\n }\n const req = request(\n {\n hostname: cfg.host,\n port: cfg.port,\n path: \"/send_group_msg\",\n method: \"POST\",\n headers,\n },\n (res) => {\n let body = \"\";\n res.on(\"data\", (c) => (body += c));\n res.on(\"end\", () => {\n if (res.statusCode !== 200) {\n log.warn(`LLBot HTTP send_group_msg \u2192 ${res.statusCode}: ${body.slice(0, 200)}`);\n reject(new Error(`LLBot HTTP ${res.statusCode}`));\n return;\n }\n resolve();\n });\n }\n );\n req.on(\"error\", reject);\n req.write(payload);\n req.end();\n });\n}\n"],
|
|
5
|
+
"mappings": "AAMA,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAC3B,SAAS,WAAW;AACpB,SAAS,mBAAmB;AAUrB,SAAS,qBAAqB,KAAkC;AACrE,SAAO;AAAA,IACL,MAAM,KAAK,SAAsB,QAAuB,UAAyC;AAC/F,UAAI,CAAC,IAAI,WAAW,IAAI,YAAY,KAAK;AACvC,cAAM,IAAI,MAAM,sCAAuB;AAAA,MACzC;AACA,YAAM,EAAE,KAAK,IAAI,YAAY,MAAM;AAEnC,UAAI,WAAW,WAAW;AACxB,cAAM,WAAW,aAAa,IAAI,SAAS,IAAI;AAC/C;AAAA,MACF;AAEA,UAAI,KAAK,2EAA6C;AACtD,YAAM,iBAAiB,KAAK,IAAI;AAAA,IAClC;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,KAAuB,MAA6B;AAC5E,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,UAAU,KAAK,UAAU;AAAA,MAC7B,UAAU,SAAS,IAAI,SAAS,EAAE;AAAA,MAClC,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,EAAE,KAAK,EAAE,CAAC;AAAA,IAC5C,CAAC;AACD,UAAM,UAA2C;AAAA,MAC/C,gBAAgB;AAAA,MAChB,kBAAkB,OAAO,WAAW,OAAO;AAAA,IAC7C;AACA,QAAI,IAAI,OAAO;AACb,cAAQ,eAAe,IAAI,UAAU,IAAI,KAAK;AAAA,IAChD;AACA,UAAM,MAAM;AAAA,MACV;AAAA,QACE,UAAU,IAAI;AAAA,QACd,MAAM,IAAI;AAAA,QACV,MAAM;AAAA,QACN,QAAQ;AAAA,QACR;AAAA,MACF;AAAA,MACA,CAAC,QAAQ;AACP,YAAI,OAAO;AACX,YAAI,GAAG,QAAQ,CAAC,MAAO,QAAQ,CAAE;AACjC,YAAI,GAAG,OAAO,MAAM;AAClB,cAAI,IAAI,eAAe,KAAK;AAC1B,gBAAI,KAAK,oCAA+B,IAAI,UAAU,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAC/E,mBAAO,IAAI,MAAM,cAAc,IAAI,UAAU,EAAE,CAAC;AAChD;AAAA,UACF;AACA,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,GAAG,SAAS,MAAM;AACtB,QAAI,MAAM,OAAO;AACjB,QAAI,IAAI;AAAA,EACV,CAAC;AACH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sendC2cMessage,
|
|
3
|
+
sendGroupMessage
|
|
4
|
+
} from "@sfmc-bds/sdk/node/qq-official";
|
|
5
|
+
import { log } from "../log.js";
|
|
6
|
+
import { renderOfficial } from "./render.js";
|
|
7
|
+
function isC2c(inbound) {
|
|
8
|
+
return inbound.scene === "c2c" || inbound.groupId.startsWith("c2c:");
|
|
9
|
+
}
|
|
10
|
+
function c2cUserOpenid(inbound) {
|
|
11
|
+
if (inbound.groupId.startsWith("c2c:")) return inbound.groupId.slice("c2c:".length);
|
|
12
|
+
return inbound.userId;
|
|
13
|
+
}
|
|
14
|
+
function createOfficialReplyPort(creds) {
|
|
15
|
+
let msgSeq = 0;
|
|
16
|
+
return {
|
|
17
|
+
async send(target, result, inbound) {
|
|
18
|
+
const rendered = renderOfficial(result);
|
|
19
|
+
msgSeq += 1;
|
|
20
|
+
const c2c = isC2c(inbound);
|
|
21
|
+
const common = {
|
|
22
|
+
msgType: rendered.msgType,
|
|
23
|
+
...rendered.msgType === 2 ? { markdown: rendered.markdown ?? result.text } : { content: rendered.content ?? result.text },
|
|
24
|
+
...rendered.keyboardButtons ? { keyboardButtons: rendered.keyboardButtons } : {},
|
|
25
|
+
...target.msgId ? { msgId: target.msgId, msgSeq } : {}
|
|
26
|
+
};
|
|
27
|
+
const sendOnce = async (plainFallback) => {
|
|
28
|
+
if (c2c) {
|
|
29
|
+
const userOpenid = c2cUserOpenid(inbound);
|
|
30
|
+
if (plainFallback) {
|
|
31
|
+
return sendC2cMessage(creds, {
|
|
32
|
+
userOpenid,
|
|
33
|
+
msgType: 0,
|
|
34
|
+
content: result.text,
|
|
35
|
+
...target.msgId ? { msgId: target.msgId, msgSeq: ++msgSeq } : {}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return sendC2cMessage(creds, { userOpenid, ...common });
|
|
39
|
+
}
|
|
40
|
+
if (plainFallback) {
|
|
41
|
+
return sendGroupMessage(creds, {
|
|
42
|
+
groupOpenid: target.groupId,
|
|
43
|
+
msgType: 0,
|
|
44
|
+
content: result.text,
|
|
45
|
+
...target.msgId ? { msgId: target.msgId, msgSeq: ++msgSeq } : {}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return sendGroupMessage(creds, { groupOpenid: target.groupId, ...common });
|
|
49
|
+
};
|
|
50
|
+
const res = await sendOnce(false);
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
if (rendered.msgType === 2) {
|
|
53
|
+
log.warn(`\u5B98\u65B9 Markdown \u53D1\u9001\u5931\u8D25\uFF0C\u964D\u7EA7\u6587\u672C: ${res.error}`);
|
|
54
|
+
const plain = await sendOnce(true);
|
|
55
|
+
if (!plain.ok) throw new Error(plain.error);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
throw new Error(res.error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
createOfficialReplyPort
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=reply-official.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/commands/reply-official.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * commands/reply-official.ts \u2014 \u5B98\u65B9 ReplyPort\uFF08\u7FA4 / C2C\uFF09\n */\n\nimport {\n sendC2cMessage,\n sendGroupMessage,\n type QqOfficialCredentials,\n} from \"@sfmc-bds/sdk/node/qq-official\";\nimport { log } from \"../log.js\";\nimport { renderOfficial } from \"./render.js\";\nimport type { CommandResult, InboundMessage, ReplyPort, ReplyTarget } from \"./types.js\";\n\nfunction isC2c(inbound: InboundMessage): boolean {\n return inbound.scene === \"c2c\" || inbound.groupId.startsWith(\"c2c:\");\n}\n\nfunction c2cUserOpenid(inbound: InboundMessage): string {\n if (inbound.groupId.startsWith(\"c2c:\")) return inbound.groupId.slice(\"c2c:\".length);\n return inbound.userId;\n}\n\nexport function createOfficialReplyPort(creds: QqOfficialCredentials): ReplyPort {\n let msgSeq = 0;\n return {\n async send(target: ReplyTarget, result: CommandResult, inbound: InboundMessage): Promise<void> {\n const rendered = renderOfficial(result);\n msgSeq += 1;\n const c2c = isC2c(inbound);\n const common = {\n msgType: rendered.msgType as 0 | 2,\n ...(rendered.msgType === 2\n ? { markdown: rendered.markdown ?? result.text }\n : { content: rendered.content ?? result.text }),\n ...(rendered.keyboardButtons ? { keyboardButtons: rendered.keyboardButtons } : {}),\n ...(target.msgId ? { msgId: target.msgId, msgSeq } : {}),\n };\n\n const sendOnce = async (plainFallback: boolean) => {\n if (c2c) {\n const userOpenid = c2cUserOpenid(inbound);\n if (plainFallback) {\n return sendC2cMessage(creds, {\n userOpenid,\n msgType: 0,\n content: result.text,\n ...(target.msgId ? { msgId: target.msgId, msgSeq: ++msgSeq } : {}),\n });\n }\n return sendC2cMessage(creds, { userOpenid, ...common });\n }\n if (plainFallback) {\n return sendGroupMessage(creds, {\n groupOpenid: target.groupId,\n msgType: 0,\n content: result.text,\n ...(target.msgId ? { msgId: target.msgId, msgSeq: ++msgSeq } : {}),\n });\n }\n return sendGroupMessage(creds, { groupOpenid: target.groupId, ...common });\n };\n\n const res = await sendOnce(false);\n if (!res.ok) {\n if (rendered.msgType === 2) {\n log.warn(`\u5B98\u65B9 Markdown \u53D1\u9001\u5931\u8D25\uFF0C\u964D\u7EA7\u6587\u672C: ${res.error}`);\n const plain = await sendOnce(true);\n if (!plain.ok) throw new Error(plain.error);\n return;\n }\n throw new Error(res.error);\n }\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "AAIA;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAS,WAAW;AACpB,SAAS,sBAAsB;AAG/B,SAAS,MAAM,SAAkC;AAC/C,SAAO,QAAQ,UAAU,SAAS,QAAQ,QAAQ,WAAW,MAAM;AACrE;AAEA,SAAS,cAAc,SAAiC;AACtD,MAAI,QAAQ,QAAQ,WAAW,MAAM,EAAG,QAAO,QAAQ,QAAQ,MAAM,OAAO,MAAM;AAClF,SAAO,QAAQ;AACjB;AAEO,SAAS,wBAAwB,OAAyC;AAC/E,MAAI,SAAS;AACb,SAAO;AAAA,IACL,MAAM,KAAK,QAAqB,QAAuB,SAAwC;AAC7F,YAAM,WAAW,eAAe,MAAM;AACtC,gBAAU;AACV,YAAM,MAAM,MAAM,OAAO;AACzB,YAAM,SAAS;AAAA,QACb,SAAS,SAAS;AAAA,QAClB,GAAI,SAAS,YAAY,IACrB,EAAE,UAAU,SAAS,YAAY,OAAO,KAAK,IAC7C,EAAE,SAAS,SAAS,WAAW,OAAO,KAAK;AAAA,QAC/C,GAAI,SAAS,kBAAkB,EAAE,iBAAiB,SAAS,gBAAgB,IAAI,CAAC;AAAA,QAChF,GAAI,OAAO,QAAQ,EAAE,OAAO,OAAO,OAAO,OAAO,IAAI,CAAC;AAAA,MACxD;AAEA,YAAM,WAAW,OAAO,kBAA2B;AACjD,YAAI,KAAK;AACP,gBAAM,aAAa,cAAc,OAAO;AACxC,cAAI,eAAe;AACjB,mBAAO,eAAe,OAAO;AAAA,cAC3B;AAAA,cACA,SAAS;AAAA,cACT,SAAS,OAAO;AAAA,cAChB,GAAI,OAAO,QAAQ,EAAE,OAAO,OAAO,OAAO,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,YAClE,CAAC;AAAA,UACH;AACA,iBAAO,eAAe,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC;AAAA,QACxD;AACA,YAAI,eAAe;AACjB,iBAAO,iBAAiB,OAAO;AAAA,YAC7B,aAAa,OAAO;AAAA,YACpB,SAAS;AAAA,YACT,SAAS,OAAO;AAAA,YAChB,GAAI,OAAO,QAAQ,EAAE,OAAO,OAAO,OAAO,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,UAClE,CAAC;AAAA,QACH;AACA,eAAO,iBAAiB,OAAO,EAAE,aAAa,OAAO,SAAS,GAAG,OAAO,CAAC;AAAA,MAC3E;AAEA,YAAM,MAAM,MAAM,SAAS,KAAK;AAChC,UAAI,CAAC,IAAI,IAAI;AACX,YAAI,SAAS,YAAY,GAAG;AAC1B,cAAI,KAAK,iFAA0B,IAAI,KAAK,EAAE;AAC9C,gBAAM,QAAQ,MAAM,SAAS,IAAI;AACjC,cAAI,CAAC,MAAM,GAAI,OAAM,IAAI,MAAM,MAAM,KAAK;AAC1C;AAAA,QACF;AACA,cAAM,IAAI,MAAM,IAAI,KAAK;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { log } from "../log.js";
|
|
2
|
+
import { normalizeTrigger } from "./registry.js";
|
|
3
|
+
class CommandRouter {
|
|
4
|
+
opts;
|
|
5
|
+
constructor(opts) {
|
|
6
|
+
this.opts = opts;
|
|
7
|
+
}
|
|
8
|
+
/** 供 menu/panel sync 读取权威命令表 */
|
|
9
|
+
get registry() {
|
|
10
|
+
return this.opts.registry;
|
|
11
|
+
}
|
|
12
|
+
async handle(inbound) {
|
|
13
|
+
const raw = String(inbound.text ?? "").trim();
|
|
14
|
+
if (!raw) return false;
|
|
15
|
+
let effective = inbound;
|
|
16
|
+
const digit = /^[1-9]$/.exec(raw);
|
|
17
|
+
if (digit) {
|
|
18
|
+
const chosen = this.opts.pending.take(inbound.backend, inbound.groupId, inbound.userId, Number(digit[0]));
|
|
19
|
+
if (!chosen) return false;
|
|
20
|
+
effective = { ...inbound, text: chosen };
|
|
21
|
+
}
|
|
22
|
+
const resolved = this.opts.registry.resolve(effective.text);
|
|
23
|
+
if (!resolved) return false;
|
|
24
|
+
const ctx = {
|
|
25
|
+
inbound: effective,
|
|
26
|
+
startedAt: this.opts.startedAt,
|
|
27
|
+
runtimeInfo: this.opts.runtimeInfo
|
|
28
|
+
};
|
|
29
|
+
const result = await resolved.handler(ctx);
|
|
30
|
+
if (effective.backend === "llbot" && result.buttons && result.buttons.length > 0) {
|
|
31
|
+
const choices = /* @__PURE__ */ new Map();
|
|
32
|
+
result.buttons.forEach((b, i) => {
|
|
33
|
+
choices.set(i + 1, b.command);
|
|
34
|
+
});
|
|
35
|
+
this.opts.pending.set(effective.backend, effective.groupId, effective.userId, choices);
|
|
36
|
+
}
|
|
37
|
+
const target = { groupId: effective.groupId };
|
|
38
|
+
if (effective.msgId) target.msgId = effective.msgId;
|
|
39
|
+
try {
|
|
40
|
+
await this.opts.reply.send(target, result, effective);
|
|
41
|
+
log.info(`cmd=${resolved.name} handled user=${effective.userId} group=${effective.groupId}`);
|
|
42
|
+
return true;
|
|
43
|
+
} catch (e) {
|
|
44
|
+
log.warn(`cmd=${resolved.name} \u56DE\u590D\u5931\u8D25: ${e.message}`);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
CommandRouter,
|
|
51
|
+
normalizeTrigger
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=router.js.map
|