@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 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
- 📖 **完整安装与配置教程**:[让 QQ 接入 openclaw!让你的助手掌管千人大群](https://kirigaya.cn/blog/article?seq=368)
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
- - ✅ normal 模式准流式回复:按短时间窗口聚合后增量发送,避免等到最后一次性吐出
30
- - ✅ **长消息生成图片**:超过阈值可将 Markdown 渲染为图片发送(可选主题:default / dust / custom 自定义 CSS)
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
+ }
@@ -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
- const denyMsg = "权限不足,请向管理员申请权限";
154
- const getConfig = () => getOneBotConfig(api);
155
- try {
156
- if (msg.message_type === "group" && msg.group_id)
157
- await sendGroupMsg(msg.group_id, denyMsg, getConfig);
158
- else
159
- await sendPrivateMsg(userId, denyMsg, getConfig);
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 = String(userId);
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
- api.logger?.info?.(`[onebot] dispatching message for session ${sessionId}`);
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
- api.logger?.info?.(`[onebot] longMessageMode=${longMessageMode}, threshold=${longMessageThreshold}`);
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
- api.logger?.error?.(`[onebot] normal-mode flush failed: ${e?.message ?? e}`);
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
- api.logger?.info?.(`[onebot] final check: totalLen=${totalLen}, threshold=${longMessageThreshold}, isLong=${isLong}, isIncremental=${isIncremental}, deliveredChunks=${deliveredChunks.length}`);
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
- api.logger?.info?.(`[onebot] checking og_image: isLong=${isLong}, mode=${longMessageMode}`);
598
+ traceLog.info(`checking og_image: isLong=${isLong}, mode=${longMessageMode}`);
548
599
  if (isLong && longMessageMode === "og_image") {
549
- api.logger?.info?.(`[onebot] triggering og_image for ${totalLen} chars`);
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
- api.logger?.error?.(`[onebot] deliver failed: ${e?.message}`);
699
+ traceLog.error(`deliver failed: ${e?.message}`);
649
700
  }
650
701
  },
651
702
  onError: async (err, info) => {
652
- api.logger?.error?.(`[onebot] ${info?.kind} reply failed: ${err}`);
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
- api.logger?.error?.(`[onebot] dispatch failed: ${err?.message}`);
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
- clearNormalModeFlushTimer();
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
- notice_type?: string;
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kirigaya/openclaw-onebot",
3
- "version": "1.0.9",
3
+ "version": "1.1.1",
4
4
  "description": "OneBot v11 protocol channel plugin for OpenClaw (QQ/Lagrange.Core/go-cqhttp)",
5
5
  "license": "MIT",
6
6
  "publishConfig": {