@zaofan/dsh-qqbot 0.6.0 → 0.8.1
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 +17 -0
- package/README_EN.md +17 -0
- package/client/qqbot-settings.js +1495 -28
- package/dist/api/group-admin.d.ts +13 -0
- package/dist/api/group-admin.d.ts.map +1 -1
- package/dist/api/group-admin.js +23 -0
- package/dist/api/group-admin.js.map +1 -1
- package/dist/channel-tools.d.ts.map +1 -1
- package/dist/channel-tools.js +58 -1
- package/dist/channel-tools.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/features/chat-ledger.d.ts.map +1 -1
- package/dist/features/chat-ledger.js +54 -3
- package/dist/features/chat-ledger.js.map +1 -1
- package/dist/features/qq-approval.d.ts +43 -1
- package/dist/features/qq-approval.d.ts.map +1 -1
- package/dist/features/qq-approval.js +167 -17
- package/dist/features/qq-approval.js.map +1 -1
- package/dist/features/qq-user-questions.d.ts +69 -0
- package/dist/features/qq-user-questions.d.ts.map +1 -0
- package/dist/features/qq-user-questions.js +256 -0
- package/dist/features/qq-user-questions.js.map +1 -0
- package/dist/features/session-registry.d.ts +51 -0
- package/dist/features/session-registry.d.ts.map +1 -0
- package/dist/features/session-registry.js +95 -0
- package/dist/features/session-registry.js.map +1 -0
- package/dist/gateway/bootstrap.d.ts.map +1 -1
- package/dist/gateway/bootstrap.js +88 -1
- package/dist/gateway/bootstrap.js.map +1 -1
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +9 -14
- package/dist/session/session-manager.js.map +1 -1
- package/dist/transport/outbound-buffer.d.ts +2 -0
- package/dist/transport/outbound-buffer.d.ts.map +1 -1
- package/dist/transport/outbound-buffer.js.map +1 -1
- package/package.json +6 -3
- package/settings-host.js +962 -5
- package/voice-convert.mjs +45 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
const BTN_PREFIX = 'q:';
|
|
2
|
+
/** 选项按钮 keyboard(每行1个=竖排, 字宽不截断; QQ 最多5行 → 最多5个选项可见)
|
|
3
|
+
* permission: 群聊限定"发起对话者本人"可点(specify_user_ids=ownerId), 防群里其他人替你选 */
|
|
4
|
+
function optionsKeyboard(qid, token, opts, ownerId) {
|
|
5
|
+
const maxRows = 5;
|
|
6
|
+
const shown = opts.slice(0, maxRows);
|
|
7
|
+
const permission = ownerId
|
|
8
|
+
? { type: 0, specify_user_ids: [ownerId] }
|
|
9
|
+
: { type: 2 };
|
|
10
|
+
const rows = shown.map((opt, idx) => ({
|
|
11
|
+
buttons: [{
|
|
12
|
+
id: `${BTN_PREFIX}${qid}:${token}:${idx}`,
|
|
13
|
+
render_data: {
|
|
14
|
+
label: String(opt.label ?? '').slice(0, 24),
|
|
15
|
+
visited_label: String(opt.label ?? '').slice(0, 24),
|
|
16
|
+
style: 1,
|
|
17
|
+
},
|
|
18
|
+
action: {
|
|
19
|
+
type: 1,
|
|
20
|
+
permission,
|
|
21
|
+
data: `${BTN_PREFIX}${qid}:${token}:${idx}`,
|
|
22
|
+
unsupport_tips: '请直接回复选项文字',
|
|
23
|
+
},
|
|
24
|
+
}],
|
|
25
|
+
}));
|
|
26
|
+
return { content: { rows } };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 解析"这张提问卡片该谁回答": 扫最近(≤60 条)真人 user/message——
|
|
30
|
+
* ① 消息文本里的 <@openid>: 取最后一个(提问目标通常放在句末; 前面常是 @bot 自己)
|
|
31
|
+
* ② 无点名: 取消息发送者壳 [昵称 (openid)] 的 openid(谁发的就给谁答)
|
|
32
|
+
* ③ 兜底: 会话发起者 record.senderId
|
|
33
|
+
* plugin 注入(上下文/系统)跳过。
|
|
34
|
+
*/
|
|
35
|
+
function resolveCardOwner(record) {
|
|
36
|
+
try {
|
|
37
|
+
const evs = record.agent?.session?.events;
|
|
38
|
+
if (Array.isArray(evs) && evs.length > 0) {
|
|
39
|
+
const from = Math.max(0, evs.length - 60);
|
|
40
|
+
for (let i = evs.length - 1; i >= from; i--) {
|
|
41
|
+
const ev = evs[i];
|
|
42
|
+
if (!ev || ev.type !== 'user/message')
|
|
43
|
+
continue;
|
|
44
|
+
const data = ev.data && typeof ev.data === 'object' ? ev.data : ev;
|
|
45
|
+
const src = data.source;
|
|
46
|
+
if (src && src.kind === 'plugin')
|
|
47
|
+
continue;
|
|
48
|
+
const content = Array.isArray(data.content) ? data.content : [];
|
|
49
|
+
const text = content.map((b) => (b?.text ?? '')).join('\n');
|
|
50
|
+
if (!text)
|
|
51
|
+
continue;
|
|
52
|
+
const ats = [];
|
|
53
|
+
for (const m of text.matchAll(/<@([A-Za-z0-9]{32})>/g))
|
|
54
|
+
ats.push(m[1]);
|
|
55
|
+
if (ats.length > 0)
|
|
56
|
+
return ats[ats.length - 1];
|
|
57
|
+
const shell = text.match(/\[[^\]\n]*?\s*\(([A-Za-z0-9]{32})\)\]/);
|
|
58
|
+
if (shell)
|
|
59
|
+
return shell[1];
|
|
60
|
+
return record.senderId;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch { /* 解析失败回落 */ }
|
|
65
|
+
return record.senderId;
|
|
66
|
+
}
|
|
67
|
+
export class QqUserQuestionsController {
|
|
68
|
+
manager;
|
|
69
|
+
sender;
|
|
70
|
+
logger;
|
|
71
|
+
timeoutMsProvider;
|
|
72
|
+
pending = new Map();
|
|
73
|
+
constructor(manager, sender, logger, timeoutMsProvider) {
|
|
74
|
+
this.manager = manager;
|
|
75
|
+
this.sender = sender;
|
|
76
|
+
this.logger = logger;
|
|
77
|
+
this.timeoutMsProvider = timeoutMsProvider;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 宿主 user-questions/request 处理器。只 claim"会话可定位 + 第一个问题带选项"的场景;
|
|
81
|
+
* 其余(无会话/无选项/多问题) → next() 交回宿主(Web UI 或原样)。
|
|
82
|
+
*/
|
|
83
|
+
async request(req, next) {
|
|
84
|
+
const record = this.manager.findByAgent(req.agent);
|
|
85
|
+
if (!record)
|
|
86
|
+
return next();
|
|
87
|
+
const questions = Array.isArray(req.questions) ? req.questions : [];
|
|
88
|
+
if (questions.length === 0)
|
|
89
|
+
return next();
|
|
90
|
+
const q = questions[0];
|
|
91
|
+
const qid = String(q?.id ?? '');
|
|
92
|
+
const opts = Array.isArray(q?.options) ? q.options : [];
|
|
93
|
+
if (!qid || opts.length === 0 || opts.length > 25)
|
|
94
|
+
return next();
|
|
95
|
+
const timeoutMs = Math.max(5000, this.timeoutMsProvider() || 120000);
|
|
96
|
+
const token = Math.random().toString(36).slice(2, 8);
|
|
97
|
+
const key = `${qid}:${token}`;
|
|
98
|
+
const headerRaw = String(q?.header ?? '');
|
|
99
|
+
const detailRaw = String(q?.detail ?? '');
|
|
100
|
+
const questionRaw = String(q?.question ?? '');
|
|
101
|
+
// 卡片可点人: 从最近真人消息解析——被点名的最后一人(@)优先, 其次消息发送者壳, 回落会话发起者
|
|
102
|
+
// (修复: 主人让 bot 问群友时, 卡片应绑被问者而不是 bot/主人, 否则被问者点卡片=无权限)
|
|
103
|
+
const owner = resolveCardOwner(record);
|
|
104
|
+
// 先注册 pending(防按钮回调先于 Promise 建立到达)
|
|
105
|
+
let resolver = () => undefined;
|
|
106
|
+
const answer = new Promise((resolve) => { resolver = resolve; });
|
|
107
|
+
const item = {
|
|
108
|
+
qid, question: questionRaw, header: headerRaw, detail: detailRaw,
|
|
109
|
+
opts: opts.map((o) => ({ label: String(o?.label ?? '') })),
|
|
110
|
+
ownerId: owner,
|
|
111
|
+
deadlineAt: Date.now() + timeoutMs,
|
|
112
|
+
resolve: resolver, timer: setTimeout(() => this.settle(key, true), timeoutMs),
|
|
113
|
+
signal: req.signal,
|
|
114
|
+
};
|
|
115
|
+
if (req.signal) {
|
|
116
|
+
item.onAbort = () => this.settle(key, true);
|
|
117
|
+
try {
|
|
118
|
+
req.signal.addEventListener('abort', item.onAbort, { once: true });
|
|
119
|
+
}
|
|
120
|
+
catch { /* ignore */ }
|
|
121
|
+
}
|
|
122
|
+
this.pending.set(key, item);
|
|
123
|
+
const header = headerRaw ? `**${headerRaw}**\n\n` : '';
|
|
124
|
+
const detail = detailRaw ? `\n\n${detailRaw}` : '';
|
|
125
|
+
const prompt = `${header}❓ **${questionRaw}**${detail}\n\n⏱ ${Math.ceil(timeoutMs / 1000)} 秒内点下方按钮回答:`;
|
|
126
|
+
try {
|
|
127
|
+
await this.sender.sendMarkdownWithKeyboard(record.replyTarget, prompt, optionsKeyboard(qid, token, opts, owner));
|
|
128
|
+
this.logger.info(`QQ question sent: qid=${qid} opts=${opts.length}`);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
this.logger.warn(`QQ question card failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
132
|
+
// 卡片不可用 → 文本列选项, 不 claim(回复走正常入站, agent 自己取答案)
|
|
133
|
+
const lines = [`❓ ${questionRaw}`, '', ...opts.map((o, i) => `${i + 1}. ${o?.label ?? ''}`), '', '(直接回复序号或内容)'];
|
|
134
|
+
try {
|
|
135
|
+
await this.sender.sendMarkdown(record.replyTarget, lines.join('\n'));
|
|
136
|
+
}
|
|
137
|
+
catch (err2) {
|
|
138
|
+
// QQ 文本降级也失败(限频/网络)≠ 提问结束: 保留 pending 供 Web 浮层兜底,
|
|
139
|
+
// 不要 settle —— 否则 QQ 没送达时 web 也看不到。
|
|
140
|
+
this.logger.error(`QQ question fallback text failed: ${err2 instanceof Error ? err2.message : String(err2)} (qid=${qid}, web 浮层可兜底)`);
|
|
141
|
+
return answer;
|
|
142
|
+
}
|
|
143
|
+
this.settle(key, true);
|
|
144
|
+
}
|
|
145
|
+
return answer;
|
|
146
|
+
}
|
|
147
|
+
/** interaction 回调: data = q:<qid>:<token>:<idx> → 按选项 label 组 answer; 仅发起者本人可答 */
|
|
148
|
+
async handleInteraction(event, replyTarget) {
|
|
149
|
+
const e = event;
|
|
150
|
+
if (e?.data?.type !== 11)
|
|
151
|
+
return false;
|
|
152
|
+
const data = e.data.resolved?.button_data;
|
|
153
|
+
if (!data || !data.startsWith(BTN_PREFIX))
|
|
154
|
+
return false;
|
|
155
|
+
const m = /^q:([^:]+):([^:]+):(\d+)$/.exec(data);
|
|
156
|
+
if (!m)
|
|
157
|
+
return false;
|
|
158
|
+
const qid = m[1];
|
|
159
|
+
const token = m[2];
|
|
160
|
+
const idx = Number(m[3]);
|
|
161
|
+
const key = `${qid}:${token}`;
|
|
162
|
+
const item = this.pending.get(key);
|
|
163
|
+
if (!item)
|
|
164
|
+
return false;
|
|
165
|
+
// 防别人代答: 群聊看 group_member_openid, c2c 看 user_openid, 必须等于发起者
|
|
166
|
+
const presser = e.group_member_openid ?? e.user_openid ?? '';
|
|
167
|
+
if (item.ownerId && presser && presser !== item.ownerId) {
|
|
168
|
+
try {
|
|
169
|
+
this.sender.sendMarkdown(replyTarget, '这个问题不是问你的哦~').catch(() => undefined);
|
|
170
|
+
}
|
|
171
|
+
catch { /* ignore */ }
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
const label = item.opts[idx]?.label ?? '';
|
|
175
|
+
if (!label)
|
|
176
|
+
return false;
|
|
177
|
+
try {
|
|
178
|
+
this.sender.sendMarkdown(replyTarget, `已选择:${label}`).catch(() => undefined);
|
|
179
|
+
}
|
|
180
|
+
catch { /* ignore */ }
|
|
181
|
+
this.settle(key, false, { answers: [{ id: item.qid, selected: [label] }] });
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
/** 结算: timedOut/abort → 空答案(selected:[]); answered → 携带选择 */
|
|
185
|
+
settle(key, empty, value) {
|
|
186
|
+
const item = this.pending.get(key);
|
|
187
|
+
if (!item)
|
|
188
|
+
return;
|
|
189
|
+
this.pending.delete(key);
|
|
190
|
+
clearTimeout(item.timer);
|
|
191
|
+
if (item.signal && item.onAbort) {
|
|
192
|
+
try {
|
|
193
|
+
item.signal.removeEventListener('abort', item.onAbort);
|
|
194
|
+
}
|
|
195
|
+
catch { /* ignore */ }
|
|
196
|
+
}
|
|
197
|
+
item.resolve(empty ? { answers: [{ id: item.qid, selected: [] }] } : value);
|
|
198
|
+
}
|
|
199
|
+
/** Web 浮层拉取待办问题列表 */
|
|
200
|
+
listPendingForWeb() {
|
|
201
|
+
const out = [];
|
|
202
|
+
for (const [key, p] of this.pending) {
|
|
203
|
+
out.push({
|
|
204
|
+
key,
|
|
205
|
+
question: p.question,
|
|
206
|
+
header: p.header,
|
|
207
|
+
detail: p.detail,
|
|
208
|
+
options: p.opts,
|
|
209
|
+
deadlineAt: p.deadlineAt,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return out.sort((a, b) => a.deadlineAt - b.deadlineAt);
|
|
213
|
+
}
|
|
214
|
+
/** Web 浮层点选项: key + 选项下标 → 与 QQ 按钮同源结算(先到先得) */
|
|
215
|
+
decideByWeb(key, optIdx) {
|
|
216
|
+
const item = this.pending.get(key);
|
|
217
|
+
if (!item)
|
|
218
|
+
return { ok: false, msg: '该问题不存在或已过期' };
|
|
219
|
+
const label = item.opts[optIdx]?.label;
|
|
220
|
+
if (!label)
|
|
221
|
+
return { ok: false, msg: '无效选项' };
|
|
222
|
+
this.settle(key, false, { answers: [{ id: item.qid, selected: [label] }] });
|
|
223
|
+
return { ok: true, msg: `已选择:${label}` };
|
|
224
|
+
}
|
|
225
|
+
dispose() {
|
|
226
|
+
for (const key of [...this.pending.keys()])
|
|
227
|
+
this.settle(key, true);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// ── 按实例(ns)注册的提问控制器注册表 —— Web 提问浮层(settings-host 同源路由)经此读写 ──
|
|
231
|
+
const questionControllers = new Map();
|
|
232
|
+
export function registerQuestionController(ns, c) {
|
|
233
|
+
if (c)
|
|
234
|
+
questionControllers.set(ns, c);
|
|
235
|
+
else
|
|
236
|
+
questionControllers.delete(ns);
|
|
237
|
+
}
|
|
238
|
+
/** 汇总所有实例的待办提问 */
|
|
239
|
+
export function listAllPendingQuestionsWeb() {
|
|
240
|
+
const out = [];
|
|
241
|
+
for (const [ns, c] of questionControllers) {
|
|
242
|
+
for (const p of c.listPendingForWeb())
|
|
243
|
+
out.push({ ns, ...p });
|
|
244
|
+
}
|
|
245
|
+
return out.sort((a, b) => a.deadlineAt - b.deadlineAt);
|
|
246
|
+
}
|
|
247
|
+
/** 跨实例结算提问 */
|
|
248
|
+
export function decideQuestionByWebAny(key, optIdx) {
|
|
249
|
+
for (const [ns, c] of questionControllers) {
|
|
250
|
+
const r = c.decideByWeb(key, optIdx);
|
|
251
|
+
if (r.ok)
|
|
252
|
+
return { ok: true, ns, msg: r.msg };
|
|
253
|
+
}
|
|
254
|
+
return { ok: false, msg: '该问题不存在或已过期' };
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=qq-user-questions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qq-user-questions.js","sourceRoot":"","sources":["../../src/features/qq-user-questions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-registry.ts — 按实例(ns)注册 SessionManager 的模块级注册表
|
|
3
|
+
*
|
|
4
|
+
* 与 qq-approval.ts 的 registerApprovalController 同款模式:
|
|
5
|
+
* qqbot 本体 bootstrap 里 registerSessionManager(ns, manager), effect 清理注销;
|
|
6
|
+
* settings-host.js 动态 import 本模块后, 可跨"插件模块"拿到各实例的 SessionManager
|
|
7
|
+
* (同一物理文件同一 ESM 实例, 无 realm 隔离 —— 已被 approval/questions 双通道验证)。
|
|
8
|
+
*
|
|
9
|
+
* 用途:
|
|
10
|
+
* - 线A(悬浮球自动选中): 按 web sessionId 反查 QQ 会话(精确命中含 fork 后 randomUUID);
|
|
11
|
+
* - 线B(用户代发插入上下文): 按 ns+scope+peerId 找会话 record, append user/message(模拟用户消息)。
|
|
12
|
+
*/
|
|
13
|
+
import type { SessionManager } from '../session/index.js';
|
|
14
|
+
import type { ChatScope } from '../types.js';
|
|
15
|
+
export declare function registerSessionManager(ns: string, m: SessionManager | undefined): void;
|
|
16
|
+
/** 遍历所有实例, 按 web sessionId 精确反查会话记录(唯一可靠源: record.sessionId 含 fork 后的 randomUUID) */
|
|
17
|
+
export declare function findSessionBySessionIdWeb(sessionId: string): Array<{
|
|
18
|
+
ns: string;
|
|
19
|
+
sessionId: string;
|
|
20
|
+
scope: ChatScope;
|
|
21
|
+
peerId: string;
|
|
22
|
+
senderId: string;
|
|
23
|
+
agentPreset?: string;
|
|
24
|
+
lastActivity: number;
|
|
25
|
+
}>;
|
|
26
|
+
/** 按 ns+scope+peerId 找会话记录(线B: 用户代发后把消息 append 进该会话) */
|
|
27
|
+
export declare function findRecordByPeerWeb(ns: string, scope: ChatScope, peerId: string): {
|
|
28
|
+
ns: string;
|
|
29
|
+
sessionId: string;
|
|
30
|
+
scope: ChatScope;
|
|
31
|
+
peerId: string;
|
|
32
|
+
senderId: string;
|
|
33
|
+
agent?: unknown;
|
|
34
|
+
} | undefined;
|
|
35
|
+
/** 活跃表 miss(会话被回收/未建立)时恢复/重建会话 —— 与入群申请通知/定时任务同款 getOrCreate(不开回合, 只保证 log 存在) */
|
|
36
|
+
export declare function getOrCreateByPeerWeb(ns: string, scope: ChatScope, peerId: string, senderId?: string): Promise<{
|
|
37
|
+
ns: string;
|
|
38
|
+
sessionId: string;
|
|
39
|
+
scope: ChatScope;
|
|
40
|
+
peerId: string;
|
|
41
|
+
senderId: string;
|
|
42
|
+
agent?: unknown;
|
|
43
|
+
} | undefined>;
|
|
44
|
+
/** 标记某实例在线状态(bootstrap bot ready/error/resumed 事件驱动) */
|
|
45
|
+
export declare function setBotOnline(ns: string, on: boolean): void;
|
|
46
|
+
/** 某实例是否在线(未注册/未知 = false) */
|
|
47
|
+
export declare function isBotOnline(ns: string): boolean;
|
|
48
|
+
/** 是否已注册某 ns(诊断用) */
|
|
49
|
+
export declare function hasManager(ns: string): boolean;
|
|
50
|
+
export declare function listManagerNs(): string[];
|
|
51
|
+
//# sourceMappingURL=session-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../../src/features/session-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,CAGtF;AAED,qFAAqF;AACrF,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAAC;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC,CAwBD;AAED,wDAAwD;AACxD,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,GACb;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAepH;AAED,kFAAkF;AAClF,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,QAAQ,SAAW,GAClB,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAAC,CAe7H;AAID,wDAAwD;AACxD,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CAG1D;AACD,8BAA8B;AAC9B,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED,qBAAqB;AACrB,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED,wBAAgB,aAAa,IAAI,MAAM,EAAE,CAExC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const managers = new Map();
|
|
2
|
+
export function registerSessionManager(ns, m) {
|
|
3
|
+
if (m)
|
|
4
|
+
managers.set(ns, m);
|
|
5
|
+
else
|
|
6
|
+
managers.delete(ns);
|
|
7
|
+
}
|
|
8
|
+
/** 遍历所有实例, 按 web sessionId 精确反查会话记录(唯一可靠源: record.sessionId 含 fork 后的 randomUUID) */
|
|
9
|
+
export function findSessionBySessionIdWeb(sessionId) {
|
|
10
|
+
const out = [];
|
|
11
|
+
for (const [ns, m] of managers) {
|
|
12
|
+
try {
|
|
13
|
+
const r = m.findBySessionId(sessionId);
|
|
14
|
+
if (r) {
|
|
15
|
+
out.push({
|
|
16
|
+
ns,
|
|
17
|
+
sessionId: r.sessionId,
|
|
18
|
+
scope: r.scope,
|
|
19
|
+
peerId: r.peerId,
|
|
20
|
+
senderId: r.senderId,
|
|
21
|
+
agentPreset: r.agentPreset,
|
|
22
|
+
lastActivity: typeof r.lastActivity === 'number'
|
|
23
|
+
? r.lastActivity
|
|
24
|
+
: 0,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch { /* 单实例异常跳过 */ }
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
/** 按 ns+scope+peerId 找会话记录(线B: 用户代发后把消息 append 进该会话) */
|
|
33
|
+
export function findRecordByPeerWeb(ns, scope, peerId) {
|
|
34
|
+
const m = managers.get(ns);
|
|
35
|
+
if (!m)
|
|
36
|
+
return undefined;
|
|
37
|
+
try {
|
|
38
|
+
const r = m.findByPeer(scope, peerId);
|
|
39
|
+
if (!r)
|
|
40
|
+
return undefined;
|
|
41
|
+
return {
|
|
42
|
+
ns,
|
|
43
|
+
sessionId: r.sessionId,
|
|
44
|
+
scope: r.scope,
|
|
45
|
+
peerId: r.peerId,
|
|
46
|
+
senderId: r.senderId,
|
|
47
|
+
agent: r.agent,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** 活跃表 miss(会话被回收/未建立)时恢复/重建会话 —— 与入群申请通知/定时任务同款 getOrCreate(不开回合, 只保证 log 存在) */
|
|
55
|
+
export async function getOrCreateByPeerWeb(ns, scope, peerId, senderId = 'master') {
|
|
56
|
+
const m = managers.get(ns);
|
|
57
|
+
if (!m || typeof m.getOrCreate !== 'function')
|
|
58
|
+
return undefined;
|
|
59
|
+
try {
|
|
60
|
+
const r = await m.getOrCreate(scope, peerId, senderId, { scope, targetId: peerId });
|
|
61
|
+
if (!r)
|
|
62
|
+
return undefined;
|
|
63
|
+
return {
|
|
64
|
+
ns,
|
|
65
|
+
sessionId: r.sessionId,
|
|
66
|
+
scope: r.scope,
|
|
67
|
+
peerId: r.peerId,
|
|
68
|
+
senderId: r.senderId,
|
|
69
|
+
agent: r.agent,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const botOnline = new Map();
|
|
77
|
+
/** 标记某实例在线状态(bootstrap bot ready/error/resumed 事件驱动) */
|
|
78
|
+
export function setBotOnline(ns, on) {
|
|
79
|
+
if (on)
|
|
80
|
+
botOnline.set(ns, true);
|
|
81
|
+
else
|
|
82
|
+
botOnline.delete(ns);
|
|
83
|
+
}
|
|
84
|
+
/** 某实例是否在线(未注册/未知 = false) */
|
|
85
|
+
export function isBotOnline(ns) {
|
|
86
|
+
return botOnline.get(ns) === true;
|
|
87
|
+
}
|
|
88
|
+
/** 是否已注册某 ns(诊断用) */
|
|
89
|
+
export function hasManager(ns) {
|
|
90
|
+
return managers.has(ns);
|
|
91
|
+
}
|
|
92
|
+
export function listManagerNs() {
|
|
93
|
+
return [...managers.keys()];
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=session-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../../src/features/session-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../src/gateway/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAKnD,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAK5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../src/gateway/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAKnD,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAK5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AA2B1C,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAsYf"}
|
|
@@ -10,12 +10,25 @@ import { initStickerGate, bindStickerGates, flushStickerGate, getStickerGate, St
|
|
|
10
10
|
import { startScheduler } from '../features/scheduler.js';
|
|
11
11
|
import { configureScheduleStore, getScheduleStore } from '../features/schedule-store.js';
|
|
12
12
|
import { setChannelBridge } from '../channel-tools.js';
|
|
13
|
-
import { QqApprovalController, setApprovalDispatch, makeApprovalListener } from '../features/qq-approval.js';
|
|
13
|
+
import { QqApprovalController, setApprovalDispatch, makeApprovalListener, registerApprovalController } from '../features/qq-approval.js';
|
|
14
|
+
import { QqUserQuestionsController, registerQuestionController } from '../features/qq-user-questions.js';
|
|
14
15
|
import { handleGroupJoinRequestEvent } from '../features/group-join-request.js';
|
|
16
|
+
import { registerSessionManager, setBotOnline } from '../features/session-registry.js';
|
|
17
|
+
/** 从 interaction 事件推出"回复目标"(回执发到按钮所在群/私聊)。scope 由事件 chat_type/scene 推断 */
|
|
18
|
+
function replyTargetOfInteraction(event, _ctx, _manager) {
|
|
19
|
+
const e = event;
|
|
20
|
+
if (e?.group_openid)
|
|
21
|
+
return { scope: 'group', targetId: e.group_openid };
|
|
22
|
+
if (e?.user_openid)
|
|
23
|
+
return { scope: 'c2c', targetId: e.user_openid };
|
|
24
|
+
// 兜底: 群聊 member_openid 只能推群, 但没有 group_openid 无法构造 → 空 target(上层会忽略)
|
|
25
|
+
return { scope: (e?.chat_type === 1 || e?.scene === 'group') ? 'group' : 'c2c', targetId: e?.group_member_openid ?? '' };
|
|
26
|
+
}
|
|
15
27
|
export async function bootstrapGateway(ctx, agents, config, logger) {
|
|
16
28
|
const manager = new SessionManager(ctx, agents, config, logger);
|
|
17
29
|
// QQ 远程审批控制器(enableApprovals 时创建; 定义在 sender 就绪后, 此处先声明供入站回调引用)
|
|
18
30
|
let approvalController;
|
|
31
|
+
let questionController;
|
|
19
32
|
// ── 表情包图库单例预初始化(防目录分裂) ──
|
|
20
33
|
// ⚠️ 单例时序坑:谁先 getStickerStore 谁定路径。必须在启动早期按 config.cwd
|
|
21
34
|
// 初始化,否则 list_stickers(无参)会以 process.cwd 建错目录(线上踩坑:C盘幽灵库)。
|
|
@@ -140,9 +153,16 @@ export async function bootstrapGateway(ctx, agents, config, logger) {
|
|
|
140
153
|
// 平台限制: markdown 主动消息常需专门权限; c2c 主动需 48h 互动窗, 窗口外拒收。
|
|
141
154
|
// 降级顺序: sendMarkdown → sendText(纯文本主动) → c2c sendWakeup(is_wakeup 召回, 30天窗)。
|
|
142
155
|
if (!target.msgId) {
|
|
156
|
+
// ⚠️ 清洗仅针对 markdown 语法字符; QQ at 标签必须原样保留——
|
|
157
|
+
// 实测(2026-09-06): 高亮 @ 用 `<@openid>`(无斜杠)或 `<qqbot-at-user id="openid" />`;
|
|
158
|
+
// 剥掉 '>' 会把标签弄残 → QQ 不渲染。这里先把两类 at 标签暂存, 清洗完再恢复。
|
|
143
159
|
const plain = String(content || '')
|
|
160
|
+
.replace(/<qqbot-at-user\s+id="([A-Za-z0-9]+)"\s*\/>/g, '\u0001ATQ:$1\u0001')
|
|
161
|
+
.replace(/<@([A-Za-z0-9]+)>/g, '\u0001ATA:$1\u0001')
|
|
144
162
|
.replace(/[#*`_~>]/g, '')
|
|
145
163
|
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
164
|
+
.replace(/\u0001ATQ:([A-Za-z0-9]+)\u0001/g, '<qqbot-at-user id="$1" />')
|
|
165
|
+
.replace(/\u0001ATA:([A-Za-z0-9]+)\u0001/g, '<@$1>')
|
|
146
166
|
.trim();
|
|
147
167
|
if (plain) {
|
|
148
168
|
try {
|
|
@@ -172,6 +192,13 @@ export async function bootstrapGateway(ctx, agents, config, logger) {
|
|
|
172
192
|
throw err;
|
|
173
193
|
}
|
|
174
194
|
},
|
|
195
|
+
sendMarkdownWithKeyboard: async (target, content, keyboard) => {
|
|
196
|
+
// 卡片按钮(审批/提问): bot.send 显式 msg_type=2 + keyboard。
|
|
197
|
+
// 失败直接抛(不静默降级)——由调用方决定文本兜底, 避免"卡片+文本码"双发。
|
|
198
|
+
const resp = (await bot.send({ target, msgType: 2, markdown: { content }, keyboard: keyboard }));
|
|
199
|
+
pushSent(target, resp?.id);
|
|
200
|
+
return resp;
|
|
201
|
+
},
|
|
175
202
|
openStream: (target) => bot.openStream({
|
|
176
203
|
target: {
|
|
177
204
|
scope: target.scope,
|
|
@@ -238,6 +265,10 @@ export async function bootstrapGateway(ctx, agents, config, logger) {
|
|
|
238
265
|
// ①dispatch: ownership(只处理本 bot 的 agent)+ enableApprovals 闸门;
|
|
239
266
|
// ②监听: 注册在插件 apply ctx, 用 {prepend:true} 插到链首(抢在宿主 GUI 转发器 dsh-api-remotes 之前)。
|
|
240
267
|
approvalController = new QqApprovalController(manager, sender, logger, () => config.approvalTimeoutMs);
|
|
268
|
+
// 注册进 ns 注册表(Web 审批浮层经 settings-host 同源路由读写); ns 取 settingsNs 与 index.ts 一致
|
|
269
|
+
const myNs = config.settingsNs?.trim() || 'im-qqbot';
|
|
270
|
+
registerApprovalController(myNs, approvalController);
|
|
271
|
+
registerSessionManager(myNs, manager);
|
|
241
272
|
setApprovalDispatch(((request, next) => {
|
|
242
273
|
if (!config.enableApprovals)
|
|
243
274
|
return next();
|
|
@@ -249,13 +280,65 @@ export async function bootstrapGateway(ctx, agents, config, logger) {
|
|
|
249
280
|
ctx.on('approval/request', makeApprovalListener(), { prepend: true });
|
|
250
281
|
console.log('[qq-approval] ACP-mode listener registered (apply-ctx + prepend)');
|
|
251
282
|
logger.info(`[im-qqbot] QQ 远程审批接线就绪(${config.enableApprovals ? '已启用' : '默认关闭, Web 设置可热开'})`);
|
|
283
|
+
// ── QQ 远程提问(按钮卡片版): ask_user_question → QQ 卡片按钮 ──
|
|
284
|
+
// 宿主 user-questions/request 是 Agent 作用域 waterfall(同 approval), Web UI 答案器
|
|
285
|
+
// 在宿主引导期注册 → 本监听同样 {prepend:true} 抢链首。enableQuestions 默认开(与审批独立开关)。
|
|
286
|
+
const enableUserQuestions = config.enableUserQuestions !== false;
|
|
287
|
+
questionController = new QqUserQuestionsController(manager, sender, logger, () => config.approvalTimeoutMs || 120000);
|
|
288
|
+
registerQuestionController(myNs, questionController);
|
|
289
|
+
ctx.on('user-questions/request', (async (request, next) => {
|
|
290
|
+
if (!enableUserQuestions)
|
|
291
|
+
return next();
|
|
292
|
+
const reqAgent = request.agent;
|
|
293
|
+
if (!reqAgent || !manager.findByAgent(reqAgent))
|
|
294
|
+
return next(); // 非本 bot agent → GUI 兜底
|
|
295
|
+
try {
|
|
296
|
+
return await questionController.request(request, next);
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
logger.warn(`[qq-questions] request 异常: ${err instanceof Error ? err.message : String(err)}`);
|
|
300
|
+
return next();
|
|
301
|
+
}
|
|
302
|
+
}), { prepend: true });
|
|
303
|
+
logger.info(`[im-qqbot] QQ 远程提问接线就绪(${enableUserQuestions ? '启用' : '关闭'})`);
|
|
304
|
+
// ── 按钮回调(INTERACTION_CREATE, type=11): 审批/提问卡片共用分发 ──
|
|
305
|
+
// SDK 事件: bot.on('interaction', (ctx, event) => …); 需 PUT /interactions/{id} 回应防 loading。
|
|
306
|
+
if (typeof bot.on === 'function') {
|
|
307
|
+
bot.on('interaction', (async (ctx, event) => {
|
|
308
|
+
const ev = event;
|
|
309
|
+
if (ev?.data?.type !== 11)
|
|
310
|
+
return; // 只处理消息按钮回调
|
|
311
|
+
const target = replyTargetOfInteraction(ev, ctx, manager);
|
|
312
|
+
let consumed = false;
|
|
313
|
+
try {
|
|
314
|
+
if (approvalController)
|
|
315
|
+
consumed = await approvalController.handleInteraction(ev, target);
|
|
316
|
+
if (!consumed && questionController)
|
|
317
|
+
consumed = await questionController.handleInteraction(ev, target);
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
logger.warn(`[qq-interaction] 处理异常: ${err instanceof Error ? err.message : String(err)}`);
|
|
321
|
+
}
|
|
322
|
+
// 回应 loading(无论是否消费都要 ack; 防客户端一直转圈)
|
|
323
|
+
try {
|
|
324
|
+
await bot.acknowledgeInteraction(ev.id ?? '');
|
|
325
|
+
}
|
|
326
|
+
catch { /* ack 失败不致命 */ }
|
|
327
|
+
}));
|
|
328
|
+
logger.info('[im-qqbot] interaction(按钮回调)分发就绪');
|
|
329
|
+
}
|
|
252
330
|
const outboundHandler = createOutboundHandler(manager, sender, config, logger, toolsRegistry);
|
|
253
331
|
ctx
|
|
254
332
|
.on('session/event', outboundHandler);
|
|
255
333
|
bot.on('error', (err) => {
|
|
256
334
|
logger.error(`bot error: ${err instanceof Error ? err.message : String(err)}`);
|
|
335
|
+
setBotOnline(myNs, false);
|
|
336
|
+
});
|
|
337
|
+
bot.on('resumed', () => {
|
|
338
|
+
setBotOnline(myNs, true);
|
|
257
339
|
});
|
|
258
340
|
bot.on('ready', () => {
|
|
341
|
+
setBotOnline(myNs, true);
|
|
259
342
|
console.log(`[im-qqbot] Bot ready! appId=${config.appId}`);
|
|
260
343
|
});
|
|
261
344
|
// ── 生命周期 ──
|
|
@@ -331,7 +414,11 @@ export async function bootstrapGateway(ctx, agents, config, logger) {
|
|
|
331
414
|
logger.info(`[schedule] ticker 启动 (30s, dataDir=${scheduleDataDir})`);
|
|
332
415
|
return async () => {
|
|
333
416
|
logger.info('Shutting down');
|
|
417
|
+
registerApprovalController(myNs, undefined);
|
|
418
|
+
registerQuestionController(myNs, undefined);
|
|
419
|
+
registerSessionManager(myNs, undefined);
|
|
334
420
|
approvalController?.dispose();
|
|
421
|
+
questionController?.dispose();
|
|
335
422
|
stopScheduler?.();
|
|
336
423
|
if (scheduleTicker) {
|
|
337
424
|
clearInterval(scheduleTicker);
|