@wenbin_wb/dsh-bridge 2.1.2 → 2.2.0
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/lib/qq/gateway.js +81 -13
- package/lib/qq/index.js +71 -4
- package/lib/qq/node.js +76 -36
- package/package.json +1 -1
package/lib/qq/gateway.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Service } from '@deepseek-ai/cordis'
|
|
|
5
5
|
import WebSocket from 'ws'
|
|
6
6
|
|
|
7
7
|
const TOKEN_URL = 'https://bots.qq.com/app/getAppAccessToken'
|
|
8
|
-
const API_BASE = 'https://api.
|
|
8
|
+
const API_BASE = 'https://api.bot.qq.com'
|
|
9
9
|
const DEFAULT_GATEWAY = 'wss://api.sgroup.qq.com/websocket/'
|
|
10
10
|
const MAX_MESSAGE_CHARS = 2000
|
|
11
11
|
const TOKEN_MARGIN_MS = 5 * 60_000
|
|
@@ -323,6 +323,7 @@ export class QqGateway extends Service {
|
|
|
323
323
|
method: 'POST',
|
|
324
324
|
body: {
|
|
325
325
|
markdown: typeof markdown === 'string' ? { content: markdown } : markdown,
|
|
326
|
+
keyboard: opts.keyboard,
|
|
326
327
|
msg_type: 2,
|
|
327
328
|
msg_id: opts.msgId,
|
|
328
329
|
event_id: opts.eventId,
|
|
@@ -331,12 +332,14 @@ export class QqGateway extends Service {
|
|
|
331
332
|
}
|
|
332
333
|
|
|
333
334
|
async sendKeyboard(peerId, content, keyboard, opts = {}) {
|
|
335
|
+
// 官方文档:keyboard 为附加字段,配合 msg_type=0(content) 或 msg_type=2(markdown) 使用
|
|
336
|
+
// 纯文本 + 键盘时使用 msg_type=0
|
|
334
337
|
return this.api(this.endpoint(peerId, opts.scope), {
|
|
335
338
|
method: 'POST',
|
|
336
339
|
body: {
|
|
337
340
|
content: stringValue(content),
|
|
338
341
|
keyboard,
|
|
339
|
-
msg_type:
|
|
342
|
+
msg_type: 0,
|
|
340
343
|
msg_id: opts.msgId,
|
|
341
344
|
event_id: opts.eventId,
|
|
342
345
|
},
|
|
@@ -355,15 +358,19 @@ export class QqGateway extends Service {
|
|
|
355
358
|
}
|
|
356
359
|
|
|
357
360
|
async sendStream(peerId, content, opts = {}) {
|
|
358
|
-
const endpoint = this.endpoint(peerId, opts.scope, '
|
|
361
|
+
const endpoint = this.endpoint(peerId, opts.scope, 'stream_messages')
|
|
359
362
|
return this.api(endpoint, {
|
|
360
363
|
method: 'POST',
|
|
361
364
|
body: {
|
|
362
|
-
|
|
363
|
-
|
|
365
|
+
content_type: opts.contentType || 'text',
|
|
366
|
+
content_raw: stringValue(content).slice(0, MAX_MESSAGE_CHARS),
|
|
367
|
+
input_mode: opts.inputMode || 'replace',
|
|
368
|
+
input_state: opts.inputState, // 1=生成中, 10=生成结束
|
|
369
|
+
index: opts.index, // 分片序号,从0递增
|
|
370
|
+
stream_msg_id: opts.streamMsgId, // 第一片由服务端返回,后续片需携带
|
|
364
371
|
msg_id: opts.msgId,
|
|
365
372
|
event_id: opts.eventId,
|
|
366
|
-
|
|
373
|
+
msg_seq: opts.msgSeq,
|
|
367
374
|
},
|
|
368
375
|
})
|
|
369
376
|
}
|
|
@@ -376,22 +383,83 @@ export class QqGateway extends Service {
|
|
|
376
383
|
}
|
|
377
384
|
|
|
378
385
|
async sendTyping(peerId, opts = {}) {
|
|
379
|
-
// QQ Bot API v2
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
const endpoint = this.endpoint(peerId, opts.scope, 'stream-messages')
|
|
386
|
+
// QQ Bot API v2 使用 msg_type: 6 发送输入状态通知
|
|
387
|
+
// 参考:https://bot.q.qq.com/wiki/develop/api-v2/autogen/api/v2_users_user_openid_messages.post.html
|
|
388
|
+
const endpoint = this.endpoint(peerId, opts.scope)
|
|
383
389
|
return this.api(endpoint, {
|
|
384
390
|
method: 'POST',
|
|
385
391
|
body: {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
392
|
+
msg_type: 6,
|
|
393
|
+
input_notify: {
|
|
394
|
+
input_type: 1,
|
|
395
|
+
input_second: Math.min(opts.durationSeconds || 5, 60), // 最长60秒
|
|
396
|
+
},
|
|
389
397
|
msg_id: opts.msgId,
|
|
390
398
|
event_id: opts.eventId,
|
|
391
399
|
},
|
|
392
400
|
})
|
|
393
401
|
}
|
|
394
402
|
|
|
403
|
+
// ---- 自定义菜单(单聊底部菜单,全局生效)----
|
|
404
|
+
// 参考:https://bot.q.qq.com/wiki/develop/api-v2/autogen/api/v2_menu.get.html
|
|
405
|
+
// https://bot.q.qq.com/wiki/develop/api-v2/autogen/api/v2_menu.put.html
|
|
406
|
+
|
|
407
|
+
/** 查询全局自定义菜单 */
|
|
408
|
+
async getMenu() {
|
|
409
|
+
return this.api('/v2/menu', { method: 'GET' })
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* 修改全局自定义菜单(覆盖式)
|
|
414
|
+
* @param {Array} items 菜单项,最多 10 个
|
|
415
|
+
* { name, type: 'switch'|'send_message'|'link'|'menu', sub_menu_items?, send_message?, link?, switch? }
|
|
416
|
+
*/
|
|
417
|
+
async setMenu(items) {
|
|
418
|
+
return this.api('/v2/menu', {
|
|
419
|
+
method: 'PUT',
|
|
420
|
+
body: { menu: { items } },
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ---- 指令面板(c2c/group/channel/dm 场景)----
|
|
425
|
+
// 参考:https://bot.q.qq.com/wiki/develop/api-v2/server-inter/menu-panel/
|
|
426
|
+
// https://bot.q.qq.com/wiki/develop/api-v2/autogen/api/v2_panels.post.html
|
|
427
|
+
|
|
428
|
+
/** 查询指令面板列表(按场景筛选) */
|
|
429
|
+
async listPanels(scope) {
|
|
430
|
+
const query = scope ? `?scope=${encodeURIComponent(scope)}` : ''
|
|
431
|
+
return this.api(`/v2/panels${query}`, { method: 'GET' })
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* 创建指令面板
|
|
436
|
+
* @param {object} body { scope, target_type?, user_openids?, group_openids?, panel: { items, remark?, version? } }
|
|
437
|
+
* @returns {Promise<{panel_id: string}>}
|
|
438
|
+
*/
|
|
439
|
+
async createPanel(body) {
|
|
440
|
+
return this.api('/v2/panels', { method: 'POST', body })
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** 查询指令面板详情 */
|
|
444
|
+
async getPanel(panelId) {
|
|
445
|
+
return this.api(`/v2/panels/${encodeURIComponent(panelId)}`, { method: 'GET' })
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** 修改指令面板 */
|
|
449
|
+
async updatePanel(panelId, body) {
|
|
450
|
+
return this.api(`/v2/panels/${encodeURIComponent(panelId)}`, { method: 'PUT', body })
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** 删除指令面板 */
|
|
454
|
+
async deletePanel(panelId) {
|
|
455
|
+
return this.api(`/v2/panels/${encodeURIComponent(panelId)}`, { method: 'DELETE' })
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** 修改指令面板关联对象(增删指定用户/群) */
|
|
459
|
+
async updatePanelTarget(panelId, body) {
|
|
460
|
+
return this.api(`/v2/panels/${encodeURIComponent(panelId)}/target`, { method: 'PUT', body })
|
|
461
|
+
}
|
|
462
|
+
|
|
395
463
|
setCredentials(values = {}) {
|
|
396
464
|
for (const key of ['appId', 'clientSecret', 'accessToken', 'accessTokenExpiresAt', 'gatewayUrl', 'intents']) {
|
|
397
465
|
if (values[key] !== undefined) this.config[key] = values[key]
|
package/lib/qq/index.js
CHANGED
|
@@ -42,6 +42,16 @@ export class QqService extends Platform {
|
|
|
42
42
|
// 挂到 ctx 供会话节点读取
|
|
43
43
|
try { ctx.qq = this.gateway } catch { /* 挂载失败不致命 */ }
|
|
44
44
|
|
|
45
|
+
// 网关连接成功后自动配置指令面板与自定义菜单(一次性)
|
|
46
|
+
this._panelSetupDone = false
|
|
47
|
+
try {
|
|
48
|
+
ctx.on?.('qq/status', (status) => {
|
|
49
|
+
if (status === 'connected') {
|
|
50
|
+
void this._ensurePanelSetup().catch(() => {})
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
} catch { /* 忽略事件订阅失败 */ }
|
|
54
|
+
|
|
45
55
|
this.node = new QqConversationNode(ctx, {
|
|
46
56
|
allowFrom: Array.isArray(config.allowFrom) ? config.allowFrom : [],
|
|
47
57
|
digestIntervalSec: config.digestIntervalSec,
|
|
@@ -79,8 +89,10 @@ export class QqService extends Platform {
|
|
|
79
89
|
return this.gateway.sendText(peerId, text, opts)
|
|
80
90
|
}
|
|
81
91
|
|
|
82
|
-
async sendTyping(peerId,
|
|
83
|
-
|
|
92
|
+
async sendTyping(peerId, opts = {}) {
|
|
93
|
+
// 兼容旧调用:sendTyping(peerId, durationSeconds)
|
|
94
|
+
const normalized = typeof opts === 'number' ? { durationSeconds: opts } : opts
|
|
95
|
+
return this.gateway.sendTyping(peerId, normalized)
|
|
84
96
|
}
|
|
85
97
|
|
|
86
98
|
async sendMedia(peerId, media, opts = {}) {
|
|
@@ -91,8 +103,8 @@ export class QqService extends Platform {
|
|
|
91
103
|
return this.gateway.sendMarkdown(peerId, markdown, opts)
|
|
92
104
|
}
|
|
93
105
|
|
|
94
|
-
async sendKeyboard(peerId, keyboard, opts = {}) {
|
|
95
|
-
return this.gateway.sendKeyboard(peerId, keyboard, opts)
|
|
106
|
+
async sendKeyboard(peerId, content, keyboard, opts = {}) {
|
|
107
|
+
return this.gateway.sendKeyboard(peerId, content, keyboard, opts)
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
/** 合并展示状态给浏览器 UI。 */
|
|
@@ -189,6 +201,58 @@ export class QqService extends Platform {
|
|
|
189
201
|
await this.persist({ allowFrom: clean })
|
|
190
202
|
}
|
|
191
203
|
|
|
204
|
+
// ---- 指令面板 / 自定义菜单(一次性自动配置)----
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 连接成功后自动创建 c2c + group 指令面板,并配置单聊自定义菜单。
|
|
208
|
+
* 幂等:已存在 remark 匹配的面板时跳过创建;失败仅记日志,不影响连接。
|
|
209
|
+
*/
|
|
210
|
+
async _ensurePanelSetup() {
|
|
211
|
+
if (this._panelSetupDone) return
|
|
212
|
+
this._panelSetupDone = true // 防并发重复执行
|
|
213
|
+
|
|
214
|
+
const gate = this.gateway
|
|
215
|
+
const remark = PANEL_REMARK
|
|
216
|
+
const items = [
|
|
217
|
+
{ name: '/new', desc: '新建对话', type: 'command' },
|
|
218
|
+
{ name: '/list', desc: '查看会话列表', type: 'command' },
|
|
219
|
+
{ name: '/resume', desc: '恢复会话', type: 'command' },
|
|
220
|
+
{ name: '/sessions', desc: '切换会话', type: 'command' },
|
|
221
|
+
{ name: '/help', desc: '命令帮助', type: 'command' },
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
// 指令面板:c2c(单聊)与 group(群聊)各建一个全局面板
|
|
225
|
+
for (const scope of ['c2c', 'group']) {
|
|
226
|
+
try {
|
|
227
|
+
const list = await gate.listPanels(scope)
|
|
228
|
+
const exists = (list?.records || []).some((r) => r?.panel?.remark === remark)
|
|
229
|
+
if (!exists) {
|
|
230
|
+
const res = await gate.createPanel({
|
|
231
|
+
scope,
|
|
232
|
+
target_type: 'all',
|
|
233
|
+
panel: { items, remark, version: 1 },
|
|
234
|
+
})
|
|
235
|
+
this.logger.info('[dsh-bridge qq] created %s command panel: %s', scope, res?.panel_id ?? '(no id)')
|
|
236
|
+
}
|
|
237
|
+
} catch (err) {
|
|
238
|
+
this.logger.warn('[dsh-bridge qq] command panel setup for %s skipped: %s', scope, err?.message ?? err)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 自定义菜单:单聊底部菜单(send_message 类型,点击自动填入命令)
|
|
243
|
+
try {
|
|
244
|
+
const menuItems = [
|
|
245
|
+
{ name: '新建', type: 'send_message', send_message: '/new' },
|
|
246
|
+
{ name: '列表', type: 'send_message', send_message: '/list' },
|
|
247
|
+
{ name: '帮助', type: 'send_message', send_message: '/help' },
|
|
248
|
+
]
|
|
249
|
+
await gate.setMenu(menuItems)
|
|
250
|
+
this.logger.info('[dsh-bridge qq] custom menu configured')
|
|
251
|
+
} catch (err) {
|
|
252
|
+
this.logger.warn('[dsh-bridge qq] custom menu setup skipped: %s', err?.message ?? err)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
192
256
|
/** 更新运行时配置并持久化。 */
|
|
193
257
|
async setConfig({ digestIntervalSec, approvalTimeoutSec, maxMessageChars, sendChunkDelayMs, appId, clientSecret } = {}) {
|
|
194
258
|
if (digestIntervalSec != null) this.node.config.digestIntervalSec = Number(digestIntervalSec)
|
|
@@ -226,3 +290,6 @@ export class QqService extends Platform {
|
|
|
226
290
|
function gatewayMaxChars() {
|
|
227
291
|
return 2000
|
|
228
292
|
}
|
|
293
|
+
|
|
294
|
+
// 指令面板 remark 标识,用于幂等判断(区分本插件创建的面板)
|
|
295
|
+
const PANEL_REMARK = 'dsh-bridge 常用命令'
|
package/lib/qq/node.js
CHANGED
|
@@ -18,8 +18,9 @@ function makePlatform(gateway) {
|
|
|
18
18
|
name: 'QQ',
|
|
19
19
|
get accountId() { return gateway.accountId ?? '' },
|
|
20
20
|
get capabilities() { return gateway.capabilities },
|
|
21
|
+
// sendText / sendTyping 由 QqConversationNode 覆盖,这里仅提供兜底
|
|
21
22
|
sendText: (peer, text) => gateway.sendText(peer, text, {}),
|
|
22
|
-
sendTyping: (
|
|
23
|
+
sendTyping: () => Promise.resolve({ ok: true }),
|
|
23
24
|
sendKeyboard: (peer, content, keyboard) => gateway.sendKeyboard(peer, content, keyboard, {}),
|
|
24
25
|
}
|
|
25
26
|
}
|
|
@@ -44,6 +45,9 @@ export class QqConversationNode extends ConversationBridge {
|
|
|
44
45
|
})
|
|
45
46
|
this.gateway = ctx.qq
|
|
46
47
|
this.lastMessageId = null // 存储最后发送的消息 ID,用于消息引用
|
|
48
|
+
// 当前对话 peer 信息(由 _handleInbound 在每次收到消息时刷新)
|
|
49
|
+
this._lastPeer = null // { peerId, scope: 'c2c'|'group' }
|
|
50
|
+
this._replyMsgId = null // 被动回复用的用户消息 ID(事件 d.id)
|
|
47
51
|
|
|
48
52
|
// 订阅网关入站事件
|
|
49
53
|
this.ctx.on('qq/message', (event) => {
|
|
@@ -54,15 +58,22 @@ export class QqConversationNode extends ConversationBridge {
|
|
|
54
58
|
})
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
// ---- 出站:覆盖 sendText
|
|
61
|
+
// ---- 出站:覆盖 sendText / sendTyping,正确处理 scope 与被动回复 ----
|
|
62
|
+
|
|
63
|
+
/** 解析当前对话 peer 信息;无活动 peer 时返回 null */
|
|
64
|
+
_currentPeer() {
|
|
65
|
+
return this._lastPeer
|
|
66
|
+
}
|
|
58
67
|
|
|
59
68
|
async sendText(text) {
|
|
60
|
-
const
|
|
61
|
-
if (!
|
|
62
|
-
|
|
69
|
+
const peerInfo = this._currentPeer()
|
|
70
|
+
if (!peerInfo) return
|
|
71
|
+
const { peerId, scope } = peerInfo
|
|
72
|
+
const replyMsgId = this._replyMsgId || undefined
|
|
73
|
+
|
|
63
74
|
// 检测是否是提示用户开始新会话的消息
|
|
64
75
|
const isPromptMessage = text.includes('没有活动会话') || text.includes('恢复会话失败')
|
|
65
|
-
|
|
76
|
+
|
|
66
77
|
if (isPromptMessage) {
|
|
67
78
|
// 发送带按钮的消息,方便用户快速操作
|
|
68
79
|
const keyboard = {
|
|
@@ -80,59 +91,73 @@ export class QqConversationNode extends ConversationBridge {
|
|
|
80
91
|
},
|
|
81
92
|
],
|
|
82
93
|
}
|
|
83
|
-
|
|
84
|
-
// 保存消息 ID
|
|
85
|
-
if (result?.data?.message_id) {
|
|
86
|
-
this.lastMessageId = result.data.message_id
|
|
87
|
-
}
|
|
88
|
-
return result
|
|
94
|
+
return this.gateway.sendKeyboard(peerId, text, keyboard, { scope, msgId: replyMsgId })
|
|
89
95
|
}
|
|
90
|
-
|
|
96
|
+
|
|
91
97
|
// 其他消息使用流式发送
|
|
92
98
|
const content = String(text || '').trim()
|
|
93
99
|
if (content.length === 0) return { success: true }
|
|
94
|
-
|
|
95
|
-
const STREAM_CHUNK_SIZE =
|
|
96
|
-
|
|
100
|
+
|
|
101
|
+
const STREAM_CHUNK_SIZE = 500
|
|
102
|
+
|
|
97
103
|
// 如果消息较短,直接发送普通消息
|
|
98
104
|
if (content.length <= STREAM_CHUNK_SIZE) {
|
|
99
|
-
const result = await this.gateway.sendText(
|
|
100
|
-
|
|
101
|
-
if (result?.data?.message_id) {
|
|
102
|
-
this.lastMessageId = result.data.message_id
|
|
103
|
-
}
|
|
105
|
+
const result = await this.gateway.sendText(peerId, content, { scope, msgId: replyMsgId })
|
|
106
|
+
if (result?.id) this.lastMessageId = result.id
|
|
104
107
|
return result
|
|
105
108
|
}
|
|
106
|
-
|
|
107
|
-
//
|
|
109
|
+
|
|
110
|
+
// 流式发送:把内容切分成多段(append 模式,服务端拼接为同一条消息)
|
|
108
111
|
const chunks = []
|
|
109
112
|
for (let i = 0; i < content.length; i += STREAM_CHUNK_SIZE) {
|
|
110
113
|
chunks.push(content.slice(i, i + STREAM_CHUNK_SIZE))
|
|
111
114
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
let firstMsgId = null
|
|
115
|
+
|
|
116
|
+
let streamMsgId = null
|
|
115
117
|
for (let i = 0; i < chunks.length; i++) {
|
|
116
118
|
const chunk = chunks[i]
|
|
117
119
|
const isLast = i === chunks.length - 1
|
|
118
|
-
const result = await this.gateway.sendStream(
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
const result = await this.gateway.sendStream(peerId, chunk, {
|
|
121
|
+
scope,
|
|
122
|
+
msgId: i === 0 ? replyMsgId : undefined, // 首片带被动回复 msg_id
|
|
123
|
+
streamMsgId, // 后续片携带服务端返回的 stream_msg_id
|
|
124
|
+
index: i, // 分片序号从 0 递增
|
|
125
|
+
inputState: isLast ? 10 : 1, // 1=生成中, 10=生成结束
|
|
126
|
+
inputMode: 'append', // 追加模式:服务端拼接到同一条消息
|
|
121
127
|
})
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
128
|
+
|
|
129
|
+
// 首片返回 stream_msg_id,后续片需携带
|
|
130
|
+
if (i === 0 && result?.id) {
|
|
131
|
+
streamMsgId = result.id
|
|
132
|
+
this.lastMessageId = result.id // 保存消息 ID 用于消息引用
|
|
125
133
|
}
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
|
|
135
|
+
if (result?.code !== undefined && result.code !== 0) {
|
|
136
|
+
return { success: false, error: result.message || `QQ API error ${result.code}` }
|
|
128
137
|
}
|
|
138
|
+
|
|
129
139
|
// 流式发送间隔稍短,避免刷屏
|
|
130
|
-
|
|
140
|
+
if (!isLast) {
|
|
141
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
142
|
+
}
|
|
131
143
|
}
|
|
132
|
-
|
|
144
|
+
|
|
133
145
|
return { success: true }
|
|
134
146
|
}
|
|
135
147
|
|
|
148
|
+
/** 发送"正在输入"状态(QQ 通过 msg_type=6 + input_notify 显示 N 秒) */
|
|
149
|
+
async sendTyping(state) {
|
|
150
|
+
const peerInfo = this._currentPeer()
|
|
151
|
+
if (!peerInfo) return
|
|
152
|
+
// state=2(停止)时无需显式结束——input_second 到期自动消失
|
|
153
|
+
if (Number(state) === 2) return { ok: true }
|
|
154
|
+
return this.gateway.sendTyping(peerInfo.peerId, {
|
|
155
|
+
scope: peerInfo.scope,
|
|
156
|
+
durationSeconds: 8,
|
|
157
|
+
msgId: this._replyMsgId || undefined,
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
136
161
|
// ---- 入站 ----
|
|
137
162
|
|
|
138
163
|
async _handleInbound(event) {
|
|
@@ -154,6 +179,13 @@ export class QqConversationNode extends ConversationBridge {
|
|
|
154
179
|
// 群聊:仅在群聊 @ 机器人事件中处理(GROUP_AT_MESSAGE_CREATE 已由网关归一化)
|
|
155
180
|
const isGroup = event.scope === 'group' || event.scope === 'guild'
|
|
156
181
|
|
|
182
|
+
// 记录当前 peer 信息与被动回复消息 ID(事件 d.id),供出站使用
|
|
183
|
+
const peerId = isGroup ? event.groupId || event.peerId : event.peerId || event.senderId
|
|
184
|
+
if (peerId) {
|
|
185
|
+
this._lastPeer = { peerId: String(peerId), scope: isGroup ? 'group' : 'c2c' }
|
|
186
|
+
if (event.id) this._replyMsgId = String(event.id)
|
|
187
|
+
}
|
|
188
|
+
|
|
157
189
|
// 检测消息引用(文本交互):如果用户回复了机器人的消息,且当前没有活动会话或消息以 /new 开头,
|
|
158
190
|
// 则自动创建新会话并发送消息
|
|
159
191
|
const messageReference = event.messageReference
|
|
@@ -181,6 +213,14 @@ export class QqConversationNode extends ConversationBridge {
|
|
|
181
213
|
return
|
|
182
214
|
}
|
|
183
215
|
|
|
216
|
+
// 记录 peer 信息,供命令回复使用
|
|
217
|
+
const isGroup = event.scope === 'group'
|
|
218
|
+
const peerId = isGroup ? event.groupId || event.peerId : event.peerId || sender
|
|
219
|
+
if (peerId) {
|
|
220
|
+
this._lastPeer = { peerId: String(peerId), scope: isGroup ? 'group' : 'c2c' }
|
|
221
|
+
this._replyMsgId = null
|
|
222
|
+
}
|
|
223
|
+
|
|
184
224
|
const data = event.data || {}
|
|
185
225
|
const resolved = data.resolved || {}
|
|
186
226
|
const buttonId = resolved.button_id || ''
|
package/package.json
CHANGED