@ryantest/openclaw-qqbot 0.0.3 → 1.6.6-alpha.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.
Files changed (89) hide show
  1. package/README.md +2 -15
  2. package/README.zh.md +3 -16
  3. package/dist/src/admin-resolver.d.ts +12 -6
  4. package/dist/src/admin-resolver.js +69 -34
  5. package/dist/src/api.d.ts +105 -1
  6. package/dist/src/api.js +164 -15
  7. package/dist/src/channel.js +13 -0
  8. package/dist/src/config.js +3 -10
  9. package/dist/src/deliver-debounce.d.ts +74 -0
  10. package/dist/src/deliver-debounce.js +174 -0
  11. package/dist/src/gateway.js +450 -248
  12. package/dist/src/image-server.d.ts +27 -8
  13. package/dist/src/image-server.js +179 -71
  14. package/dist/src/inbound-attachments.d.ts +3 -1
  15. package/dist/src/inbound-attachments.js +28 -14
  16. package/dist/src/outbound-deliver.js +77 -148
  17. package/dist/src/outbound.d.ts +6 -4
  18. package/dist/src/outbound.js +266 -442
  19. package/dist/src/reply-dispatcher.js +4 -4
  20. package/dist/src/request-context.d.ts +18 -0
  21. package/dist/src/request-context.js +30 -0
  22. package/dist/src/slash-commands.js +277 -32
  23. package/dist/src/startup-greeting.d.ts +5 -5
  24. package/dist/src/startup-greeting.js +32 -13
  25. package/dist/src/streaming.d.ts +244 -0
  26. package/dist/src/streaming.js +907 -0
  27. package/dist/src/tools/remind.js +11 -10
  28. package/dist/src/types.d.ts +101 -0
  29. package/dist/src/types.js +17 -1
  30. package/dist/src/update-checker.js +2 -8
  31. package/dist/src/utils/audio-convert.d.ts +9 -0
  32. package/dist/src/utils/audio-convert.js +51 -0
  33. package/dist/src/utils/chunked-upload.d.ts +59 -0
  34. package/dist/src/utils/chunked-upload.js +289 -0
  35. package/dist/src/utils/file-utils.d.ts +7 -1
  36. package/dist/src/utils/file-utils.js +24 -2
  37. package/dist/src/utils/media-send.d.ts +147 -0
  38. package/dist/src/utils/media-send.js +434 -0
  39. package/dist/src/utils/pkg-version.d.ts +5 -0
  40. package/dist/src/utils/pkg-version.js +51 -0
  41. package/dist/src/utils/ssrf-guard.d.ts +25 -0
  42. package/dist/src/utils/ssrf-guard.js +91 -0
  43. package/node_modules/ws/index.js +15 -6
  44. package/node_modules/ws/lib/permessage-deflate.js +6 -6
  45. package/node_modules/ws/lib/websocket-server.js +5 -5
  46. package/node_modules/ws/lib/websocket.js +6 -6
  47. package/node_modules/ws/package.json +4 -3
  48. package/node_modules/ws/wrapper.mjs +14 -1
  49. package/openclaw.plugin.json +1 -0
  50. package/package.json +11 -22
  51. package/scripts/postinstall-link-sdk.js +113 -0
  52. package/scripts/upgrade-via-npm.ps1 +161 -6
  53. package/scripts/upgrade-via-npm.sh +311 -104
  54. package/scripts/upgrade-via-source.sh +117 -0
  55. package/skills/qqbot-media/SKILL.md +9 -5
  56. package/skills/qqbot-remind/SKILL.md +3 -3
  57. package/src/admin-resolver.ts +76 -35
  58. package/src/api.ts +284 -12
  59. package/src/channel.ts +12 -0
  60. package/src/config.ts +3 -10
  61. package/src/deliver-debounce.ts +229 -0
  62. package/src/gateway.ts +277 -67
  63. package/src/image-server.ts +213 -77
  64. package/src/inbound-attachments.ts +32 -15
  65. package/src/outbound-deliver.ts +77 -157
  66. package/src/outbound.ts +304 -451
  67. package/src/reply-dispatcher.ts +4 -4
  68. package/src/request-context.ts +39 -0
  69. package/src/slash-commands.ts +303 -33
  70. package/src/startup-greeting.ts +35 -13
  71. package/src/streaming.ts +1096 -0
  72. package/src/tools/remind.ts +15 -11
  73. package/src/types.ts +111 -0
  74. package/src/update-checker.ts +2 -7
  75. package/src/utils/audio-convert.ts +56 -0
  76. package/src/utils/chunked-upload.ts +419 -0
  77. package/src/utils/file-utils.ts +28 -2
  78. package/src/utils/media-send.ts +563 -0
  79. package/src/utils/pkg-version.ts +54 -0
  80. package/src/utils/ssrf-guard.ts +102 -0
  81. package/clawdbot.plugin.json +0 -16
  82. package/dist/src/user-messages.d.ts +0 -8
  83. package/dist/src/user-messages.js +0 -8
  84. package/moltbot.plugin.json +0 -16
  85. package/scripts/upgrade-via-alt-pkg.sh +0 -307
  86. package/src/bot-logs-2026-03-21T11-21-47(2).txt +0 -46
  87. package/src/gateway.log +0 -43
  88. package/src/openclaw-2026-03-21.log +0 -3729
  89. package/src/user-messages.ts +0 -7
@@ -52,17 +52,10 @@ export function resolveQQBotAccount(cfg, accountId) {
52
52
  let clientSecret = "";
53
53
  let secretSource = "none";
54
54
  if (resolvedAccountId === DEFAULT_ACCOUNT_ID) {
55
- // 默认账户从顶层读取
55
+ // 默认账户从顶层读取(展开所有字段,避免遗漏新增配置项)
56
+ const { accounts: _accounts, ...topLevelConfig } = qqbot ?? {};
56
57
  accountConfig = {
57
- enabled: qqbot?.enabled,
58
- name: qqbot?.name,
59
- appId: qqbot?.appId,
60
- clientSecret: qqbot?.clientSecret,
61
- clientSecretFile: qqbot?.clientSecretFile,
62
- dmPolicy: qqbot?.dmPolicy,
63
- allowFrom: qqbot?.allowFrom,
64
- systemPrompt: qqbot?.systemPrompt,
65
- imageServerBaseUrl: qqbot?.imageServerBaseUrl,
58
+ ...topLevelConfig,
66
59
  markdownSupport: qqbot?.markdownSupport ?? true,
67
60
  };
68
61
  appId = normalizeAppId(qqbot?.appId);
@@ -0,0 +1,74 @@
1
+ /**
2
+ * 出站消息合并回复(Deliver Debounce)模块
3
+ *
4
+ * 解决的问题:
5
+ * 当 openclaw 框架层的 embedded agent 超时或快速连续产生多次 deliver 时,
6
+ * 用户会在短时间内收到大量碎片消息(消息轰炸)。
7
+ *
8
+ * 解决方案:
9
+ * 在 deliver 回调和实际发送之间加入 debounce 层。
10
+ * 短时间内(windowMs)连续到达的多条纯文本 deliver 会被合并为一条消息发送。
11
+ * 含媒体的 deliver 会立即 flush 已缓冲的文本并正常处理媒体。
12
+ */
13
+ import type { DeliverDebounceConfig } from "./types.js";
14
+ export interface DeliverPayload {
15
+ text?: string;
16
+ mediaUrls?: string[];
17
+ mediaUrl?: string;
18
+ }
19
+ export interface DeliverInfo {
20
+ kind: string;
21
+ }
22
+ /** 实际执行发送的回调 */
23
+ export type DeliverExecutor = (payload: DeliverPayload, info: DeliverInfo) => Promise<void>;
24
+ export declare class DeliverDebouncer {
25
+ private readonly windowMs;
26
+ private readonly maxWaitMs;
27
+ private readonly separator;
28
+ private readonly executor;
29
+ private readonly log?;
30
+ private readonly prefix;
31
+ /** 缓冲中的文本片段 */
32
+ private bufferedTexts;
33
+ /** 缓冲中最后一次 deliver 的 info(用于 flush 时传递 kind) */
34
+ private lastInfo;
35
+ /** 缓冲中最后一次 deliver 的 payload(非文本字段,如 mediaUrls) */
36
+ private lastPayload;
37
+ /** debounce 定时器 */
38
+ private debounceTimer;
39
+ /** 最大等待定时器(从第一条 deliver 开始计算) */
40
+ private maxWaitTimer;
41
+ /** 是否正在 flush */
42
+ private flushing;
43
+ /** 已销毁标记 */
44
+ private disposed;
45
+ constructor(config: DeliverDebounceConfig | undefined, executor: DeliverExecutor, log?: {
46
+ info: (msg: string) => void;
47
+ error: (msg: string) => void;
48
+ }, prefix?: string);
49
+ /**
50
+ * 接收一次 deliver 调用。
51
+ * - 纯文本 deliver → 缓冲并设置 debounce 定时器
52
+ * - 含媒体 deliver → 先 flush 已缓冲文本,再直接执行当前 deliver
53
+ */
54
+ deliver(payload: DeliverPayload, info: DeliverInfo): Promise<void>;
55
+ /**
56
+ * 将缓冲中的文本合并为一条消息发送
57
+ */
58
+ flush(): Promise<void>;
59
+ /**
60
+ * 销毁:flush 剩余缓冲并清除定时器
61
+ */
62
+ dispose(): Promise<void>;
63
+ /** 当前是否有缓冲中的文本 */
64
+ get hasPending(): boolean;
65
+ /** 缓冲中的文本数量 */
66
+ get pendingCount(): number;
67
+ }
68
+ /**
69
+ * 根据配置创建 debouncer 或返回 null(禁用时)
70
+ */
71
+ export declare function createDeliverDebouncer(config: DeliverDebounceConfig | undefined, executor: DeliverExecutor, log?: {
72
+ info: (msg: string) => void;
73
+ error: (msg: string) => void;
74
+ }, prefix?: string): DeliverDebouncer | null;
@@ -0,0 +1,174 @@
1
+ /**
2
+ * 出站消息合并回复(Deliver Debounce)模块
3
+ *
4
+ * 解决的问题:
5
+ * 当 openclaw 框架层的 embedded agent 超时或快速连续产生多次 deliver 时,
6
+ * 用户会在短时间内收到大量碎片消息(消息轰炸)。
7
+ *
8
+ * 解决方案:
9
+ * 在 deliver 回调和实际发送之间加入 debounce 层。
10
+ * 短时间内(windowMs)连续到达的多条纯文本 deliver 会被合并为一条消息发送。
11
+ * 含媒体的 deliver 会立即 flush 已缓冲的文本并正常处理媒体。
12
+ */
13
+ // ============ 默认值 ============
14
+ const DEFAULT_WINDOW_MS = 1500;
15
+ const DEFAULT_MAX_WAIT_MS = 8000;
16
+ const DEFAULT_SEPARATOR = "\n\n---\n\n";
17
+ // ============ DeliverDebouncer 类 ============
18
+ export class DeliverDebouncer {
19
+ windowMs;
20
+ maxWaitMs;
21
+ separator;
22
+ executor;
23
+ log;
24
+ prefix;
25
+ /** 缓冲中的文本片段 */
26
+ bufferedTexts = [];
27
+ /** 缓冲中最后一次 deliver 的 info(用于 flush 时传递 kind) */
28
+ lastInfo = null;
29
+ /** 缓冲中最后一次 deliver 的 payload(非文本字段,如 mediaUrls) */
30
+ lastPayload = null;
31
+ /** debounce 定时器 */
32
+ debounceTimer = null;
33
+ /** 最大等待定时器(从第一条 deliver 开始计算) */
34
+ maxWaitTimer = null;
35
+ /** 是否正在 flush */
36
+ flushing = false;
37
+ /** 已销毁标记 */
38
+ disposed = false;
39
+ constructor(config, executor, log, prefix = "[debounce]") {
40
+ this.windowMs = config?.windowMs ?? DEFAULT_WINDOW_MS;
41
+ this.maxWaitMs = config?.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
42
+ this.separator = config?.separator ?? DEFAULT_SEPARATOR;
43
+ this.executor = executor;
44
+ this.log = log;
45
+ this.prefix = prefix;
46
+ }
47
+ /**
48
+ * 接收一次 deliver 调用。
49
+ * - 纯文本 deliver → 缓冲并设置 debounce 定时器
50
+ * - 含媒体 deliver → 先 flush 已缓冲文本,再直接执行当前 deliver
51
+ */
52
+ async deliver(payload, info) {
53
+ if (this.disposed)
54
+ return;
55
+ const hasMedia = Boolean((payload.mediaUrls && payload.mediaUrls.length > 0) || payload.mediaUrl);
56
+ const text = (payload.text ?? "").trim();
57
+ // 含媒体的 deliver:立即 flush 缓冲 + 直接执行
58
+ if (hasMedia) {
59
+ this.log?.info(`${this.prefix} Media deliver detected, flushing ${this.bufferedTexts.length} buffered text(s) first`);
60
+ await this.flush();
61
+ await this.executor(payload, info);
62
+ return;
63
+ }
64
+ // 空文本 deliver:直接透传(不缓冲)
65
+ if (!text) {
66
+ await this.executor(payload, info);
67
+ return;
68
+ }
69
+ // 纯文本 deliver:缓冲
70
+ this.bufferedTexts.push(text);
71
+ this.lastInfo = info;
72
+ this.lastPayload = payload;
73
+ this.log?.info(`${this.prefix} Buffered text #${this.bufferedTexts.length} (${text.length} chars), window=${this.windowMs}ms`);
74
+ // 重置 debounce 定时器
75
+ if (this.debounceTimer) {
76
+ clearTimeout(this.debounceTimer);
77
+ }
78
+ this.debounceTimer = setTimeout(() => {
79
+ this.flush().catch((err) => {
80
+ this.log?.error(`${this.prefix} Flush error (debounce timer): ${err}`);
81
+ });
82
+ }, this.windowMs);
83
+ // 首次缓冲时启动最大等待定时器
84
+ if (this.bufferedTexts.length === 1) {
85
+ if (this.maxWaitTimer) {
86
+ clearTimeout(this.maxWaitTimer);
87
+ }
88
+ this.maxWaitTimer = setTimeout(() => {
89
+ this.log?.info(`${this.prefix} Max wait (${this.maxWaitMs}ms) reached, force flushing`);
90
+ this.flush().catch((err) => {
91
+ this.log?.error(`${this.prefix} Flush error (max wait timer): ${err}`);
92
+ });
93
+ }, this.maxWaitMs);
94
+ }
95
+ }
96
+ /**
97
+ * 将缓冲中的文本合并为一条消息发送
98
+ */
99
+ async flush() {
100
+ if (this.flushing || this.bufferedTexts.length === 0)
101
+ return;
102
+ this.flushing = true;
103
+ // 清除定时器
104
+ if (this.debounceTimer) {
105
+ clearTimeout(this.debounceTimer);
106
+ this.debounceTimer = null;
107
+ }
108
+ if (this.maxWaitTimer) {
109
+ clearTimeout(this.maxWaitTimer);
110
+ this.maxWaitTimer = null;
111
+ }
112
+ // 取出缓冲
113
+ const texts = this.bufferedTexts;
114
+ const info = this.lastInfo;
115
+ const lastPayload = this.lastPayload;
116
+ this.bufferedTexts = [];
117
+ this.lastInfo = null;
118
+ this.lastPayload = null;
119
+ try {
120
+ if (texts.length === 1) {
121
+ // 只有一条,直接透传原始 payload
122
+ this.log?.info(`${this.prefix} Flushing single buffered text (${texts[0].length} chars)`);
123
+ await this.executor({ ...lastPayload, text: texts[0] }, info);
124
+ }
125
+ else {
126
+ // 多条合并
127
+ const merged = texts.join(this.separator);
128
+ this.log?.info(`${this.prefix} Merged ${texts.length} buffered texts into one (${merged.length} chars)`);
129
+ await this.executor({ ...lastPayload, text: merged }, info);
130
+ }
131
+ }
132
+ finally {
133
+ this.flushing = false;
134
+ }
135
+ }
136
+ /**
137
+ * 销毁:flush 剩余缓冲并清除定时器
138
+ */
139
+ async dispose() {
140
+ this.disposed = true;
141
+ if (this.debounceTimer) {
142
+ clearTimeout(this.debounceTimer);
143
+ this.debounceTimer = null;
144
+ }
145
+ if (this.maxWaitTimer) {
146
+ clearTimeout(this.maxWaitTimer);
147
+ this.maxWaitTimer = null;
148
+ }
149
+ // flush 剩余
150
+ if (this.bufferedTexts.length > 0) {
151
+ this.flushing = false; // 确保 flush 能执行
152
+ await this.flush();
153
+ }
154
+ }
155
+ /** 当前是否有缓冲中的文本 */
156
+ get hasPending() {
157
+ return this.bufferedTexts.length > 0;
158
+ }
159
+ /** 缓冲中的文本数量 */
160
+ get pendingCount() {
161
+ return this.bufferedTexts.length;
162
+ }
163
+ }
164
+ // ============ 工厂函数 ============
165
+ /**
166
+ * 根据配置创建 debouncer 或返回 null(禁用时)
167
+ */
168
+ export function createDeliverDebouncer(config, executor, log, prefix) {
169
+ // 未配置时默认启用
170
+ if (config?.enabled === false) {
171
+ return null;
172
+ }
173
+ return new DeliverDebouncer(config, executor, log, prefix);
174
+ }