@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
@@ -1,170 +0,0 @@
1
- /**
2
- * QQ Bot 主动发送消息模块
3
- *
4
- * 该模块提供以下能力:
5
- * 1. 记录已知用户(曾与机器人交互过的用户)
6
- * 2. 主动发送消息给用户或群组
7
- * 3. 查询已知用户列表
8
- */
9
- import type { ResolvedQQBotAccount } from "./types.js";
10
- /**
11
- * 已知用户信息
12
- */
13
- export interface KnownUser {
14
- type: "c2c" | "group" | "channel";
15
- openid: string;
16
- accountId: string;
17
- nickname?: string;
18
- firstInteractionAt: number;
19
- lastInteractionAt: number;
20
- }
21
- /**
22
- * 主动发送消息选项
23
- */
24
- export interface ProactiveSendOptions {
25
- to: string;
26
- text: string;
27
- type?: "c2c" | "group" | "channel";
28
- imageUrl?: string;
29
- accountId?: string;
30
- }
31
- /**
32
- * 主动发送消息结果
33
- */
34
- export interface ProactiveSendResult {
35
- success: boolean;
36
- messageId?: string;
37
- timestamp?: number | string;
38
- error?: string;
39
- }
40
- /**
41
- * 列出已知用户选项
42
- */
43
- export interface ListKnownUsersOptions {
44
- type?: "c2c" | "group" | "channel";
45
- accountId?: string;
46
- sortByLastInteraction?: boolean;
47
- limit?: number;
48
- }
49
- import type { OpenClawConfig } from "openclaw/plugin-sdk";
50
- /**
51
- * 记录一个已知用户(当收到用户消息时调用)
52
- *
53
- * @param user - 用户信息
54
- */
55
- export declare function recordKnownUser(user: Omit<KnownUser, "firstInteractionAt">): void;
56
- /**
57
- * 获取一个已知用户
58
- *
59
- * @param type - 用户类型
60
- * @param openid - 用户 openid
61
- * @param accountId - 账户 ID
62
- */
63
- export declare function getKnownUser(type: string, openid: string, accountId: string): KnownUser | undefined;
64
- /**
65
- * 列出已知用户
66
- *
67
- * @param options - 过滤选项
68
- */
69
- export declare function listKnownUsers(options?: ListKnownUsersOptions): KnownUser[];
70
- /**
71
- * 删除一个已知用户
72
- *
73
- * @param type - 用户类型
74
- * @param openid - 用户 openid
75
- * @param accountId - 账户 ID
76
- */
77
- export declare function removeKnownUser(type: string, openid: string, accountId: string): boolean;
78
- /**
79
- * 清除所有已知用户
80
- *
81
- * @param accountId - 可选,只清除指定账户的用户
82
- */
83
- export declare function clearKnownUsers(accountId?: string): number;
84
- /**
85
- * 主动发送消息(带配置解析)
86
- * 注意:与 outbound.ts 中的 sendProactiveMessage 不同,这个函数接受 OpenClawConfig 并自动解析账户
87
- *
88
- * @param options - 发送选项
89
- * @param cfg - OpenClaw 配置
90
- * @returns 发送结果
91
- *
92
- * @example
93
- * ```typescript
94
- * // 发送私聊消息
95
- * const result = await sendProactive({
96
- * to: "E7A8F3B2C1D4E5F6A7B8C9D0E1F2A3B4", // 用户 openid
97
- * text: "你好!这是一条主动消息",
98
- * type: "c2c",
99
- * }, cfg);
100
- *
101
- * // 发送群聊消息
102
- * const result = await sendProactive({
103
- * to: "A1B2C3D4E5F6A7B8", // 群组 openid
104
- * text: "群公告:今天有活动",
105
- * type: "group",
106
- * }, cfg);
107
- *
108
- * // 发送带图片的消息
109
- * const result = await sendProactive({
110
- * to: "E7A8F3B2C1D4E5F6A7B8C9D0E1F2A3B4",
111
- * text: "看看这张图片",
112
- * imageUrl: "https://example.com/image.png",
113
- * type: "c2c",
114
- * }, cfg);
115
- * ```
116
- */
117
- export declare function sendProactive(options: ProactiveSendOptions, cfg: OpenClawConfig): Promise<ProactiveSendResult>;
118
- /**
119
- * 批量发送主动消息
120
- *
121
- * @param recipients - 接收者列表(openid 数组)
122
- * @param text - 消息内容
123
- * @param type - 消息类型
124
- * @param cfg - OpenClaw 配置
125
- * @param accountId - 账户 ID
126
- * @returns 发送结果列表
127
- */
128
- export declare function sendBulkProactiveMessage(recipients: string[], text: string, type: "c2c" | "group", cfg: OpenClawConfig, accountId?: string): Promise<Array<{
129
- to: string;
130
- result: ProactiveSendResult;
131
- }>>;
132
- /**
133
- * 发送消息给所有已知用户
134
- *
135
- * @param text - 消息内容
136
- * @param cfg - OpenClaw 配置
137
- * @param options - 过滤选项
138
- * @returns 发送结果统计
139
- */
140
- export declare function broadcastMessage(text: string, cfg: OpenClawConfig, options?: {
141
- type?: "c2c" | "group";
142
- accountId?: string;
143
- limit?: number;
144
- }): Promise<{
145
- total: number;
146
- success: number;
147
- failed: number;
148
- results: Array<{
149
- to: string;
150
- result: ProactiveSendResult;
151
- }>;
152
- }>;
153
- /**
154
- * 根据账户配置直接发送主动消息(不需要 cfg)
155
- *
156
- * @param account - 已解析的账户配置
157
- * @param to - 目标 openid
158
- * @param text - 消息内容
159
- * @param type - 消息类型
160
- */
161
- export declare function sendProactiveMessageDirect(account: ResolvedQQBotAccount, to: string, text: string, type?: "c2c" | "group"): Promise<ProactiveSendResult>;
162
- /**
163
- * 获取已知用户统计
164
- */
165
- export declare function getKnownUsersStats(accountId?: string): {
166
- total: number;
167
- c2c: number;
168
- group: number;
169
- channel: number;
170
- };
@@ -1,399 +0,0 @@
1
- /**
2
- * QQ Bot 主动发送消息模块
3
- *
4
- * 该模块提供以下能力:
5
- * 1. 记录已知用户(曾与机器人交互过的用户)
6
- * 2. 主动发送消息给用户或群组
7
- * 3. 查询已知用户列表
8
- */
9
- import * as fs from "node:fs";
10
- import * as path from "node:path";
11
- import { getAccessToken, sendProactiveC2CMessage, sendProactiveGroupMessage, sendC2CImageMessage, sendGroupImageMessage, } from "./api.js";
12
- import { resolveQQBotAccount } from "./config.js";
13
- // ============ 用户存储管理 ============
14
- /**
15
- * 已知用户存储
16
- * 使用简单的 JSON 文件存储,保存在 .openclaw/qqbot 目录下
17
- */
18
- import { getQQBotDataDir } from "./utils/platform.js";
19
- const STORAGE_DIR = getQQBotDataDir("data");
20
- const KNOWN_USERS_FILE = path.join(STORAGE_DIR, "known-users.json");
21
- // 内存缓存
22
- let knownUsersCache = null;
23
- let cacheLastModified = 0;
24
- /**
25
- * 确保存储目录存在
26
- */
27
- function ensureStorageDir() {
28
- if (!fs.existsSync(STORAGE_DIR)) {
29
- fs.mkdirSync(STORAGE_DIR, { recursive: true });
30
- }
31
- }
32
- /**
33
- * 生成用户唯一键
34
- */
35
- function getUserKey(type, openid, accountId) {
36
- return `${accountId}:${type}:${openid}`;
37
- }
38
- /**
39
- * 从文件加载已知用户
40
- */
41
- function loadKnownUsers() {
42
- if (knownUsersCache !== null) {
43
- // 检查文件是否被修改
44
- try {
45
- const stat = fs.statSync(KNOWN_USERS_FILE);
46
- if (stat.mtimeMs <= cacheLastModified) {
47
- return knownUsersCache;
48
- }
49
- }
50
- catch {
51
- // 文件不存在,使用缓存
52
- return knownUsersCache;
53
- }
54
- }
55
- const users = new Map();
56
- try {
57
- if (fs.existsSync(KNOWN_USERS_FILE)) {
58
- const data = fs.readFileSync(KNOWN_USERS_FILE, "utf-8");
59
- const parsed = JSON.parse(data);
60
- for (const user of parsed) {
61
- const key = getUserKey(user.type, user.openid, user.accountId);
62
- users.set(key, user);
63
- }
64
- cacheLastModified = fs.statSync(KNOWN_USERS_FILE).mtimeMs;
65
- }
66
- }
67
- catch (err) {
68
- console.error(`[qqbot:proactive] Failed to load known users: ${err}`);
69
- }
70
- knownUsersCache = users;
71
- return users;
72
- }
73
- /**
74
- * 保存已知用户到文件
75
- */
76
- function saveKnownUsers(users) {
77
- try {
78
- ensureStorageDir();
79
- const data = Array.from(users.values());
80
- fs.writeFileSync(KNOWN_USERS_FILE, JSON.stringify(data, null, 2), "utf-8");
81
- cacheLastModified = Date.now();
82
- knownUsersCache = users;
83
- }
84
- catch (err) {
85
- console.error(`[qqbot:proactive] Failed to save known users: ${err}`);
86
- }
87
- }
88
- /**
89
- * 记录一个已知用户(当收到用户消息时调用)
90
- *
91
- * @param user - 用户信息
92
- */
93
- export function recordKnownUser(user) {
94
- const users = loadKnownUsers();
95
- const key = getUserKey(user.type, user.openid, user.accountId);
96
- const existing = users.get(key);
97
- const now = user.lastInteractionAt || Date.now();
98
- users.set(key, {
99
- ...user,
100
- lastInteractionAt: now,
101
- firstInteractionAt: existing?.firstInteractionAt ?? now,
102
- // 更新昵称(如果有新的)
103
- nickname: user.nickname || existing?.nickname,
104
- });
105
- saveKnownUsers(users);
106
- console.log(`[qqbot:proactive] Recorded user: ${key}`);
107
- }
108
- /**
109
- * 获取一个已知用户
110
- *
111
- * @param type - 用户类型
112
- * @param openid - 用户 openid
113
- * @param accountId - 账户 ID
114
- */
115
- export function getKnownUser(type, openid, accountId) {
116
- const users = loadKnownUsers();
117
- const key = getUserKey(type, openid, accountId);
118
- return users.get(key);
119
- }
120
- /**
121
- * 列出已知用户
122
- *
123
- * @param options - 过滤选项
124
- */
125
- export function listKnownUsers(options) {
126
- const users = loadKnownUsers();
127
- let result = Array.from(users.values());
128
- // 过滤类型
129
- if (options?.type) {
130
- result = result.filter(u => u.type === options.type);
131
- }
132
- // 过滤账户
133
- if (options?.accountId) {
134
- result = result.filter(u => u.accountId === options.accountId);
135
- }
136
- // 排序
137
- if (options?.sortByLastInteraction !== false) {
138
- result.sort((a, b) => b.lastInteractionAt - a.lastInteractionAt);
139
- }
140
- // 限制数量
141
- if (options?.limit && options.limit > 0) {
142
- result = result.slice(0, options.limit);
143
- }
144
- return result;
145
- }
146
- /**
147
- * 删除一个已知用户
148
- *
149
- * @param type - 用户类型
150
- * @param openid - 用户 openid
151
- * @param accountId - 账户 ID
152
- */
153
- export function removeKnownUser(type, openid, accountId) {
154
- const users = loadKnownUsers();
155
- const key = getUserKey(type, openid, accountId);
156
- const deleted = users.delete(key);
157
- if (deleted) {
158
- saveKnownUsers(users);
159
- }
160
- return deleted;
161
- }
162
- /**
163
- * 清除所有已知用户
164
- *
165
- * @param accountId - 可选,只清除指定账户的用户
166
- */
167
- export function clearKnownUsers(accountId) {
168
- const users = loadKnownUsers();
169
- let count = 0;
170
- if (accountId) {
171
- for (const [key, user] of users) {
172
- if (user.accountId === accountId) {
173
- users.delete(key);
174
- count++;
175
- }
176
- }
177
- }
178
- else {
179
- count = users.size;
180
- users.clear();
181
- }
182
- if (count > 0) {
183
- saveKnownUsers(users);
184
- }
185
- return count;
186
- }
187
- // ============ 主动发送消息 ============
188
- /**
189
- * 主动发送消息(带配置解析)
190
- * 注意:与 outbound.ts 中的 sendProactiveMessage 不同,这个函数接受 OpenClawConfig 并自动解析账户
191
- *
192
- * @param options - 发送选项
193
- * @param cfg - OpenClaw 配置
194
- * @returns 发送结果
195
- *
196
- * @example
197
- * ```typescript
198
- * // 发送私聊消息
199
- * const result = await sendProactive({
200
- * to: "E7A8F3B2C1D4E5F6A7B8C9D0E1F2A3B4", // 用户 openid
201
- * text: "你好!这是一条主动消息",
202
- * type: "c2c",
203
- * }, cfg);
204
- *
205
- * // 发送群聊消息
206
- * const result = await sendProactive({
207
- * to: "A1B2C3D4E5F6A7B8", // 群组 openid
208
- * text: "群公告:今天有活动",
209
- * type: "group",
210
- * }, cfg);
211
- *
212
- * // 发送带图片的消息
213
- * const result = await sendProactive({
214
- * to: "E7A8F3B2C1D4E5F6A7B8C9D0E1F2A3B4",
215
- * text: "看看这张图片",
216
- * imageUrl: "https://example.com/image.png",
217
- * type: "c2c",
218
- * }, cfg);
219
- * ```
220
- */
221
- export async function sendProactive(options, cfg) {
222
- const { to, text, type = "c2c", imageUrl, accountId = "default" } = options;
223
- // 解析账户配置
224
- const account = resolveQQBotAccount(cfg, accountId);
225
- if (!account.appId || !account.clientSecret) {
226
- return {
227
- success: false,
228
- error: "QQBot not configured (missing appId or clientSecret)",
229
- };
230
- }
231
- try {
232
- const accessToken = await getAccessToken(account.appId, account.clientSecret);
233
- // 如果有图片,先发送图片
234
- if (imageUrl) {
235
- try {
236
- if (type === "c2c") {
237
- await sendC2CImageMessage(accessToken, to, imageUrl, undefined, undefined);
238
- }
239
- else if (type === "group") {
240
- await sendGroupImageMessage(accessToken, to, imageUrl, undefined, undefined);
241
- }
242
- console.log(`[qqbot:proactive] Sent image to ${type}:${to}`);
243
- }
244
- catch (err) {
245
- console.error(`[qqbot:proactive] Failed to send image: ${err}`);
246
- // 图片发送失败不影响文本发送
247
- }
248
- }
249
- // 发送文本消息
250
- let result;
251
- if (type === "c2c") {
252
- result = await sendProactiveC2CMessage(accessToken, to, text);
253
- }
254
- else if (type === "group") {
255
- result = await sendProactiveGroupMessage(accessToken, to, text);
256
- }
257
- else if (type === "channel") {
258
- // 频道消息需要 channel_id,这里暂时不支持主动发送
259
- return {
260
- success: false,
261
- error: "Channel proactive messages are not supported. Please use group or c2c.",
262
- };
263
- }
264
- else {
265
- return {
266
- success: false,
267
- error: `Unknown message type: ${type}`,
268
- };
269
- }
270
- console.log(`[qqbot:proactive] Sent message to ${type}:${to}, id: ${result.id}`);
271
- return {
272
- success: true,
273
- messageId: result.id,
274
- timestamp: result.timestamp,
275
- };
276
- }
277
- catch (err) {
278
- const message = err instanceof Error ? err.message : String(err);
279
- console.error(`[qqbot:proactive] Failed to send message: ${message}`);
280
- return {
281
- success: false,
282
- error: message,
283
- };
284
- }
285
- }
286
- /**
287
- * 批量发送主动消息
288
- *
289
- * @param recipients - 接收者列表(openid 数组)
290
- * @param text - 消息内容
291
- * @param type - 消息类型
292
- * @param cfg - OpenClaw 配置
293
- * @param accountId - 账户 ID
294
- * @returns 发送结果列表
295
- */
296
- export async function sendBulkProactiveMessage(recipients, text, type, cfg, accountId = "default") {
297
- const results = [];
298
- for (const to of recipients) {
299
- const result = await sendProactive({ to, text, type, accountId }, cfg);
300
- results.push({ to, result });
301
- // 添加延迟,避免频率限制
302
- await new Promise(resolve => setTimeout(resolve, 500));
303
- }
304
- return results;
305
- }
306
- /**
307
- * 发送消息给所有已知用户
308
- *
309
- * @param text - 消息内容
310
- * @param cfg - OpenClaw 配置
311
- * @param options - 过滤选项
312
- * @returns 发送结果统计
313
- */
314
- export async function broadcastMessage(text, cfg, options) {
315
- const users = listKnownUsers({
316
- type: options?.type,
317
- accountId: options?.accountId,
318
- limit: options?.limit,
319
- sortByLastInteraction: true,
320
- });
321
- // 过滤掉频道用户(不支持主动发送)
322
- const validUsers = users.filter(u => u.type === "c2c" || u.type === "group");
323
- const results = [];
324
- let success = 0;
325
- let failed = 0;
326
- for (const user of validUsers) {
327
- const result = await sendProactive({
328
- to: user.openid,
329
- text,
330
- type: user.type,
331
- accountId: user.accountId,
332
- }, cfg);
333
- results.push({ to: user.openid, result });
334
- if (result.success) {
335
- success++;
336
- }
337
- else {
338
- failed++;
339
- }
340
- // 添加延迟,避免频率限制
341
- await new Promise(resolve => setTimeout(resolve, 500));
342
- }
343
- return {
344
- total: validUsers.length,
345
- success,
346
- failed,
347
- results,
348
- };
349
- }
350
- // ============ 辅助函数 ============
351
- /**
352
- * 根据账户配置直接发送主动消息(不需要 cfg)
353
- *
354
- * @param account - 已解析的账户配置
355
- * @param to - 目标 openid
356
- * @param text - 消息内容
357
- * @param type - 消息类型
358
- */
359
- export async function sendProactiveMessageDirect(account, to, text, type = "c2c") {
360
- if (!account.appId || !account.clientSecret) {
361
- return {
362
- success: false,
363
- error: "QQBot not configured (missing appId or clientSecret)",
364
- };
365
- }
366
- try {
367
- const accessToken = await getAccessToken(account.appId, account.clientSecret);
368
- let result;
369
- if (type === "c2c") {
370
- result = await sendProactiveC2CMessage(accessToken, to, text);
371
- }
372
- else {
373
- result = await sendProactiveGroupMessage(accessToken, to, text);
374
- }
375
- return {
376
- success: true,
377
- messageId: result.id,
378
- timestamp: result.timestamp,
379
- };
380
- }
381
- catch (err) {
382
- return {
383
- success: false,
384
- error: err instanceof Error ? err.message : String(err),
385
- };
386
- }
387
- }
388
- /**
389
- * 获取已知用户统计
390
- */
391
- export function getKnownUsersStats(accountId) {
392
- const users = listKnownUsers({ accountId });
393
- return {
394
- total: users.length,
395
- c2c: users.filter(u => u.type === "c2c").length,
396
- group: users.filter(u => u.type === "group").length,
397
- channel: users.filter(u => u.type === "channel").length,
398
- };
399
- }
@@ -1,101 +0,0 @@
1
- /**
2
- * QQ Bot 引用索引持久化存储
3
- *
4
- * QQ Bot 使用 REFIDX_xxx 索引体系做引用消息,
5
- * 入站事件只有索引值,无 API 可回查内容。
6
- * 采用 内存缓存 + JSONL 追加写持久化 方案,确保重启后历史引用仍可命中。
7
- *
8
- * 存储位置:~/.openclaw/qqbot/data/ref-index.jsonl
9
- *
10
- * 每行格式:{"k":"REFIDX_xxx","v":{...},"t":1709000000}
11
- * - k = refIdx 键
12
- * - v = 消息数据
13
- * - t = 写入时间(用于 TTL 淘汰和 compact)
14
- */
15
- export interface RefIndexEntry {
16
- /** 消息文本内容(完整保存) */
17
- content: string;
18
- /** 发送者 ID */
19
- senderId: string;
20
- /** 发送者名称 */
21
- senderName?: string;
22
- /** 消息时间戳 (ms) */
23
- timestamp: number;
24
- /** 是否是 bot 发出的消息 */
25
- isBot?: boolean;
26
- /** 附件摘要(图片/语音/视频/文件等) */
27
- attachments?: RefAttachmentSummary[];
28
- }
29
- /** 附件摘要:存本地路径、在线 URL 和类型描述 */
30
- export interface RefAttachmentSummary {
31
- /** 附件类型 */
32
- type: "image" | "voice" | "video" | "file" | "unknown";
33
- /** 文件名(如有) */
34
- filename?: string;
35
- /** MIME 类型 */
36
- contentType?: string;
37
- /** 语音转录文本(入站:STT/ASR识别结果;出站:TTS原文本) */
38
- transcript?: string;
39
- /** 语音转录来源:stt=本地STT、asr=平台ASR、tts=TTS原文本、fallback=兜底文案 */
40
- transcriptSource?: "stt" | "asr" | "tts" | "fallback";
41
- /** 已下载到本地的文件路径(持久化后可供引用时访问) */
42
- localPath?: string;
43
- /** 在线来源 URL(公网图片/文件等) */
44
- url?: string;
45
- }
46
- /**
47
- * 存储一条消息的 refIdx 映射
48
- */
49
- export declare function setRefIndex(refIdx: string, entry: RefIndexEntry): void;
50
- /**
51
- * 查找被引用消息
52
- */
53
- export declare function getRefIndex(refIdx: string): RefIndexEntry | null;
54
- /**
55
- * 将引用消息内容格式化为人类可读的描述(供 AI 上下文注入)
56
- */
57
- export declare function formatRefEntryForAgent(entry: RefIndexEntry): string;
58
- /**
59
- * 将 QQ 推送事件中的 message_reference 结构格式化为人类可读的描述(供 AI 上下文注入)
60
- *
61
- * 完整参考 gateway 中对当前消息的处理流程:
62
- * 1. 调用 processAttachments 下载附件到本地、语音转录
63
- * 2. 调用 formatVoiceText 格式化语音转录文本
64
- * 3. 调用 parseFaceTags 解析 QQ 表情标签
65
- * 4. 按 gateway 中 userContent 的拼接逻辑组合最终文本
66
- */
67
- export declare function formatMessageReferenceForAgent(ref: {
68
- content: string;
69
- attachments?: Array<{
70
- content_type: string;
71
- url: string;
72
- filename?: string;
73
- height?: number;
74
- width?: number;
75
- size?: number;
76
- voice_wav_url?: string;
77
- asr_refer_text?: string;
78
- }>;
79
- } | undefined, ctx: {
80
- appId: string;
81
- peerId?: string;
82
- cfg: unknown;
83
- log?: {
84
- info: (msg: string) => void;
85
- error: (msg: string) => void;
86
- debug?: (msg: string) => void;
87
- };
88
- }): Promise<string>;
89
- /**
90
- * 进程退出前强制 compact(确保数据一致性)
91
- */
92
- export declare function flushRefIndex(): void;
93
- /**
94
- * 缓存统计(调试用)
95
- */
96
- export declare function getRefIndexStats(): {
97
- size: number;
98
- maxEntries: number;
99
- totalLinesOnDisk: number;
100
- filePath: string;
101
- };