@kirigaya/openclaw-onebot 1.0.9 → 1.1.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 +52 -20
- package/dist/config.d.ts +2 -0
- package/dist/config.js +5 -0
- package/dist/connection.js +8 -3
- package/dist/handlers/process-inbound.js +94 -23
- package/dist/types.d.ts +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,29 +15,29 @@
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
openclaw plugins install @kirigaya/openclaw-onebot
|
|
22
|
+
openclaw onebot setup
|
|
23
|
+
```
|
|
24
|
+
|
|
18
25
|
## 教程
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
[让 QQ 接入 openclaw!让你的助手掌管千人大群](https://kirigaya.cn/blog/article?seq=368)
|
|
28
|
+
|
|
29
|
+
<img src="./figure/arch.png" />
|
|
30
|
+
|
|
21
31
|
|
|
22
32
|
## 功能
|
|
23
33
|
|
|
24
34
|
- ✅ 私聊:所有消息 AI 都会回复
|
|
25
|
-
- ✅
|
|
35
|
+
- ✅ 触发:支持 @触发 和基于关键字的触发
|
|
26
36
|
- ✅ 自动获取上下文
|
|
27
|
-
- ✅
|
|
28
|
-
- ✅
|
|
29
|
-
- ✅
|
|
30
|
-
- ✅
|
|
31
|
-
- ✅ 支持文件,图像读取/上传
|
|
32
|
-
- ✅ 支持白名单系统
|
|
33
|
-
- ✅ 通过 `openclaw message send` CLI 发送(无 Agent 工具,降低 token 消耗)
|
|
34
|
-
|
|
35
|
-
## 安装
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
openclaw plugins install @kirigaya/openclaw-onebot
|
|
39
|
-
openclaw onebot setup
|
|
40
|
-
```
|
|
37
|
+
- ✅ 自定义新成员入群欢迎触发器
|
|
38
|
+
- ✅ 自动合并转发长消息:超过阈值可渲染为图片发送或者合并发送
|
|
39
|
+
- ✅ 支持文件,图像读取/发送
|
|
40
|
+
- ✅ 支持黑白名单系统
|
|
41
41
|
|
|
42
42
|
## 安装 onebot 服务端
|
|
43
43
|
|
|
@@ -48,9 +48,11 @@ openclaw onebot setup
|
|
|
48
48
|
|
|
49
49
|
| 类型 | 说明 |
|
|
50
50
|
|------|------|
|
|
51
|
-
| `forward-websocket` | 插件主动连接 OneBot(go-cqhttp、Lagrange.Core 正向 WS) |
|
|
51
|
+
| `forward-websocket` | 插件主动连接 OneBot(go-cqhttp、Lagrange.Core 正向 WS/WSS) |
|
|
52
52
|
| `backward-websocket` | 插件作为服务端,OneBot 连接过来 |
|
|
53
53
|
|
|
54
|
+
> 💡 **提示**:支持 `ws://` 和 `wss://`(WebSocket Secure)协议,可填写完整 URL 如 `wss://ws-napcatqq.example.com`
|
|
55
|
+
|
|
54
56
|
### 环境变量
|
|
55
57
|
|
|
56
58
|
可替代配置文件,适用于 Lagrange 等:
|
|
@@ -66,7 +68,31 @@ openclaw onebot setup
|
|
|
66
68
|
|
|
67
69
|
1. 安装并配置
|
|
68
70
|
2. 重启 Gateway:`openclaw gateway restart`
|
|
69
|
-
3. 在 QQ 私聊或群聊中发消息(群聊需 @
|
|
71
|
+
3. 在 QQ 私聊或群聊中发消息(群聊需 @ 机器人,或配置关键字触发)
|
|
72
|
+
|
|
73
|
+
## 关键字触发回复
|
|
74
|
+
|
|
75
|
+
除了 @ 机器人外,还可以配置关键字检测,当群消息中包含指定关键字时自动触发回复(无需 @)。
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"channels": {
|
|
80
|
+
"onebot": {
|
|
81
|
+
"keywordTriggers": {
|
|
82
|
+
"enabled": true,
|
|
83
|
+
"keywords": ["AI", "助手", "帮我问"],
|
|
84
|
+
"caseSensitive": false
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
| 配置项 | 说明 |
|
|
92
|
+
|--------|------|
|
|
93
|
+
| `enabled` | 是否启用关键字触发 |
|
|
94
|
+
| `keywords` | 关键字列表,包含任一关键字即触发 |
|
|
95
|
+
| `caseSensitive` | 是否区分大小写 |
|
|
70
96
|
|
|
71
97
|
## 长消息处理与 OG 图片渲染
|
|
72
98
|
|
|
@@ -190,7 +216,7 @@ openclaw message send --channel onebot --target group:987654321 --media "file://
|
|
|
190
216
|
}
|
|
191
217
|
```
|
|
192
218
|
|
|
193
|
-
|
|
219
|
+
## 黑名单
|
|
194
220
|
|
|
195
221
|
在群里有时候有些人需要被屏蔽,不管他怎么 @ 还是怎么,都屏蔽他的消息不触发。
|
|
196
222
|
|
|
@@ -204,7 +230,7 @@ openclaw message send --channel onebot --target group:987654321 --media "file://
|
|
|
204
230
|
}
|
|
205
231
|
```
|
|
206
232
|
|
|
207
|
-
|
|
233
|
+
**注意**:白名单优先级高于黑名单。如果同时设置了白名单和黑名单,只有白名单内的用户才能触发,且黑名单内的白名单用户也会被屏蔽。
|
|
208
234
|
|
|
209
235
|
## 新人入群触发器
|
|
210
236
|
|
|
@@ -264,6 +290,12 @@ npm run test:render-og-image -- "C:/path/to/your-theme.css"
|
|
|
264
290
|
- [Lagrange.Core](https://github.com/LSTM-Kirigaya/Lagrange.Core)
|
|
265
291
|
- [NapCat](https://github.com/NapNeko/NapCatQQ)
|
|
266
292
|
|
|
293
|
+
## 联系
|
|
294
|
+
|
|
295
|
+
zhelonghuang@qq.com
|
|
296
|
+
|
|
297
|
+
要是我不回你,可以选择进我的QQ群。782833642
|
|
298
|
+
|
|
267
299
|
## License
|
|
268
300
|
|
|
269
301
|
MIT © [LSTM-Kirigaya](https://github.com/LSTM-Kirigaya)
|
package/dist/config.d.ts
CHANGED
|
@@ -32,3 +32,5 @@ export declare function getTriggerKeywords(cfg: any): string[];
|
|
|
32
32
|
* - "contains": 消息包含关键词即可
|
|
33
33
|
*/
|
|
34
34
|
export declare function getTriggerMode(cfg: any): "prefix" | "contains";
|
|
35
|
+
/** 是否在用户不在白名单时回复“权限不足”,默认 true */
|
|
36
|
+
export declare function getReplyWhenWhitelistDenied(cfg: any): boolean;
|
package/dist/config.js
CHANGED
|
@@ -131,3 +131,8 @@ export function getTriggerMode(cfg) {
|
|
|
131
131
|
return "contains";
|
|
132
132
|
return "prefix"; // 默认为前缀匹配
|
|
133
133
|
}
|
|
134
|
+
/** 是否在用户不在白名单时回复“权限不足”,默认 true */
|
|
135
|
+
export function getReplyWhenWhitelistDenied(cfg) {
|
|
136
|
+
const v = cfg?.channels?.onebot?.replyWhenWhitelistDenied;
|
|
137
|
+
return v === undefined ? true : Boolean(v);
|
|
138
|
+
}
|
package/dist/connection.js
CHANGED
|
@@ -177,18 +177,22 @@ function getLogger() {
|
|
|
177
177
|
function sendOneBotAction(wsocket, action, params, log = getLogger()) {
|
|
178
178
|
const echo = nextEcho();
|
|
179
179
|
const payload = { action, params, echo };
|
|
180
|
+
// Log the initiation of the action with basic target info
|
|
181
|
+
const targetInfo = params.group_id ? `group=${params.group_id}` : (params.user_id ? `user=${params.user_id}` : "");
|
|
182
|
+
log.info?.(`[onebot-trace] sendOneBotAction action=${action} echo=${echo} ${targetInfo}`);
|
|
180
183
|
return new Promise((resolve, reject) => {
|
|
181
184
|
const timeout = setTimeout(() => {
|
|
182
185
|
pendingEcho.delete(echo);
|
|
183
|
-
log.warn?.(`[onebot] sendOneBotAction ${action} timeout`);
|
|
184
|
-
reject(new Error(`OneBot action ${action} timeout`));
|
|
186
|
+
log.warn?.(`[onebot-trace] sendOneBotAction ${action} timeout for echo=${echo}, ws.readyState=${wsocket.readyState}`);
|
|
187
|
+
reject(new Error(`OneBot action ${action} timeout (echo=${echo}, ws.readyState=${wsocket.readyState})`));
|
|
185
188
|
}, 15000);
|
|
186
189
|
pendingEcho.set(echo, {
|
|
187
190
|
resolve: (v) => {
|
|
188
191
|
clearTimeout(timeout);
|
|
189
192
|
pendingEcho.delete(echo);
|
|
193
|
+
log.info?.(`[onebot-trace] echo ${echo} resolved with retcode=${v?.retcode} message_id=${v?.data?.message_id ?? "unknown"}`);
|
|
190
194
|
if (v?.retcode !== 0)
|
|
191
|
-
log.warn?.(`[onebot] sendOneBotAction ${action} retcode=${v?.retcode} msg=${v?.msg ?? ""}`);
|
|
195
|
+
log.warn?.(`[onebot-trace] sendOneBotAction ${action} retcode=${v?.retcode} msg=${v?.msg ?? ""}`);
|
|
192
196
|
resolve(v);
|
|
193
197
|
},
|
|
194
198
|
});
|
|
@@ -196,6 +200,7 @@ function sendOneBotAction(wsocket, action, params, log = getLogger()) {
|
|
|
196
200
|
if (err) {
|
|
197
201
|
pendingEcho.delete(echo);
|
|
198
202
|
clearTimeout(timeout);
|
|
203
|
+
log.warn?.(`[onebot-trace] sendOneBotAction ${action} wsocket.send error for echo=${echo}: ${err.message}`);
|
|
199
204
|
reject(err);
|
|
200
205
|
}
|
|
201
206
|
});
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { getOneBotConfig } from "../config.js";
|
|
5
5
|
import { getRawText, getTextFromSegments, getReplyMessageId, getTextFromMessageContent, isMentioned, } from "../message.js";
|
|
6
|
-
import { getRenderMarkdownToPlain, getCollapseDoubleNewlines, getWhitelistUserIds, getBlacklistUserIds, getOgImageRenderTheme, getNormalModeFlushIntervalMs, getNormalModeFlushChars, getTriggerKeywords, getTriggerMode, } from "../config.js";
|
|
6
|
+
import { getRenderMarkdownToPlain, getCollapseDoubleNewlines, getWhitelistUserIds, getBlacklistUserIds, getOgImageRenderTheme, getNormalModeFlushIntervalMs, getNormalModeFlushChars, getTriggerKeywords, getTriggerMode, getReplyWhenWhitelistDenied, } from "../config.js";
|
|
7
7
|
import { markdownToPlain, collapseDoubleNewlines } from "../markdown.js";
|
|
8
8
|
import { markdownToImage } from "../og-image.js";
|
|
9
|
-
import { sendPrivateMsg, sendGroupMsg, sendPrivateImage, sendGroupImage, sendGroupForwardMsg, sendPrivateForwardMsg, setMsgEmojiLike, getMsg, } from "../connection.js";
|
|
9
|
+
import { sendPrivateMsg, sendGroupMsg, sendPrivateImage, sendGroupImage, sendGroupForwardMsg, sendPrivateForwardMsg, setMsgEmojiLike, getMsg, getStrangerInfo, getGroupMemberInfo, } from "../connection.js";
|
|
10
10
|
import { setActiveReplyTarget, clearActiveReplyTarget, setActiveReplySessionId, setForwardSuppressDelivery, setActiveReplySelfId } from "../reply-context.js";
|
|
11
11
|
import { loadPluginSdk, getSdk } from "../sdk.js";
|
|
12
12
|
import { handleGroupIncrease } from "./group-increase.js";
|
|
@@ -62,6 +62,40 @@ export function startForwardCleanupTimer() {
|
|
|
62
62
|
return;
|
|
63
63
|
forwardCleanupTimer = setInterval(cleanupForwardPendingSessions, FORWARD_CLEANUP_INTERVAL_MS);
|
|
64
64
|
}
|
|
65
|
+
const nicknameCache = new Map();
|
|
66
|
+
const NICKNAME_CACHE_TTL_MS = 5 * 60 * 1000; // 5 分钟
|
|
67
|
+
/**
|
|
68
|
+
* 解析发送者展示名称。
|
|
69
|
+
* 优先使用消息体自带的群名片/昵称;若缺失,则通过 OneBot API 查询并缓存。
|
|
70
|
+
* 最终格式为 nickname(qq: userId),确保 AI 上下文中同时包含昵称与 ID。
|
|
71
|
+
*/
|
|
72
|
+
async function resolveSenderNickname(userId, groupId, isGroup, senderFromMsg) {
|
|
73
|
+
const card = senderFromMsg?.card?.trim();
|
|
74
|
+
const nickname = senderFromMsg?.nickname?.trim();
|
|
75
|
+
const base = card || nickname || "";
|
|
76
|
+
if (base) {
|
|
77
|
+
return `${base}(qq: ${userId})`;
|
|
78
|
+
}
|
|
79
|
+
const cacheKey = isGroup && groupId ? `group:${groupId}:${userId}` : `user:${userId}`;
|
|
80
|
+
const cached = nicknameCache.get(cacheKey);
|
|
81
|
+
if (cached && Date.now() - cached.ts < NICKNAME_CACHE_TTL_MS) {
|
|
82
|
+
return `${cached.nickname}(qq: ${userId})`;
|
|
83
|
+
}
|
|
84
|
+
let resolved = "";
|
|
85
|
+
if (isGroup && groupId) {
|
|
86
|
+
const info = await getGroupMemberInfo(groupId, userId);
|
|
87
|
+
resolved = info?.card?.trim() || info?.nickname?.trim() || "";
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const info = await getStrangerInfo(userId);
|
|
91
|
+
resolved = info?.nickname?.trim() || "";
|
|
92
|
+
}
|
|
93
|
+
if (resolved) {
|
|
94
|
+
nicknameCache.set(cacheKey, { nickname: resolved, ts: Date.now() });
|
|
95
|
+
return `${resolved}(qq: ${userId})`;
|
|
96
|
+
}
|
|
97
|
+
return String(userId);
|
|
98
|
+
}
|
|
65
99
|
export async function processInboundMessage(api, msg) {
|
|
66
100
|
await loadPluginSdk();
|
|
67
101
|
const { buildPendingHistoryContextFromMap, recordPendingHistoryEntry, clearHistoryEntriesIfEnabled } = getSdk();
|
|
@@ -150,15 +184,17 @@ export async function processInboundMessage(api, msg) {
|
|
|
150
184
|
// 白名单检查
|
|
151
185
|
const whitelist = getWhitelistUserIds(cfg);
|
|
152
186
|
if (whitelist.length > 0 && !whitelist.includes(Number(userId))) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
187
|
+
if (getReplyWhenWhitelistDenied(cfg)) {
|
|
188
|
+
const denyMsg = "权限不足,请向管理员申请权限";
|
|
189
|
+
const getConfig = () => getOneBotConfig(api);
|
|
190
|
+
try {
|
|
191
|
+
if (msg.message_type === "group" && msg.group_id)
|
|
192
|
+
await sendGroupMsg(msg.group_id, denyMsg, getConfig);
|
|
193
|
+
else
|
|
194
|
+
await sendPrivateMsg(userId, denyMsg, getConfig);
|
|
195
|
+
}
|
|
196
|
+
catch (_) { }
|
|
160
197
|
}
|
|
161
|
-
catch (_) { }
|
|
162
198
|
api.logger?.info?.(`[onebot] user ${userId} not in whitelist, denied`);
|
|
163
199
|
return;
|
|
164
200
|
}
|
|
@@ -185,7 +221,7 @@ export async function processInboundMessage(api, msg) {
|
|
|
185
221
|
}) ?? "";
|
|
186
222
|
const envelopeOptions = runtime.channel.reply?.resolveEnvelopeFormatOptions?.(cfg) ?? {};
|
|
187
223
|
const chatType = isGroup ? "group" : "direct";
|
|
188
|
-
const fromLabel =
|
|
224
|
+
const fromLabel = await resolveSenderNickname(Number(userId), groupId, isGroup, msg.sender);
|
|
189
225
|
// 添加日志:打印插件接收到的原始消息内容
|
|
190
226
|
api.logger?.info?.(`[onebot] received message from user ${userId}: "${messageText}"`);
|
|
191
227
|
const formattedBody = runtime.channel.reply?.formatInboundEnvelope?.({
|
|
@@ -289,10 +325,16 @@ export async function processInboundMessage(api, msg) {
|
|
|
289
325
|
api.logger?.warn?.("[onebot] setMsgEmojiLike failed (maybe OneBot doesn't support it)");
|
|
290
326
|
}
|
|
291
327
|
}
|
|
292
|
-
|
|
328
|
+
const traceId = `trace-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
329
|
+
const traceLog = {
|
|
330
|
+
info: (m) => api.logger?.info?.(`[${traceId}] ${m}`),
|
|
331
|
+
warn: (m) => api.logger?.warn?.(`[${traceId}] ${m}`),
|
|
332
|
+
error: (m) => api.logger?.error?.(`[${traceId}] ${m}`)
|
|
333
|
+
};
|
|
334
|
+
traceLog.info(`dispatching message for session ${sessionId}`);
|
|
293
335
|
const longMessageMode = onebotCfg.longMessageMode ?? "normal";
|
|
294
336
|
const longMessageThreshold = onebotCfg.longMessageThreshold ?? 300;
|
|
295
|
-
|
|
337
|
+
traceLog.info(`longMessageMode=${longMessageMode}, threshold=${longMessageThreshold}`);
|
|
296
338
|
const normalModeFlushIntervalMs = getNormalModeFlushIntervalMs(cfg);
|
|
297
339
|
const normalModeFlushChars = getNormalModeFlushChars(cfg);
|
|
298
340
|
const replySessionId = `onebot-reply-${Date.now()}-${sessionId}`;
|
|
@@ -307,11 +349,13 @@ export async function processInboundMessage(api, msg) {
|
|
|
307
349
|
let normalModeBufferedRawText = "";
|
|
308
350
|
let normalModeFlushTimer = null;
|
|
309
351
|
let normalModeFlushChain = Promise.resolve();
|
|
352
|
+
let receivedFinal = false;
|
|
310
353
|
const getConfig = () => getOneBotConfig(api);
|
|
311
354
|
const onReplySessionEnd = onebotCfg.onReplySessionEnd;
|
|
312
355
|
const normalModePunctuationFlushMinChars = 24;
|
|
313
|
-
const clearNormalModeFlushTimer = () => {
|
|
356
|
+
const clearNormalModeFlushTimer = (reason = "unknown") => {
|
|
314
357
|
if (normalModeFlushTimer) {
|
|
358
|
+
traceLog.info(`clearNormalModeFlushTimer: clearing timer, reason=${reason}`);
|
|
315
359
|
clearTimeout(normalModeFlushTimer);
|
|
316
360
|
normalModeFlushTimer = null;
|
|
317
361
|
}
|
|
@@ -321,7 +365,7 @@ export async function processInboundMessage(api, msg) {
|
|
|
321
365
|
normalModeFlushChain = normalModeFlushChain
|
|
322
366
|
.then(action)
|
|
323
367
|
.catch((e) => {
|
|
324
|
-
|
|
368
|
+
traceLog.error(`normal-mode flush failed: ${e?.message ?? e}`);
|
|
325
369
|
});
|
|
326
370
|
return normalModeFlushChain;
|
|
327
371
|
};
|
|
@@ -340,11 +384,12 @@ export async function processInboundMessage(api, msg) {
|
|
|
340
384
|
}
|
|
341
385
|
};
|
|
342
386
|
const flushBufferedNormalModeText = async (effectiveIsGroup, effectiveGroupId, uid) => {
|
|
343
|
-
clearNormalModeFlushTimer();
|
|
387
|
+
clearNormalModeFlushTimer("flushBufferedNormalModeText");
|
|
344
388
|
if (!hasBufferedNormalModeText())
|
|
345
389
|
return;
|
|
346
390
|
const text = normalModeBufferedText;
|
|
347
391
|
const rawText = normalModeBufferedRawText;
|
|
392
|
+
traceLog.info(`flushBufferedNormalModeText: textLen=${text.length}, textPreview="${text.slice(0, 30).replace(/\n/g, '\\n')}"`);
|
|
348
393
|
normalModeBufferedText = "";
|
|
349
394
|
normalModeBufferedRawText = "";
|
|
350
395
|
await doSendChunk(effectiveIsGroup, effectiveGroupId, uid, text, undefined);
|
|
@@ -357,7 +402,9 @@ export async function processInboundMessage(api, msg) {
|
|
|
357
402
|
const scheduleNormalModeFlush = (effectiveIsGroup, effectiveGroupId, uid) => {
|
|
358
403
|
if (normalModeFlushTimer)
|
|
359
404
|
return;
|
|
405
|
+
traceLog.info(`scheduleNormalModeFlush: scheduled (interval=${normalModeFlushIntervalMs}ms)`);
|
|
360
406
|
normalModeFlushTimer = setTimeout(() => {
|
|
407
|
+
traceLog.info(`scheduleNormalModeFlush: timer triggered`);
|
|
361
408
|
void queueNormalModeFlush(() => flushBufferedNormalModeText(effectiveIsGroup, effectiveGroupId, uid));
|
|
362
409
|
}, normalModeFlushIntervalMs);
|
|
363
410
|
};
|
|
@@ -394,6 +441,10 @@ export async function processInboundMessage(api, msg) {
|
|
|
394
441
|
const replyText = typeof p === "string" ? p : (p?.text ?? p?.body ?? "");
|
|
395
442
|
const mediaUrl = typeof p === "string" ? undefined : (p?.mediaUrl ?? p?.mediaUrls?.[0]);
|
|
396
443
|
const trimmed = (replyText || "").trim();
|
|
444
|
+
traceLog.info(`deliver entry: kind=${info.kind}, textLen=${replyText.length}, mediaUrl=${!!mediaUrl}, deliveredChunks=${deliveredChunks.length}`);
|
|
445
|
+
if (info.kind === "final") {
|
|
446
|
+
receivedFinal = true;
|
|
447
|
+
}
|
|
397
448
|
if ((!trimmed || trimmed === "NO_REPLY" || trimmed.endsWith("NO_REPLY")) && !mediaUrl)
|
|
398
449
|
return;
|
|
399
450
|
const { userId: uid, groupId: gid, isGroup: ig } = ctxPayload._onebot || {};
|
|
@@ -460,7 +511,7 @@ export async function processInboundMessage(api, msg) {
|
|
|
460
511
|
const isLong = totalLen > longMessageThreshold;
|
|
461
512
|
const isIncrementalLong = incrementalLen > longMessageThreshold;
|
|
462
513
|
const isIncremental = lastSentCount > 0;
|
|
463
|
-
|
|
514
|
+
traceLog.info(`final check: totalLen=${totalLen}, threshold=${longMessageThreshold}, isLong=${isLong}, isIncremental=${isIncremental}, deliveredChunks=${deliveredChunks.length}`);
|
|
464
515
|
if (isIncremental) {
|
|
465
516
|
setForwardSuppressDelivery(false);
|
|
466
517
|
// normal 模式下增量 chunk 已在 deliver 中实时发出;这里不能在 final 再补发一次。
|
|
@@ -544,9 +595,9 @@ export async function processInboundMessage(api, msg) {
|
|
|
544
595
|
}
|
|
545
596
|
}
|
|
546
597
|
else if (!shouldSendNow && (longMessageMode === "og_image" || longMessageMode === "forward")) {
|
|
547
|
-
|
|
598
|
+
traceLog.info(`checking og_image: isLong=${isLong}, mode=${longMessageMode}`);
|
|
548
599
|
if (isLong && longMessageMode === "og_image") {
|
|
549
|
-
|
|
600
|
+
traceLog.info(`triggering og_image for ${totalLen} chars`);
|
|
550
601
|
const fullRaw = deliveredChunks.map((c) => c.rawText ?? c.text ?? "").join("\n\n");
|
|
551
602
|
if (fullRaw.trim()) {
|
|
552
603
|
try {
|
|
@@ -645,20 +696,24 @@ export async function processInboundMessage(api, msg) {
|
|
|
645
696
|
}
|
|
646
697
|
}
|
|
647
698
|
catch (e) {
|
|
648
|
-
|
|
699
|
+
traceLog.error(`deliver failed: ${e?.message}`);
|
|
649
700
|
}
|
|
650
701
|
},
|
|
651
702
|
onError: async (err, info) => {
|
|
652
|
-
|
|
703
|
+
traceLog.error(`${info?.kind} reply failed: ${err}`);
|
|
653
704
|
await clearEmojiReaction();
|
|
654
705
|
},
|
|
655
706
|
},
|
|
656
707
|
replyOptions: { disableBlockStreaming: longMessageMode !== "normal" },
|
|
657
708
|
});
|
|
709
|
+
traceLog.info(`dispatchReplyWithBufferedBlockDispatcher returned successfully.`);
|
|
658
710
|
}
|
|
659
711
|
catch (err) {
|
|
660
712
|
await clearEmojiReaction();
|
|
661
|
-
|
|
713
|
+
// 异常时清空缓冲,避免 finally 补发半截正文后再发错误消息
|
|
714
|
+
traceLog.error(`dispatch catch block: err=${err?.message}, receivedFinal=${receivedFinal}, chunkIndex=${chunkIndex}`);
|
|
715
|
+
normalModeBufferedText = "";
|
|
716
|
+
normalModeBufferedRawText = "";
|
|
662
717
|
try {
|
|
663
718
|
const { userId: uid, groupId: gid, isGroup: ig } = ctxPayload._onebot || {};
|
|
664
719
|
if (ig && gid)
|
|
@@ -669,7 +724,23 @@ export async function processInboundMessage(api, msg) {
|
|
|
669
724
|
catch (_) { }
|
|
670
725
|
}
|
|
671
726
|
finally {
|
|
672
|
-
|
|
727
|
+
traceLog.info(`dispatch finally block: receivedFinal=${receivedFinal}, hasBuffered=${hasBufferedNormalModeText()}, bufferLen=${normalModeBufferedText.length}, hasTimer=${!!normalModeFlushTimer}, chunks=${deliveredChunks.length}`);
|
|
728
|
+
// 补发缓冲池中残留的文本(引擎未发送 final 帧时会走到这里)
|
|
729
|
+
if (hasBufferedNormalModeText()) {
|
|
730
|
+
try {
|
|
731
|
+
const { userId: uid, groupId: gid, isGroup: ig } = ctxPayload._onebot || {};
|
|
732
|
+
const sessionKey = String(ctxPayload.SessionKey ?? sessionId);
|
|
733
|
+
const groupMatch = sessionKey.match(/^onebot:group:(\d+)$/i);
|
|
734
|
+
const effectiveIsGroup = groupMatch != null || Boolean(ig);
|
|
735
|
+
const effectiveGroupId = (groupMatch ? parseInt(groupMatch[1], 10) : undefined) ?? gid;
|
|
736
|
+
queueNormalModeFlush(() => flushBufferedNormalModeText(effectiveIsGroup, effectiveGroupId, uid));
|
|
737
|
+
await normalModeFlushChain;
|
|
738
|
+
}
|
|
739
|
+
catch (e) {
|
|
740
|
+
traceLog.error(`finally flush failed: ${e?.message ?? e}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
clearNormalModeFlushTimer("finally");
|
|
673
744
|
setForwardSuppressDelivery(false);
|
|
674
745
|
setActiveReplySelfId(null);
|
|
675
746
|
lastSentChunkCountBySession.delete(replySessionId);
|
package/dist/types.d.ts
CHANGED
|
@@ -14,7 +14,11 @@ export interface OneBotMessage {
|
|
|
14
14
|
raw_message?: string;
|
|
15
15
|
self_id?: number;
|
|
16
16
|
time?: number;
|
|
17
|
-
|
|
17
|
+
sender?: {
|
|
18
|
+
user_id?: number;
|
|
19
|
+
nickname?: string;
|
|
20
|
+
card?: string;
|
|
21
|
+
};
|
|
18
22
|
[key: string]: unknown;
|
|
19
23
|
}
|
|
20
24
|
export interface OneBotAccountConfig {
|