@tencent-connect/openclaw-qqbot 1.7.1 → 2.0.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 (283) hide show
  1. package/README.md +206 -4
  2. package/README.zh.md +207 -4
  3. package/bin/qqbot-cli.js +5 -2
  4. package/dist/index.cjs +15549 -0
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.d.cts +890 -0
  7. package/index.ts +56 -18
  8. package/openclaw.plugin.json +19 -3
  9. package/package.json +14 -23
  10. package/preload.cjs +17 -14
  11. package/scripts/link-sdk-core.cjs +133 -50
  12. package/scripts/upgrade-via-npm.sh +400 -73
  13. package/scripts/upgrade-via-source.sh +126 -89
  14. package/skills/qqbot-channel/SKILL.md +34 -12
  15. package/src/adapter/contract.ts +69 -0
  16. package/src/adapter/gateway.ts +61 -0
  17. package/src/adapter/index.ts +15 -0
  18. package/src/adapter/lint.ts +144 -0
  19. package/src/adapter/media.ts +112 -0
  20. package/src/adapter/pairing.ts +99 -0
  21. package/src/adapter/resolve.ts +333 -0
  22. package/src/adapter/setup.ts +55 -0
  23. package/src/adapter/webhook.ts +248 -0
  24. package/src/adapter/workspace.ts +29 -0
  25. package/src/bot-instance.ts +60 -0
  26. package/src/channel.ts +202 -413
  27. package/src/commands/bot-approve.ts +143 -0
  28. package/src/commands/bot-clear-storage.ts +114 -0
  29. package/src/commands/bot-group-always.ts +62 -0
  30. package/src/commands/bot-help.ts +40 -0
  31. package/src/commands/bot-logs.ts +248 -0
  32. package/src/commands/bot-me.ts +18 -0
  33. package/src/commands/bot-pairing.ts +57 -0
  34. package/src/commands/bot-ping.ts +33 -0
  35. package/src/commands/bot-streaming.ts +55 -0
  36. package/src/commands/bot-upgrade.ts +56 -0
  37. package/src/commands/bot-version.ts +41 -0
  38. package/src/commands/config-util.ts +96 -0
  39. package/src/commands/index.ts +49 -0
  40. package/src/config.ts +59 -19
  41. package/src/dispatch/body-assembler.ts +356 -0
  42. package/src/dispatch/ctx-builder.ts +110 -0
  43. package/src/dispatch/dispatch.ts +352 -0
  44. package/src/dispatch/envelope-builder.ts +112 -0
  45. package/src/dispatch/index.ts +2 -0
  46. package/src/{approval-handler.ts → features/approval-handler.ts} +40 -94
  47. package/src/features/approval-utils.ts +41 -0
  48. package/src/features/credential-backup.ts +82 -0
  49. package/src/features/history-store.ts +19 -0
  50. package/src/features/msgid-cache.ts +55 -0
  51. package/src/features/onboarding.ts +38 -0
  52. package/src/{proactive.ts → features/proactive.ts} +68 -49
  53. package/src/features/ref-index-store.ts +282 -0
  54. package/src/{update-checker.ts → features/update-checker.ts} +15 -35
  55. package/src/gateway/event-handlers.ts +241 -0
  56. package/src/gateway/index.ts +3 -0
  57. package/src/gateway/lifecycle.ts +221 -0
  58. package/src/gateway/middleware-setup.ts +139 -0
  59. package/src/gateway/qqbot-gateway.ts +342 -0
  60. package/src/middleware/access-control.ts +130 -0
  61. package/src/middleware/attachment.ts +314 -0
  62. package/src/middleware/policy-injector.ts +66 -0
  63. package/src/openclaw-plugin-sdk.d.ts +81 -10
  64. package/src/outbound/cron-scheduler.ts +130 -0
  65. package/src/outbound/debounce.ts +102 -0
  66. package/src/outbound/deliver-pipeline.ts +235 -0
  67. package/src/outbound/image-size.ts +123 -0
  68. package/src/outbound/index.ts +3 -0
  69. package/src/outbound/local-file-router.ts +145 -0
  70. package/src/outbound/media-send.ts +377 -0
  71. package/src/outbound/outbound-service.ts +222 -0
  72. package/src/outbound/reply-limiter.ts +128 -0
  73. package/src/outbound/sanitize.ts +32 -0
  74. package/src/outbound/streaming-controller.ts +228 -0
  75. package/src/outbound/target.ts +63 -0
  76. package/src/outbound/tts-provider.ts +84 -0
  77. package/src/request-context.ts +9 -8
  78. package/src/runtime.ts +31 -6
  79. package/src/setup/account-key.ts +41 -0
  80. package/src/setup/finalize.ts +110 -0
  81. package/src/setup/login.ts +197 -0
  82. package/src/setup/surface.ts +40 -0
  83. package/src/tools/platform.ts +149 -0
  84. package/src/tools/remind.ts +1 -1
  85. package/src/types-augment.d.ts +54 -0
  86. package/src/types.ts +71 -8
  87. package/src/typings/openclaw-webhook-ingress.d.ts +66 -0
  88. package/src/utils/mention.ts +52 -0
  89. package/src/utils/pkg-version.ts +84 -47
  90. package/src/utils/platform.ts +31 -7
  91. package/src/utils/plugin-logger.ts +104 -0
  92. package/src/utils/ssrf-guard.ts +32 -2
  93. package/src/utils/stt.ts +137 -0
  94. package/src/utils/voice-text.ts +61 -0
  95. package/tsconfig.json +2 -1
  96. package/tsup.config.ts +57 -0
  97. package/dist/index.d.ts +0 -17
  98. package/dist/index.js +0 -26
  99. package/dist/src/admin-resolver.d.ts +0 -33
  100. package/dist/src/admin-resolver.js +0 -157
  101. package/dist/src/api.d.ts +0 -307
  102. package/dist/src/api.js +0 -910
  103. package/dist/src/approval-handler.d.ts +0 -47
  104. package/dist/src/approval-handler.js +0 -372
  105. package/dist/src/channel.d.ts +0 -29
  106. package/dist/src/channel.js +0 -528
  107. package/dist/src/config.d.ts +0 -56
  108. package/dist/src/config.js +0 -278
  109. package/dist/src/credential-backup.d.ts +0 -31
  110. package/dist/src/credential-backup.js +0 -66
  111. package/dist/src/deliver-debounce.d.ts +0 -74
  112. package/dist/src/deliver-debounce.js +0 -174
  113. package/dist/src/gateway.d.ts +0 -18
  114. package/dist/src/gateway.js +0 -2063
  115. package/dist/src/group-history.d.ts +0 -136
  116. package/dist/src/group-history.js +0 -226
  117. package/dist/src/image-server.d.ts +0 -87
  118. package/dist/src/image-server.js +0 -570
  119. package/dist/src/inbound-attachments.d.ts +0 -60
  120. package/dist/src/inbound-attachments.js +0 -248
  121. package/dist/src/known-users.d.ts +0 -100
  122. package/dist/src/known-users.js +0 -263
  123. package/dist/src/message-gating.d.ts +0 -53
  124. package/dist/src/message-gating.js +0 -107
  125. package/dist/src/message-queue.d.ts +0 -91
  126. package/dist/src/message-queue.js +0 -257
  127. package/dist/src/onboarding.d.ts +0 -10
  128. package/dist/src/onboarding.js +0 -203
  129. package/dist/src/outbound-deliver.d.ts +0 -48
  130. package/dist/src/outbound-deliver.js +0 -392
  131. package/dist/src/outbound.d.ts +0 -220
  132. package/dist/src/outbound.js +0 -975
  133. package/dist/src/proactive.d.ts +0 -170
  134. package/dist/src/proactive.js +0 -399
  135. package/dist/src/ref-index-store.d.ts +0 -101
  136. package/dist/src/ref-index-store.js +0 -298
  137. package/dist/src/reply-dispatcher.d.ts +0 -35
  138. package/dist/src/reply-dispatcher.js +0 -311
  139. package/dist/src/request-context.d.ts +0 -25
  140. package/dist/src/request-context.js +0 -37
  141. package/dist/src/runtime.d.ts +0 -3
  142. package/dist/src/runtime.js +0 -13
  143. package/dist/src/session-store.d.ts +0 -52
  144. package/dist/src/session-store.js +0 -254
  145. package/dist/src/slash-commands.d.ts +0 -82
  146. package/dist/src/slash-commands.js +0 -2117
  147. package/dist/src/startup-greeting.d.ts +0 -30
  148. package/dist/src/startup-greeting.js +0 -97
  149. package/dist/src/streaming.d.ts +0 -247
  150. package/dist/src/streaming.js +0 -899
  151. package/dist/src/stt.d.ts +0 -21
  152. package/dist/src/stt.js +0 -70
  153. package/dist/src/tools/channel.d.ts +0 -16
  154. package/dist/src/tools/channel.js +0 -234
  155. package/dist/src/tools/remind.d.ts +0 -2
  156. package/dist/src/tools/remind.js +0 -256
  157. package/dist/src/types.d.ts +0 -443
  158. package/dist/src/types.js +0 -22
  159. package/dist/src/typing-keepalive.d.ts +0 -27
  160. package/dist/src/typing-keepalive.js +0 -64
  161. package/dist/src/update-checker.d.ts +0 -36
  162. package/dist/src/update-checker.js +0 -171
  163. package/dist/src/utils/audio-convert.d.ts +0 -98
  164. package/dist/src/utils/audio-convert.js +0 -755
  165. package/dist/src/utils/chunked-upload.d.ts +0 -68
  166. package/dist/src/utils/chunked-upload.js +0 -341
  167. package/dist/src/utils/file-utils.d.ts +0 -61
  168. package/dist/src/utils/file-utils.js +0 -172
  169. package/dist/src/utils/image-size.d.ts +0 -51
  170. package/dist/src/utils/image-size.js +0 -234
  171. package/dist/src/utils/media-send.d.ts +0 -158
  172. package/dist/src/utils/media-send.js +0 -502
  173. package/dist/src/utils/media-tags.d.ts +0 -14
  174. package/dist/src/utils/media-tags.js +0 -165
  175. package/dist/src/utils/payload.d.ts +0 -112
  176. package/dist/src/utils/payload.js +0 -186
  177. package/dist/src/utils/pkg-version.d.ts +0 -5
  178. package/dist/src/utils/pkg-version.js +0 -61
  179. package/dist/src/utils/platform.d.ts +0 -137
  180. package/dist/src/utils/platform.js +0 -390
  181. package/dist/src/utils/ssrf-guard.d.ts +0 -25
  182. package/dist/src/utils/ssrf-guard.js +0 -91
  183. package/dist/src/utils/text-parsing.d.ts +0 -31
  184. package/dist/src/utils/text-parsing.js +0 -74
  185. package/dist/src/utils/upload-cache.d.ts +0 -34
  186. package/dist/src/utils/upload-cache.js +0 -93
  187. package/node_modules/@eshaz/web-worker/LICENSE +0 -201
  188. package/node_modules/@eshaz/web-worker/README.md +0 -134
  189. package/node_modules/@eshaz/web-worker/browser.js +0 -17
  190. package/node_modules/@eshaz/web-worker/cjs/browser.js +0 -16
  191. package/node_modules/@eshaz/web-worker/cjs/node.js +0 -219
  192. package/node_modules/@eshaz/web-worker/index.d.ts +0 -4
  193. package/node_modules/@eshaz/web-worker/node.js +0 -223
  194. package/node_modules/@eshaz/web-worker/package.json +0 -54
  195. package/node_modules/@wasm-audio-decoders/common/index.js +0 -5
  196. package/node_modules/@wasm-audio-decoders/common/package.json +0 -36
  197. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +0 -231
  198. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +0 -129
  199. package/node_modules/@wasm-audio-decoders/common/src/puff/README +0 -67
  200. package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +0 -31
  201. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +0 -863
  202. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +0 -35
  203. package/node_modules/@wasm-audio-decoders/common/src/utilities.js +0 -3
  204. package/node_modules/@wasm-audio-decoders/common/types.d.ts +0 -7
  205. package/node_modules/mpg123-decoder/README.md +0 -265
  206. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +0 -185
  207. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +0 -1
  208. package/node_modules/mpg123-decoder/index.js +0 -8
  209. package/node_modules/mpg123-decoder/package.json +0 -58
  210. package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +0 -464
  211. package/node_modules/mpg123-decoder/src/MPEGDecoder.js +0 -200
  212. package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +0 -21
  213. package/node_modules/mpg123-decoder/types.d.ts +0 -30
  214. package/node_modules/silk-wasm/LICENSE +0 -21
  215. package/node_modules/silk-wasm/README.md +0 -85
  216. package/node_modules/silk-wasm/lib/index.cjs +0 -16
  217. package/node_modules/silk-wasm/lib/index.d.ts +0 -70
  218. package/node_modules/silk-wasm/lib/index.mjs +0 -16
  219. package/node_modules/silk-wasm/lib/silk.wasm +0 -0
  220. package/node_modules/silk-wasm/lib/utils.d.ts +0 -4
  221. package/node_modules/silk-wasm/package.json +0 -39
  222. package/node_modules/simple-yenc/.github/FUNDING.yml +0 -1
  223. package/node_modules/simple-yenc/.prettierignore +0 -1
  224. package/node_modules/simple-yenc/LICENSE +0 -7
  225. package/node_modules/simple-yenc/README.md +0 -163
  226. package/node_modules/simple-yenc/dist/esm.js +0 -1
  227. package/node_modules/simple-yenc/dist/index.js +0 -1
  228. package/node_modules/simple-yenc/package.json +0 -50
  229. package/node_modules/simple-yenc/rollup.config.js +0 -27
  230. package/node_modules/simple-yenc/src/simple-yenc.js +0 -302
  231. package/node_modules/ws/LICENSE +0 -20
  232. package/node_modules/ws/README.md +0 -548
  233. package/node_modules/ws/browser.js +0 -8
  234. package/node_modules/ws/index.js +0 -13
  235. package/node_modules/ws/lib/buffer-util.js +0 -131
  236. package/node_modules/ws/lib/constants.js +0 -19
  237. package/node_modules/ws/lib/event-target.js +0 -292
  238. package/node_modules/ws/lib/extension.js +0 -203
  239. package/node_modules/ws/lib/limiter.js +0 -55
  240. package/node_modules/ws/lib/permessage-deflate.js +0 -528
  241. package/node_modules/ws/lib/receiver.js +0 -706
  242. package/node_modules/ws/lib/sender.js +0 -602
  243. package/node_modules/ws/lib/stream.js +0 -161
  244. package/node_modules/ws/lib/subprotocol.js +0 -62
  245. package/node_modules/ws/lib/validation.js +0 -152
  246. package/node_modules/ws/lib/websocket-server.js +0 -554
  247. package/node_modules/ws/lib/websocket.js +0 -1393
  248. package/node_modules/ws/package.json +0 -69
  249. package/node_modules/ws/wrapper.mjs +0 -8
  250. package/scripts/postinstall-link-sdk.js +0 -126
  251. package/skills/qqbot-media/SKILL.md +0 -60
  252. package/src/admin-resolver.ts +0 -181
  253. package/src/api.ts +0 -1319
  254. package/src/credential-backup.ts +0 -72
  255. package/src/deliver-debounce.ts +0 -229
  256. package/src/gateway.ts +0 -2304
  257. package/src/group-history.ts +0 -328
  258. package/src/image-server.ts +0 -675
  259. package/src/inbound-attachments.ts +0 -321
  260. package/src/known-users.ts +0 -353
  261. package/src/message-gating.ts +0 -190
  262. package/src/message-queue.ts +0 -354
  263. package/src/onboarding.ts +0 -274
  264. package/src/outbound-deliver.ts +0 -486
  265. package/src/outbound.ts +0 -1179
  266. package/src/ref-index-store.ts +0 -412
  267. package/src/reply-dispatcher.ts +0 -334
  268. package/src/session-store.ts +0 -303
  269. package/src/slash-commands.ts +0 -2297
  270. package/src/startup-greeting.ts +0 -120
  271. package/src/streaming.ts +0 -1077
  272. package/src/stt.ts +0 -86
  273. package/src/tools/channel.ts +0 -281
  274. package/src/typing-keepalive.ts +0 -59
  275. package/src/utils/audio-convert.ts +0 -859
  276. package/src/utils/chunked-upload.ts +0 -483
  277. package/src/utils/file-utils.ts +0 -193
  278. package/src/utils/image-size.ts +0 -266
  279. package/src/utils/media-send.ts +0 -636
  280. package/src/utils/media-tags.ts +0 -183
  281. package/src/utils/payload.ts +0 -265
  282. package/src/utils/text-parsing.ts +0 -85
  283. package/src/utils/upload-cache.ts +0 -128
@@ -0,0 +1,33 @@
1
+ import type { SlashCommand } from '@tencent-connect/qqbot-nodejs';
2
+
3
+ /** /bot-ping — 测试当前 openclaw 与 QQ 连接的网络延迟 */
4
+ export function botPing(): SlashCommand {
5
+ return {
6
+ name: 'bot-ping',
7
+ description: '测试当前 openclaw 与 QQ 连接的网络延迟',
8
+ usage: [
9
+ '/bot-ping',
10
+ '',
11
+ '测试 OpenClaw 主机与 QQ 服务器之间的网络延迟。',
12
+ '返回网络传输耗时和插件处理耗时。',
13
+ ].join('\n'),
14
+ handler: (ctx) => {
15
+ const now = Date.now();
16
+ const ts = (ctx.message as any).timestamp;
17
+ const eventTime = ts ? new Date(ts).getTime() : NaN;
18
+ if (isNaN(eventTime)) {
19
+ return '✅ pong!';
20
+ }
21
+ const totalMs = now - eventTime;
22
+ const qqToPlugin = ctx.receivedAt - eventTime;
23
+ const pluginProcess = now - ctx.receivedAt;
24
+ return [
25
+ '✅ pong!',
26
+ '',
27
+ `⏱ 延迟: ${totalMs}ms`,
28
+ ` ├ 网络传输: ${qqToPlugin}ms`,
29
+ ` └ 插件处理: ${pluginProcess}ms`,
30
+ ].join('\n');
31
+ },
32
+ };
33
+ }
@@ -0,0 +1,55 @@
1
+ import type { SlashCommand } from '@tencent-connect/qqbot-nodejs';
2
+ import type { PluginRuntime } from 'openclaw/plugin-sdk';
3
+ import type { ResolvedQQBotAccount } from '../types.js';
4
+ import { updateAccountConfig, checkCommandAuth } from './config-util.js';
5
+
6
+ /** /bot-streaming — 一键开关流式消息 */
7
+ export function botStreaming(account: ResolvedQQBotAccount, getRuntime: () => PluginRuntime): SlashCommand {
8
+ return {
9
+ name: 'bot-streaming',
10
+ description: '一键开关流式消息',
11
+ scope: 'c2c',
12
+ authorized: checkCommandAuth,
13
+ usage: `/bot-streaming
14
+
15
+ 查看当前流式消息状态,或切换开/关。
16
+ 流式消息仅支持 C2C(私聊)场景。`,
17
+ handler: async (ctx) => {
18
+ const args = (Array.isArray(ctx.command.args) ? ctx.command.args.join(' ') : String(ctx.command.args ?? '')).trim().toLowerCase();
19
+ const streaming = account.config?.streaming;
20
+ const currentEnabled = typeof streaming === 'boolean'
21
+ ? streaming
22
+ : (streaming as any)?.mode !== 'off';
23
+
24
+ // 无参数 → 显示状态
25
+ if (!args) {
26
+ const status = currentEnabled ? '✅ 已启用' : '❌ 未启用';
27
+ const toggleHint = currentEnabled
28
+ ? '<qqbot-cmd-input text="/bot-streaming off" show="关闭流式"/>'
29
+ : '<qqbot-cmd-input text="/bot-streaming on" show="开启流式"/>';
30
+ return [
31
+ `🌊 流式消息状态: ${status}`,
32
+ '',
33
+ '流式消息仅支持 C2C(私聊)场景。',
34
+ `点击 ${toggleHint} 切换。`,
35
+ ].join('\n');
36
+ }
37
+
38
+ // on / off → 切换
39
+ const targetEnabled = args === 'on' || args === '1' || args === 'true';
40
+ if (targetEnabled === currentEnabled) {
41
+ return `ℹ️ 流式消息已经是${currentEnabled ? '开启' : '关闭'}状态,无需切换。`;
42
+ }
43
+
44
+ const error = await updateAccountConfig(account, getRuntime, (acfg) => {
45
+ (acfg as any).streaming = { mode: targetEnabled ? 'partial' : 'off' };
46
+ });
47
+ if (error) return error;
48
+
49
+ account.config.streaming = { mode: targetEnabled ? 'partial' : 'off' };
50
+ return targetEnabled
51
+ ? '✅ 流式消息已开启,私聊消息将以流式方式发送。'
52
+ : '✅ 流式消息已关闭,私聊消息将以静态方式发送。';
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,56 @@
1
+ import type { SlashCommand } from '@tencent-connect/qqbot-nodejs';
2
+ import type { ResolvedQQBotAccount } from '../types.js';
3
+ import { getPackageVersion } from '../utils/pkg-version.js';
4
+ import { getUpdateInfo } from '../features/update-checker.js';
5
+ import { checkCommandAuth } from './config-util.js';
6
+
7
+ const PLUGIN_VERSION = getPackageVersion();
8
+ const DEFAULT_UPGRADE_URL = 'https://docs.qq.com/doc/DSGxOZk1oVnVKVkpq';
9
+ const GITHUB_URL = 'https://github.com/nicepkg/openclaw';
10
+
11
+ /** /bot-upgrade — 检查更新并查看升级指引 */
12
+ export function botUpgrade(account: ResolvedQQBotAccount): SlashCommand {
13
+ return {
14
+ name: 'bot-upgrade',
15
+ description: '检查更新并查看升级指引',
16
+ scope: 'c2c',
17
+ authorized: checkCommandAuth,
18
+ usage: [
19
+ '/bot-upgrade 检查是否有新版本',
20
+ ].join('\n'),
21
+ handler: async () => {
22
+ const url = account.config.upgradeUrl ?? DEFAULT_UPGRADE_URL;
23
+ const info = await getUpdateInfo();
24
+
25
+ if (info.checkedAt === 0) {
26
+ return '⏳ 版本检查中,请稍后再试';
27
+ }
28
+
29
+ if (info.error) {
30
+ return [
31
+ '❌ 主机网络访问异常,无法检查更新',
32
+ '',
33
+ `查看升级指引:[点击查看](${url})`,
34
+ ].join('\n');
35
+ }
36
+
37
+ if (!info.hasUpdate) {
38
+ return [
39
+ `✅ 当前已是最新版本 v${PLUGIN_VERSION}`,
40
+ '',
41
+ `项目地址:[GitHub](${GITHUB_URL})`,
42
+ ].join('\n');
43
+ }
44
+
45
+ return [
46
+ '🆕 发现新版本',
47
+ '',
48
+ `当前版本:**v${PLUGIN_VERSION}**`,
49
+ `最新版本:**v${info.latest}**`,
50
+ '',
51
+ `📖 升级指引:[点击查看](${url})`,
52
+ `🌟 官方 GitHub 仓库:[点击前往](${GITHUB_URL})`,
53
+ ].join('\n');
54
+ },
55
+ };
56
+ }
@@ -0,0 +1,41 @@
1
+ import type { SlashCommand } from '@tencent-connect/qqbot-nodejs';
2
+ import type { ResolvedQQBotAccount } from '../types.js';
3
+ import { getPackageVersion } from '../utils/pkg-version.js';
4
+ import { getUpdateInfo } from '../features/update-checker.js';
5
+ import { getOpenClawVersion } from '../bot-instance.js';
6
+
7
+ const PLUGIN_VERSION = getPackageVersion();
8
+ const GITHUB_URL = 'https://github.com/nicepkg/openclaw';
9
+
10
+ /** /bot-version — 查看插件版本号 */
11
+ export function botVersion(_account: ResolvedQQBotAccount): SlashCommand {
12
+ return {
13
+ name: 'bot-version',
14
+ description: '查看插件版本号',
15
+ usage: [
16
+ '/bot-version',
17
+ '',
18
+ '查看当前 QQBot 插件版本和 OpenClaw 框架版本。',
19
+ '同时检查是否有新版本可用。',
20
+ ].join('\n'),
21
+ handler: async () => {
22
+ const frameworkVersion = getOpenClawVersion();
23
+ const lines = [
24
+ `🦞框架版本:${frameworkVersion}`,
25
+ `🤖QQBot 插件版本:v${PLUGIN_VERSION}`,
26
+ ];
27
+
28
+ const info = await getUpdateInfo();
29
+ if (info.checkedAt === 0) {
30
+ lines.push('⏳ 版本检查中...');
31
+ } else if (info.error) {
32
+ lines.push('⚠️ 版本检查失败');
33
+ } else if (info.hasUpdate && info.latest) {
34
+ lines.push(`🆕最新可用版本:v${info.latest},点击 <qqbot-cmd-input text="/bot-upgrade" show="/bot-upgrade"/> 查看升级指引`);
35
+ }
36
+
37
+ lines.push(`🌟官方 GitHub 仓库:[点击前往](${GITHUB_URL})`);
38
+ return lines.join('\n');
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,96 @@
1
+ import type { ResolvedQQBotAccount } from '../types.js';
2
+ import type { SlashCommandHandlerContext } from '@tencent-connect/qqbot-nodejs';
3
+ import type { PluginRuntime } from 'openclaw/plugin-sdk';
4
+ import { getAdapters } from '../adapter/resolve.js';
5
+
6
+ /**
7
+ * 命令授权检查(用于 SlashCommand.authorized 回调)。
8
+ *
9
+ * 规则:
10
+ * - mode=open → 允许
11
+ * - allowFrom 为空或含 "*" → 允许
12
+ * - senderId 在 allowFrom 中 → 允许
13
+ * - 否则 → 返回错误消息
14
+ */
15
+ export function checkCommandAuth(ctx: SlashCommandHandlerContext): boolean | string {
16
+ const p = (ctx.state as any).policy;
17
+ const mode = p?.c2cMode ?? 'allowlist';
18
+ const allowFrom: string[] = p?.allowFrom ?? [];
19
+ if (mode === 'open' || !allowFrom.length || allowFrom.includes('*')) return true;
20
+ return allowFrom.includes(ctx.message.senderId) || '⚠️ 无权限执行此命令';
21
+ }
22
+
23
+ interface PersistResult {
24
+ persist: (updater: (cfg: any) => void) => Promise<void>;
25
+ }
26
+
27
+ /**
28
+ * 底层:获取 persistConfig,统一处理 runtime/adapters 判空。
29
+ * @returns [error, result] — error 非空时不可用;result.persist 直接调 persistConfig。
30
+ */
31
+ async function resolvePersistFn(getRuntime: () => PluginRuntime): Promise<[string | null, PersistResult | null]> {
32
+ const runtime = getRuntime();
33
+ if (!runtime) return ['⚠️ runtime 不可用,无法修改配置。', null];
34
+
35
+ const adapters = getAdapters(runtime);
36
+ if (!adapters.persistConfig) {
37
+ return ['⚠️ 当前框架版本不支持在线修改配置,请手动编辑配置文件。', null];
38
+ }
39
+
40
+ return [
41
+ null,
42
+ { persist: (updater: (cfg: any) => void) => adapters.persistConfig!(updater) },
43
+ ];
44
+ }
45
+
46
+ /**
47
+ * 持久化更新**账户**配置。自动处理命名账户 / 默认账户路径差异。
48
+ */
49
+ export async function updateAccountConfig(
50
+ account: ResolvedQQBotAccount,
51
+ getRuntime: () => PluginRuntime,
52
+ updater: (accountConfig: Record<string, unknown>) => void,
53
+ ): Promise<string | null> {
54
+ const [err, result] = await resolvePersistFn(getRuntime);
55
+ if (err || !result) return err;
56
+
57
+ try {
58
+ await result.persist((cfg: any) => {
59
+ cfg.channels ??= {};
60
+ cfg.channels.qqbot ??= {};
61
+
62
+ const qqbot = cfg.channels.qqbot;
63
+ const accountId = account.accountId;
64
+ const isNamedAccount = accountId !== 'default' && qqbot.accounts?.[accountId];
65
+
66
+ if (isNamedAccount) {
67
+ qqbot.accounts[accountId] ??= {};
68
+ updater(qqbot.accounts[accountId]);
69
+ } else {
70
+ updater(qqbot);
71
+ }
72
+ });
73
+ return null;
74
+ } catch (e) {
75
+ return `⚠️ 配置修改失败:${e instanceof Error ? e.message : String(e)}`;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * 持久化更新**框架通用**配置(不从 channels.qqbot.accounts 走)。
81
+ * 适用于 tools.exec 等非账户级字段。
82
+ */
83
+ export async function updateGlobalConfig(
84
+ getRuntime: () => PluginRuntime,
85
+ updater: (cfg: any) => void,
86
+ ): Promise<string | null> {
87
+ const [err, result] = await resolvePersistFn(getRuntime);
88
+ if (err || !result) return err;
89
+
90
+ try {
91
+ await result.persist(updater);
92
+ return null;
93
+ } catch (e) {
94
+ return `⚠️ 配置修改失败:${e instanceof Error ? e.message : String(e)}`;
95
+ }
96
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * 斜杠命令注册表
3
+ *
4
+ * 通过 SDK 的 slashCommand 中间件统一注册所有内置命令。
5
+ * 每个命令拆分为独立文件,此处仅编排。
6
+ */
7
+ import type { SlashCommand } from '@tencent-connect/qqbot-nodejs';
8
+ import type { PluginRuntime } from 'openclaw/plugin-sdk';
9
+ import type { ResolvedQQBotAccount } from '../types.js';
10
+ import { botHelp } from './bot-help.js';
11
+ import { botPing } from './bot-ping.js';
12
+ import { botVersion } from './bot-version.js';
13
+ import { botMe } from './bot-me.js';
14
+ import { botUpgrade } from './bot-upgrade.js';
15
+ import { botStreaming } from './bot-streaming.js';
16
+ import { botClearStorage } from './bot-clear-storage.js';
17
+ import { botLogs } from './bot-logs.js';
18
+ import { botApprove } from './bot-approve.js';
19
+ import { botGroupAlways } from './bot-group-always.js';
20
+ import { botPairing } from './bot-pairing.js';
21
+
22
+ export interface CommandBuildOptions {
23
+ getRuntime: () => PluginRuntime;
24
+ }
25
+
26
+ /**
27
+ * 构建标准命令列表(匹配后直接回复,不进入 AI)
28
+ */
29
+ export function buildCommandList(account: ResolvedQQBotAccount, opts: CommandBuildOptions): SlashCommand[] {
30
+ const commands: SlashCommand[] = [];
31
+
32
+ // help 需要访问完整命令列表,延迟绑定
33
+ const help = botHelp(account, () => commands);
34
+ commands.push(
35
+ help,
36
+ botPing(),
37
+ botVersion(account),
38
+ botMe(),
39
+ botUpgrade(account),
40
+ botLogs(opts.getRuntime()),
41
+ botStreaming(account, opts.getRuntime),
42
+ botClearStorage(account),
43
+ botApprove(opts.getRuntime),
44
+ botGroupAlways(account, opts.getRuntime),
45
+ botPairing(opts.getRuntime),
46
+ );
47
+
48
+ return commands;
49
+ }
package/src/config.ts CHANGED
@@ -69,6 +69,8 @@ function evaluateMatchedGroupAccessForPolicy(params: {
69
69
  }
70
70
 
71
71
  interface QQBotChannelConfig extends QQBotAccountConfig {
72
+ /** HTTP/WebSocket User-Agent 追加后缀 */
73
+ userAgentSuffix?: string;
72
74
  accounts?: Record<string, QQBotAccountConfig>;
73
75
  }
74
76
 
@@ -77,7 +79,10 @@ interface QQBotChannelConfig extends QQBotAccountConfig {
77
79
  const DEFAULT_GROUP_POLICY: GroupPolicy = "open";
78
80
 
79
81
  /** 群历史缓存条数默认值 */
80
- const DEFAULT_GROUP_HISTORY_LIMIT = 50;
82
+ const DEFAULT_GROUP_HISTORY_LIMIT = 20;
83
+
84
+ /** 单条消息默认处理超时(0 = 不限制) */
85
+ const DEFAULT_PROCESSING_TIMEOUT_MS = 0;
81
86
 
82
87
  const DEFAULT_GROUP_CONFIG: Omit<Required<GroupConfig>, "prompt"> = {
83
88
  requireMention: true,
@@ -108,8 +113,9 @@ export function resolveGroupAllowFrom(cfg: OpenClawConfig, accountId?: string):
108
113
 
109
114
  /** 检查指定群是否被允许(使用标准策略引擎) */
110
115
  export function isGroupAllowed(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): boolean {
111
- const policy = resolveGroupPolicy(cfg, accountId);
112
- const allowList = resolveGroupAllowFrom(cfg, accountId);
116
+ const account = resolveQQBotAccount(cfg, accountId);
117
+ const policy = account.config?.groupPolicy ?? DEFAULT_GROUP_POLICY;
118
+ const allowList = (account.config?.groupAllowFrom ?? []).map((id) => String(id).trim().toUpperCase());
113
119
  const allowlistConfigured = allowList.length > 0;
114
120
  const allowlistMatched = allowList.some((id) => id === "*" || id === groupOpenid.toUpperCase());
115
121
 
@@ -120,26 +126,28 @@ export function isGroupAllowed(cfg: OpenClawConfig, groupOpenid: string, account
120
126
  }).allowed;
121
127
  }
122
128
 
123
- type ResolvedGroupConfig = Omit<Required<GroupConfig>, "prompt"> & Pick<GroupConfig, "prompt">;
129
+ export type ResolvedGroupConfig = Required<GroupConfig>;
124
130
 
125
- /** 解析指定群配置(具体 groupOpenid > 通配符 "*" > 默认值) */
126
- export function resolveGroupConfig(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): ResolvedGroupConfig {
127
- const account = resolveQQBotAccount(cfg, accountId);
131
+ export function resolveGroupConfigFromAccount(account: ResolvedQQBotAccount, groupOpenid: string): ResolvedGroupConfig {
128
132
  const groups = account.config?.groups ?? {};
129
-
130
133
  const wildcardCfg = groups["*"] ?? {};
131
134
  const specificCfg = groups[groupOpenid] ?? {};
135
+ const accountDefaultRequireMention = account.config?.defaultRequireMention ?? DEFAULT_GROUP_CONFIG.requireMention;
132
136
 
133
137
  return {
134
- requireMention: specificCfg.requireMention ?? wildcardCfg.requireMention ?? DEFAULT_GROUP_CONFIG.requireMention,
138
+ requireMention: specificCfg.requireMention ?? wildcardCfg.requireMention ?? accountDefaultRequireMention,
135
139
  ignoreOtherMentions: specificCfg.ignoreOtherMentions ?? wildcardCfg.ignoreOtherMentions ?? DEFAULT_GROUP_CONFIG.ignoreOtherMentions,
136
140
  toolPolicy: specificCfg.toolPolicy ?? wildcardCfg.toolPolicy ?? DEFAULT_GROUP_CONFIG.toolPolicy,
137
141
  name: specificCfg.name ?? wildcardCfg.name ?? DEFAULT_GROUP_CONFIG.name,
138
- prompt: specificCfg.prompt ?? wildcardCfg.prompt,
142
+ prompt: specificCfg.prompt ?? wildcardCfg.prompt ?? DEFAULT_GROUP_PROMPT,
139
143
  historyLimit: specificCfg.historyLimit ?? wildcardCfg.historyLimit ?? DEFAULT_GROUP_CONFIG.historyLimit,
140
144
  };
141
145
  }
142
146
 
147
+ export function resolveGroupConfig(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): ResolvedGroupConfig {
148
+ return resolveGroupConfigFromAccount(resolveQQBotAccount(cfg, accountId), groupOpenid);
149
+ }
150
+
143
151
  /** 解析群历史消息缓存条数 */
144
152
  export function resolveHistoryLimit(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): number {
145
153
  return Math.max(0, resolveGroupConfig(cfg, groupOpenid, accountId).historyLimit);
@@ -147,10 +155,7 @@ export function resolveHistoryLimit(cfg: OpenClawConfig, groupOpenid: string, ac
147
155
 
148
156
  /** 解析群行为 PE(具体群 > "*" > 默认值) */
149
157
  export function resolveGroupPrompt(cfg: OpenClawConfig, groupOpenid: string, accountId?: string): string {
150
- const account = resolveQQBotAccount(cfg, accountId);
151
- const groups = account.config?.groups ?? {};
152
-
153
- return groups[groupOpenid]?.prompt ?? groups["*"]?.prompt ?? DEFAULT_GROUP_PROMPT;
158
+ return resolveGroupConfig(cfg, groupOpenid, accountId).prompt;
154
159
  }
155
160
 
156
161
  /** 解析群是否需要 @机器人才响应 */
@@ -174,6 +179,14 @@ export function resolveGroupName(cfg: OpenClawConfig, groupOpenid: string, accou
174
179
  return name || groupOpenid.slice(0, 8);
175
180
  }
176
181
 
182
+ /**
183
+ * 解析 User-Agent 追加后缀(仅通道级:channels.qqbot.userAgentSuffix)
184
+ */
185
+ export function resolveUserAgentSuffix(cfg: OpenClawConfig): string {
186
+ const qqbot = cfg.channels?.qqbot as QQBotChannelConfig | undefined;
187
+ return qqbot?.userAgentSuffix ? String(qqbot.userAgentSuffix).trim() : "";
188
+ }
189
+
177
190
  function normalizeAppId(raw: unknown): string {
178
191
  if (raw === null || raw === undefined) return "";
179
192
  return String(raw).trim();
@@ -220,6 +233,25 @@ export function resolveDefaultQQBotAccountId(cfg: OpenClawConfig): string {
220
233
  return DEFAULT_ACCOUNT_ID;
221
234
  }
222
235
 
236
+ /**
237
+ * 解析单条消息处理超时时间(ms)。
238
+ * 优先级:账户配置 > 环境变量 OPENCLAW_PROCESSING_TIMEOUT_MS > 默认
239
+ * 返回 0 表示不限制超时。
240
+ */
241
+ export function resolveProcessingTimeoutMs(
242
+ accountConfig?: QQBotAccountConfig,
243
+ ): number {
244
+ if (accountConfig?.processingTimeoutMs !== undefined) {
245
+ return accountConfig.processingTimeoutMs;
246
+ }
247
+ const env = process.env.OPENCLAW_PROCESSING_TIMEOUT_MS;
248
+ if (env) {
249
+ const v = Number(env);
250
+ if (!Number.isNaN(v) && v >= 0) return v;
251
+ }
252
+ return DEFAULT_PROCESSING_TIMEOUT_MS;
253
+ }
254
+
223
255
  /**
224
256
  * 解析 QQBot 账户配置
225
257
  */
@@ -276,19 +308,29 @@ export function resolveQQBotAccount(
276
308
  clientSecret,
277
309
  secretSource,
278
310
  systemPrompt: accountConfig.systemPrompt,
279
- imageServerBaseUrl: accountConfig.imageServerBaseUrl || process.env.QQBOT_IMAGE_SERVER_BASE_URL,
280
311
  markdownSupport: accountConfig.markdownSupport !== false,
281
- config: accountConfig,
312
+ userAgentSuffix: resolveUserAgentSuffix(cfg),
313
+ processingTimeoutMs: resolveProcessingTimeoutMs(accountConfig),
314
+ config: normalizeAccountConfig(accountConfig),
282
315
  };
283
316
  }
284
317
 
318
+ /** 兼容旧版 streaming: boolean 格式 → { mode: "partial" | "off" },对齐框架 schema */
319
+ function normalizeAccountConfig(raw: QQBotAccountConfig): QQBotAccountConfig {
320
+ if (typeof (raw as any).streaming === 'boolean') {
321
+ const { streaming, ...rest } = raw as any;
322
+ return { ...rest, streaming: { mode: streaming ? 'partial' : 'off' } };
323
+ }
324
+ return raw;
325
+ }
326
+
285
327
  /**
286
328
  * 应用账户配置
287
329
  */
288
330
  export function applyQQBotAccountConfig(
289
331
  cfg: OpenClawConfig,
290
332
  accountId: string,
291
- input: { appId?: string; clientSecret?: string; clientSecretFile?: string; name?: string; imageServerBaseUrl?: string }
333
+ input: { appId?: string; clientSecret?: string; clientSecretFile?: string; name?: string }
292
334
  ): OpenClawConfig {
293
335
  const next = { ...cfg };
294
336
 
@@ -310,7 +352,6 @@ export function applyQQBotAccountConfig(
310
352
  ? { clientSecretFile: input.clientSecretFile }
311
353
  : {}),
312
354
  ...(input.name ? { name: input.name } : {}),
313
- ...(input.imageServerBaseUrl ? { imageServerBaseUrl: input.imageServerBaseUrl } : {}),
314
355
  },
315
356
  };
316
357
  } else {
@@ -336,7 +377,6 @@ export function applyQQBotAccountConfig(
336
377
  ? { clientSecretFile: input.clientSecretFile }
337
378
  : {}),
338
379
  ...(input.name ? { name: input.name } : {}),
339
- ...(input.imageServerBaseUrl ? { imageServerBaseUrl: input.imageServerBaseUrl } : {}),
340
380
  },
341
381
  },
342
382
  },