@ryantest/openclaw-qqbot 0.0.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.
Files changed (197) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +483 -0
  3. package/README.zh.md +478 -0
  4. package/bin/qqbot-cli.js +243 -0
  5. package/clawdbot.plugin.json +16 -0
  6. package/dist/index.d.ts +17 -0
  7. package/dist/index.js +26 -0
  8. package/dist/src/admin-resolver.d.ts +27 -0
  9. package/dist/src/admin-resolver.js +122 -0
  10. package/dist/src/api.d.ts +156 -0
  11. package/dist/src/api.js +599 -0
  12. package/dist/src/channel.d.ts +11 -0
  13. package/dist/src/channel.js +354 -0
  14. package/dist/src/config.d.ts +25 -0
  15. package/dist/src/config.js +161 -0
  16. package/dist/src/credential-backup.d.ts +31 -0
  17. package/dist/src/credential-backup.js +66 -0
  18. package/dist/src/gateway.d.ts +18 -0
  19. package/dist/src/gateway.js +1265 -0
  20. package/dist/src/image-server.d.ts +68 -0
  21. package/dist/src/image-server.js +462 -0
  22. package/dist/src/inbound-attachments.d.ts +58 -0
  23. package/dist/src/inbound-attachments.js +234 -0
  24. package/dist/src/known-users.d.ts +100 -0
  25. package/dist/src/known-users.js +263 -0
  26. package/dist/src/message-queue.d.ts +50 -0
  27. package/dist/src/message-queue.js +115 -0
  28. package/dist/src/onboarding.d.ts +10 -0
  29. package/dist/src/onboarding.js +203 -0
  30. package/dist/src/outbound-deliver.d.ts +48 -0
  31. package/dist/src/outbound-deliver.js +462 -0
  32. package/dist/src/outbound.d.ts +203 -0
  33. package/dist/src/outbound.js +1102 -0
  34. package/dist/src/proactive.d.ts +170 -0
  35. package/dist/src/proactive.js +399 -0
  36. package/dist/src/ref-index-store.d.ts +70 -0
  37. package/dist/src/ref-index-store.js +273 -0
  38. package/dist/src/reply-dispatcher.d.ts +35 -0
  39. package/dist/src/reply-dispatcher.js +311 -0
  40. package/dist/src/runtime.d.ts +3 -0
  41. package/dist/src/runtime.js +10 -0
  42. package/dist/src/session-store.d.ts +52 -0
  43. package/dist/src/session-store.js +254 -0
  44. package/dist/src/slash-commands.d.ts +71 -0
  45. package/dist/src/slash-commands.js +1179 -0
  46. package/dist/src/startup-greeting.d.ts +30 -0
  47. package/dist/src/startup-greeting.js +78 -0
  48. package/dist/src/stt.d.ts +21 -0
  49. package/dist/src/stt.js +70 -0
  50. package/dist/src/tools/channel.d.ts +16 -0
  51. package/dist/src/tools/channel.js +234 -0
  52. package/dist/src/tools/remind.d.ts +2 -0
  53. package/dist/src/tools/remind.js +247 -0
  54. package/dist/src/types.d.ts +175 -0
  55. package/dist/src/types.js +1 -0
  56. package/dist/src/typing-keepalive.d.ts +27 -0
  57. package/dist/src/typing-keepalive.js +64 -0
  58. package/dist/src/update-checker.d.ts +34 -0
  59. package/dist/src/update-checker.js +166 -0
  60. package/dist/src/user-messages.d.ts +8 -0
  61. package/dist/src/user-messages.js +8 -0
  62. package/dist/src/utils/audio-convert.d.ts +89 -0
  63. package/dist/src/utils/audio-convert.js +704 -0
  64. package/dist/src/utils/file-utils.d.ts +55 -0
  65. package/dist/src/utils/file-utils.js +150 -0
  66. package/dist/src/utils/image-size.d.ts +51 -0
  67. package/dist/src/utils/image-size.js +234 -0
  68. package/dist/src/utils/media-tags.d.ts +14 -0
  69. package/dist/src/utils/media-tags.js +164 -0
  70. package/dist/src/utils/payload.d.ts +112 -0
  71. package/dist/src/utils/payload.js +186 -0
  72. package/dist/src/utils/platform.d.ts +137 -0
  73. package/dist/src/utils/platform.js +390 -0
  74. package/dist/src/utils/text-parsing.d.ts +32 -0
  75. package/dist/src/utils/text-parsing.js +80 -0
  76. package/dist/src/utils/upload-cache.d.ts +34 -0
  77. package/dist/src/utils/upload-cache.js +93 -0
  78. package/index.ts +31 -0
  79. package/moltbot.plugin.json +16 -0
  80. package/node_modules/@eshaz/web-worker/LICENSE +201 -0
  81. package/node_modules/@eshaz/web-worker/README.md +134 -0
  82. package/node_modules/@eshaz/web-worker/browser.js +17 -0
  83. package/node_modules/@eshaz/web-worker/cjs/browser.js +16 -0
  84. package/node_modules/@eshaz/web-worker/cjs/node.js +219 -0
  85. package/node_modules/@eshaz/web-worker/index.d.ts +4 -0
  86. package/node_modules/@eshaz/web-worker/node.js +223 -0
  87. package/node_modules/@eshaz/web-worker/package.json +54 -0
  88. package/node_modules/@wasm-audio-decoders/common/index.js +5 -0
  89. package/node_modules/@wasm-audio-decoders/common/package.json +36 -0
  90. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +231 -0
  91. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +129 -0
  92. package/node_modules/@wasm-audio-decoders/common/src/puff/README +67 -0
  93. package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +31 -0
  94. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +863 -0
  95. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +35 -0
  96. package/node_modules/@wasm-audio-decoders/common/src/utilities.js +3 -0
  97. package/node_modules/@wasm-audio-decoders/common/types.d.ts +7 -0
  98. package/node_modules/mpg123-decoder/README.md +265 -0
  99. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +185 -0
  100. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +1 -0
  101. package/node_modules/mpg123-decoder/index.js +8 -0
  102. package/node_modules/mpg123-decoder/package.json +58 -0
  103. package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +464 -0
  104. package/node_modules/mpg123-decoder/src/MPEGDecoder.js +200 -0
  105. package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +21 -0
  106. package/node_modules/mpg123-decoder/types.d.ts +30 -0
  107. package/node_modules/silk-wasm/LICENSE +21 -0
  108. package/node_modules/silk-wasm/README.md +85 -0
  109. package/node_modules/silk-wasm/lib/index.cjs +16 -0
  110. package/node_modules/silk-wasm/lib/index.d.ts +70 -0
  111. package/node_modules/silk-wasm/lib/index.mjs +16 -0
  112. package/node_modules/silk-wasm/lib/silk.wasm +0 -0
  113. package/node_modules/silk-wasm/lib/utils.d.ts +4 -0
  114. package/node_modules/silk-wasm/package.json +39 -0
  115. package/node_modules/simple-yenc/.github/FUNDING.yml +1 -0
  116. package/node_modules/simple-yenc/.prettierignore +1 -0
  117. package/node_modules/simple-yenc/LICENSE +7 -0
  118. package/node_modules/simple-yenc/README.md +163 -0
  119. package/node_modules/simple-yenc/dist/esm.js +1 -0
  120. package/node_modules/simple-yenc/dist/index.js +1 -0
  121. package/node_modules/simple-yenc/package.json +50 -0
  122. package/node_modules/simple-yenc/rollup.config.js +27 -0
  123. package/node_modules/simple-yenc/src/simple-yenc.js +302 -0
  124. package/node_modules/ws/LICENSE +20 -0
  125. package/node_modules/ws/README.md +548 -0
  126. package/node_modules/ws/browser.js +8 -0
  127. package/node_modules/ws/index.js +13 -0
  128. package/node_modules/ws/lib/buffer-util.js +131 -0
  129. package/node_modules/ws/lib/constants.js +19 -0
  130. package/node_modules/ws/lib/event-target.js +292 -0
  131. package/node_modules/ws/lib/extension.js +203 -0
  132. package/node_modules/ws/lib/limiter.js +55 -0
  133. package/node_modules/ws/lib/permessage-deflate.js +528 -0
  134. package/node_modules/ws/lib/receiver.js +706 -0
  135. package/node_modules/ws/lib/sender.js +602 -0
  136. package/node_modules/ws/lib/stream.js +161 -0
  137. package/node_modules/ws/lib/subprotocol.js +62 -0
  138. package/node_modules/ws/lib/validation.js +152 -0
  139. package/node_modules/ws/lib/websocket-server.js +554 -0
  140. package/node_modules/ws/lib/websocket.js +1393 -0
  141. package/node_modules/ws/package.json +69 -0
  142. package/node_modules/ws/wrapper.mjs +8 -0
  143. package/openclaw.plugin.json +16 -0
  144. package/package.json +76 -0
  145. package/scripts/cleanup-legacy-plugins.sh +124 -0
  146. package/scripts/proactive-api-server.ts +369 -0
  147. package/scripts/send-proactive.ts +293 -0
  148. package/scripts/set-markdown.sh +156 -0
  149. package/scripts/test-sendmedia.ts +116 -0
  150. package/scripts/upgrade-via-alt-pkg.sh +307 -0
  151. package/scripts/upgrade-via-npm.ps1 +296 -0
  152. package/scripts/upgrade-via-npm.sh +301 -0
  153. package/scripts/upgrade-via-source.sh +774 -0
  154. package/skills/qqbot-channel/SKILL.md +263 -0
  155. package/skills/qqbot-channel/references/api_references.md +521 -0
  156. package/skills/qqbot-media/SKILL.md +56 -0
  157. package/skills/qqbot-remind/SKILL.md +149 -0
  158. package/src/admin-resolver.ts +140 -0
  159. package/src/api.ts +819 -0
  160. package/src/bot-logs-2026-03-21T11-21-47(2).txt +46 -0
  161. package/src/channel.ts +381 -0
  162. package/src/config.ts +187 -0
  163. package/src/credential-backup.ts +72 -0
  164. package/src/gateway.log +43 -0
  165. package/src/gateway.ts +1404 -0
  166. package/src/image-server.ts +539 -0
  167. package/src/inbound-attachments.ts +304 -0
  168. package/src/known-users.ts +353 -0
  169. package/src/message-queue.ts +169 -0
  170. package/src/onboarding.ts +274 -0
  171. package/src/openclaw-2026-03-21.log +3729 -0
  172. package/src/openclaw-plugin-sdk.d.ts +522 -0
  173. package/src/outbound-deliver.ts +552 -0
  174. package/src/outbound.ts +1266 -0
  175. package/src/proactive.ts +530 -0
  176. package/src/ref-index-store.ts +357 -0
  177. package/src/reply-dispatcher.ts +334 -0
  178. package/src/runtime.ts +14 -0
  179. package/src/session-store.ts +303 -0
  180. package/src/slash-commands.ts +1305 -0
  181. package/src/startup-greeting.ts +98 -0
  182. package/src/stt.ts +86 -0
  183. package/src/tools/channel.ts +281 -0
  184. package/src/tools/remind.ts +296 -0
  185. package/src/types.ts +183 -0
  186. package/src/typing-keepalive.ts +59 -0
  187. package/src/update-checker.ts +179 -0
  188. package/src/user-messages.ts +7 -0
  189. package/src/utils/audio-convert.ts +803 -0
  190. package/src/utils/file-utils.ts +167 -0
  191. package/src/utils/image-size.ts +266 -0
  192. package/src/utils/media-tags.ts +182 -0
  193. package/src/utils/payload.ts +265 -0
  194. package/src/utils/platform.ts +435 -0
  195. package/src/utils/text-parsing.ts +82 -0
  196. package/src/utils/upload-cache.ts +128 -0
  197. package/tsconfig.json +16 -0
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env npx ts-node
2
+ /**
3
+ * QQBot 主动消息 CLI 工具
4
+ *
5
+ * 使用示例:
6
+ * # 发送私聊消息
7
+ * npx ts-node scripts/send-proactive.ts --to "用户openid" --text "你好!"
8
+ *
9
+ * # 发送群聊消息
10
+ * npx ts-node scripts/send-proactive.ts --to "群组openid" --type group --text "群公告"
11
+ *
12
+ * # 列出已知用户
13
+ * npx ts-node scripts/send-proactive.ts --list
14
+ *
15
+ * # 列出群聊用户
16
+ * npx ts-node scripts/send-proactive.ts --list --type group
17
+ *
18
+ * # 广播消息
19
+ * npx ts-node scripts/send-proactive.ts --broadcast --text "系统公告" --type c2c --limit 10
20
+ */
21
+
22
+ import {
23
+ sendProactiveMessageDirect,
24
+ listKnownUsers,
25
+ getKnownUsersStats,
26
+ broadcastMessage,
27
+ } from "../src/proactive.js";
28
+ import type { ResolvedQQBotAccount } from "../src/types.js";
29
+ import * as fs from "node:fs";
30
+ import * as path from "node:path";
31
+
32
+ // 解析命令行参数
33
+ function parseArgs(): Record<string, string | boolean> {
34
+ const args: Record<string, string | boolean> = {};
35
+ const argv = process.argv.slice(2);
36
+
37
+ for (let i = 0; i < argv.length; i++) {
38
+ const arg = argv[i];
39
+ if (arg.startsWith("--")) {
40
+ const key = arg.slice(2);
41
+ const nextArg = argv[i + 1];
42
+ if (nextArg && !nextArg.startsWith("--")) {
43
+ args[key] = nextArg;
44
+ i++;
45
+ } else {
46
+ args[key] = true;
47
+ }
48
+ }
49
+ }
50
+
51
+ return args;
52
+ }
53
+
54
+ function normalizeAppId(raw: unknown): string {
55
+ if (raw === null || raw === undefined) return "";
56
+ return String(raw).trim();
57
+ }
58
+
59
+ function detectConfigPath(): string | null {
60
+ const home = process.env.HOME || "/home/ubuntu";
61
+ for (const app of ["openclaw", "clawdbot", "moltbot"]) {
62
+ const p = path.join(home, `.${app}`, `${app}.json`);
63
+ if (fs.existsSync(p)) return p;
64
+ }
65
+ return null;
66
+ }
67
+
68
+ // 从配置文件加载账户信息
69
+ function loadAccount(accountId = "default"): ResolvedQQBotAccount | null {
70
+ const configPath = detectConfigPath();
71
+
72
+ try {
73
+ if (!configPath || !fs.existsSync(configPath)) {
74
+ // 尝试从环境变量获取
75
+ const appId = process.env.QQBOT_APP_ID;
76
+ const clientSecret = process.env.QQBOT_CLIENT_SECRET;
77
+
78
+ if (appId && clientSecret) {
79
+ return {
80
+ accountId,
81
+ appId: normalizeAppId(appId),
82
+ clientSecret,
83
+ enabled: true,
84
+ secretSource: "env",
85
+ markdownSupport: true,
86
+ config: {},
87
+ };
88
+ }
89
+
90
+ console.error("配置文件不存在且环境变量未设置");
91
+ return null;
92
+ }
93
+
94
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
95
+ const qqbot = config.channels?.qqbot;
96
+
97
+ if (!qqbot) {
98
+ console.error("配置中没有 qqbot 配置");
99
+ return null;
100
+ }
101
+
102
+ // 解析账户配置
103
+ if (accountId === "default") {
104
+ return {
105
+ accountId: "default",
106
+ appId: normalizeAppId(qqbot.appId ?? process.env.QQBOT_APP_ID),
107
+ clientSecret: qqbot.clientSecret || process.env.QQBOT_CLIENT_SECRET,
108
+ enabled: qqbot.enabled ?? true,
109
+ secretSource: qqbot.clientSecret ? "config" : "env",
110
+ markdownSupport: qqbot.markdownSupport ?? true,
111
+ config: qqbot,
112
+ };
113
+ }
114
+
115
+ const accountConfig = qqbot.accounts?.[accountId];
116
+ if (accountConfig) {
117
+ return {
118
+ accountId,
119
+ appId: normalizeAppId(accountConfig.appId ?? qqbot.appId ?? process.env.QQBOT_APP_ID),
120
+ clientSecret: accountConfig.clientSecret || qqbot.clientSecret || process.env.QQBOT_CLIENT_SECRET,
121
+ enabled: accountConfig.enabled ?? true,
122
+ secretSource: accountConfig.clientSecret ? "config" : "env",
123
+ markdownSupport: accountConfig.markdownSupport ?? qqbot.markdownSupport ?? true,
124
+ config: accountConfig,
125
+ };
126
+ }
127
+
128
+ console.error(`账户 ${accountId} 不存在`);
129
+ return null;
130
+ } catch (err) {
131
+ console.error(`加载配置失败: ${err}`);
132
+ return null;
133
+ }
134
+ }
135
+
136
+ async function main() {
137
+ const args = parseArgs();
138
+
139
+ // 显示帮助
140
+ if (args.help || args.h) {
141
+ console.log(`
142
+ QQBot 主动消息 CLI 工具
143
+
144
+ 用法:
145
+ npx ts-node scripts/send-proactive.ts [选项]
146
+
147
+ 选项:
148
+ --to <openid> 目标用户或群组的 openid
149
+ --text <message> 要发送的消息内容
150
+ --type <type> 消息类型: c2c (私聊) 或 group (群聊),默认 c2c
151
+ --account <id> 账户 ID,默认 default
152
+
153
+ --list 列出已知用户
154
+ --stats 显示用户统计
155
+ --broadcast 广播消息给所有已知用户
156
+ --limit <n> 限制数量
157
+
158
+ --help, -h 显示帮助
159
+
160
+ 示例:
161
+ # 发送私聊消息
162
+ npx ts-node scripts/send-proactive.ts --to "0Eda5EA7-xxx" --text "你好!"
163
+
164
+ # 发送群聊消息
165
+ npx ts-node scripts/send-proactive.ts --to "A1B2C3D4" --type group --text "群公告"
166
+
167
+ # 列出最近 10 个私聊用户
168
+ npx ts-node scripts/send-proactive.ts --list --type c2c --limit 10
169
+
170
+ # 广播消息
171
+ npx ts-node scripts/send-proactive.ts --broadcast --text "系统公告" --limit 5
172
+ `);
173
+ return;
174
+ }
175
+
176
+ const accountId = (args.account as string) || "default";
177
+ const type = (args.type as "c2c" | "group") || "c2c";
178
+ const limit = args.limit ? parseInt(args.limit as string, 10) : undefined;
179
+
180
+ // 列出已知用户
181
+ if (args.list) {
182
+ const users = listKnownUsers({
183
+ type: args.type as "c2c" | "group" | "channel" | undefined,
184
+ accountId: args.account as string | undefined,
185
+ limit,
186
+ });
187
+
188
+ if (users.length === 0) {
189
+ console.log("没有已知用户");
190
+ return;
191
+ }
192
+
193
+ console.log(`\n已知用户列表 (共 ${users.length} 个):\n`);
194
+ console.log("类型\t\tOpenID\t\t\t\t\t\t昵称\t\t最后交互时间");
195
+ console.log("─".repeat(100));
196
+
197
+ for (const user of users) {
198
+ const lastTime = new Date(user.lastInteractionAt).toLocaleString();
199
+ console.log(`${user.type}\t\t${user.openid.slice(0, 20)}...\t${user.nickname || "-"}\t\t${lastTime}`);
200
+ }
201
+ return;
202
+ }
203
+
204
+ // 显示统计
205
+ if (args.stats) {
206
+ const stats = getKnownUsersStats(args.account as string | undefined);
207
+ console.log(`\n用户统计:`);
208
+ console.log(` 总计: ${stats.total}`);
209
+ console.log(` 私聊: ${stats.c2c}`);
210
+ console.log(` 群聊: ${stats.group}`);
211
+ console.log(` 频道: ${stats.channel}`);
212
+ return;
213
+ }
214
+
215
+ // 广播消息
216
+ if (args.broadcast) {
217
+ if (!args.text) {
218
+ console.error("请指定消息内容 (--text)");
219
+ process.exit(1);
220
+ }
221
+
222
+ // 加载配置用于广播
223
+ const configPath = detectConfigPath();
224
+ let cfg: Record<string, unknown> = {};
225
+ try {
226
+ if (configPath && fs.existsSync(configPath)) {
227
+ cfg = JSON.parse(fs.readFileSync(configPath, "utf-8"));
228
+ }
229
+ } catch {}
230
+
231
+ console.log(`\n开始广播消息...\n`);
232
+ const result = await broadcastMessage(args.text as string, cfg as any, {
233
+ type,
234
+ accountId,
235
+ limit,
236
+ });
237
+
238
+ console.log(`\n广播完成:`);
239
+ console.log(` 发送总数: ${result.total}`);
240
+ console.log(` 成功: ${result.success}`);
241
+ console.log(` 失败: ${result.failed}`);
242
+
243
+ if (result.failed > 0) {
244
+ console.log(`\n失败详情:`);
245
+ for (const r of result.results) {
246
+ if (!r.result.success) {
247
+ console.log(` ${r.to}: ${r.result.error}`);
248
+ }
249
+ }
250
+ }
251
+ return;
252
+ }
253
+
254
+ // 发送单条消息
255
+ if (args.to && args.text) {
256
+ const account = loadAccount(accountId);
257
+ if (!account) {
258
+ console.error("无法加载账户配置");
259
+ process.exit(1);
260
+ }
261
+
262
+ console.log(`\n发送消息...`);
263
+ console.log(` 目标: ${args.to}`);
264
+ console.log(` 类型: ${type}`);
265
+ console.log(` 内容: ${args.text}`);
266
+
267
+ const result = await sendProactiveMessageDirect(
268
+ account,
269
+ args.to as string,
270
+ args.text as string,
271
+ type
272
+ );
273
+
274
+ if (result.success) {
275
+ console.log(`\n✅ 发送成功!`);
276
+ console.log(` 消息ID: ${result.messageId}`);
277
+ console.log(` 时间戳: ${result.timestamp}`);
278
+ } else {
279
+ console.log(`\n❌ 发送失败: ${result.error}`);
280
+ process.exit(1);
281
+ }
282
+ return;
283
+ }
284
+
285
+ // 没有有效参数
286
+ console.error("请指定操作。使用 --help 查看帮助。");
287
+ process.exit(1);
288
+ }
289
+
290
+ main().catch((err) => {
291
+ console.error(`执行失败: ${err}`);
292
+ process.exit(1);
293
+ });
@@ -0,0 +1,156 @@
1
+ #!/bin/bash
2
+
3
+ # qqbot markdown 配置脚本
4
+ # 用于单独设置是否启用 markdown 消息格式
5
+ # 直接编辑 JSON 配置文件,避免框架验证拒绝未注册的 channel
6
+
7
+ set -e
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+ cd "$SCRIPT_DIR"
11
+
12
+ # 自动检测 CLI 配置文件(兼容 openclaw / clawdbot / moltbot)
13
+ OPENCLAW_CONFIG=""
14
+ for app in openclaw clawdbot moltbot; do
15
+ cfg="$HOME/.$app/$app.json"
16
+ if [ -f "$cfg" ]; then
17
+ OPENCLAW_CONFIG="$cfg"
18
+ break
19
+ fi
20
+ done
21
+
22
+ if [ -z "$OPENCLAW_CONFIG" ]; then
23
+ echo "❌ 未找到 openclaw / clawdbot / moltbot 配置文件"
24
+ echo " 请先运行 openclaw onboard 初始化配置"
25
+ exit 1
26
+ fi
27
+
28
+ show_help() {
29
+ echo "用法: $0 [选项]"
30
+ echo ""
31
+ echo "选项:"
32
+ echo " enable, on, yes 启用 markdown 消息格式"
33
+ echo " disable, off, no 禁用 markdown 消息格式(使用纯文本)"
34
+ echo " status 显示当前 markdown 配置状态"
35
+ echo " -h, --help 显示帮助信息"
36
+ echo ""
37
+ echo "示例:"
38
+ echo " $0 enable 启用 markdown"
39
+ echo " $0 disable 禁用 markdown"
40
+ echo " $0 status 查看当前状态"
41
+ echo " $0 交互式选择"
42
+ echo ""
43
+ echo "⚠️ 注意: 启用 markdown 需要在 QQ 开放平台申请 markdown 消息权限"
44
+ echo " 如果没有权限,消息将无法正常发送!"
45
+ }
46
+
47
+ set_markdown_value() {
48
+ local value="$1"
49
+ node -e "
50
+ const fs = require('fs');
51
+ const cfg = JSON.parse(fs.readFileSync('$OPENCLAW_CONFIG', 'utf-8'));
52
+ if (!cfg.channels) cfg.channels = {};
53
+ if (!cfg.channels.qqbot) cfg.channels.qqbot = {};
54
+ cfg.channels.qqbot.markdownSupport = $value;
55
+ fs.writeFileSync('$OPENCLAW_CONFIG', JSON.stringify(cfg, null, 4) + '\n');
56
+ "
57
+ }
58
+
59
+ enable_markdown() {
60
+ echo "✅ 启用 markdown 消息格式..."
61
+ set_markdown_value true
62
+ echo ""
63
+ echo "markdown 已启用。"
64
+ echo "⚠️ 请确保您已在 QQ 开放平台申请了 markdown 消息权限。"
65
+ }
66
+
67
+ disable_markdown() {
68
+ echo "❌ 禁用 markdown 消息格式(使用纯文本)..."
69
+ set_markdown_value false
70
+ echo ""
71
+ echo "markdown 已禁用,将使用纯文本格式发送消息。"
72
+ }
73
+
74
+ show_status() {
75
+ echo "当前 markdown 配置状态:"
76
+ echo " 配置文件: $OPENCLAW_CONFIG"
77
+ echo ""
78
+ current=$(node -e "
79
+ const cfg = JSON.parse(require('fs').readFileSync('$OPENCLAW_CONFIG', 'utf-8'));
80
+ console.log(cfg.channels?.qqbot?.markdownSupport ?? '未设置');
81
+ " 2>/dev/null || echo "未设置")
82
+ if [ "$current" = "true" ]; then
83
+ echo " 状态: ✅ 已启用"
84
+ echo ""
85
+ echo " ⚠️ 请确保您已在 QQ 开放平台申请了 markdown 消息权限。"
86
+ elif [ "$current" = "false" ]; then
87
+ echo " 状态: ❌ 已禁用(纯文本模式)"
88
+ else
89
+ echo " 状态: 未设置(默认: 禁用)"
90
+ fi
91
+ }
92
+
93
+ interactive_select() {
94
+ echo "========================================="
95
+ echo " qqbot markdown 配置"
96
+ echo "========================================="
97
+ echo ""
98
+ show_status
99
+ echo ""
100
+ echo "-----------------------------------------"
101
+ echo ""
102
+ echo "是否启用 markdown 消息格式?"
103
+ echo ""
104
+ echo "⚠️ 注意: 启用 markdown 需要在 QQ 开放平台申请 markdown 消息权限"
105
+ echo " 如果没有权限,消息将无法正常发送!"
106
+ echo ""
107
+ echo " 1) 启用 markdown"
108
+ echo " 2) 禁用 markdown(纯文本)"
109
+ echo " 3) 取消"
110
+ echo ""
111
+ read -t 10 -p "请选择 [1-3] (默认: 2): " choice || choice="2"
112
+
113
+ case "$choice" in
114
+ 1)
115
+ echo ""
116
+ enable_markdown
117
+ ;;
118
+ 2|"")
119
+ echo ""
120
+ disable_markdown
121
+ ;;
122
+ 3)
123
+ echo "已取消。"
124
+ exit 0
125
+ ;;
126
+ *)
127
+ echo "无效选择,已取消。"
128
+ exit 1
129
+ ;;
130
+ esac
131
+ }
132
+
133
+ # 主逻辑
134
+ case "${1:-}" in
135
+ enable|on|yes)
136
+ enable_markdown
137
+ ;;
138
+ disable|off|no)
139
+ disable_markdown
140
+ ;;
141
+ status)
142
+ show_status
143
+ ;;
144
+ -h|--help)
145
+ show_help
146
+ ;;
147
+ "")
148
+ interactive_select
149
+ ;;
150
+ *)
151
+ echo "未知选项: $1"
152
+ echo ""
153
+ show_help
154
+ exit 1
155
+ ;;
156
+ esac
@@ -0,0 +1,116 @@
1
+ /**
2
+ * 测试 sendMedia 路径:语音、视频、文件
3
+ * 用法:npx tsx scripts/test-sendmedia.ts <openid>
4
+ */
5
+ import { sendMedia } from "../src/outbound.js";
6
+ import type { ResolvedQQBotAccount } from "../src/types.js";
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
9
+
10
+ const LOG_FILE = "/tmp/test-sendmedia-output.log";
11
+
12
+ function log(msg: string) {
13
+ const line = msg + "\n";
14
+ process.stdout.write(line);
15
+ fs.appendFileSync(LOG_FILE, line);
16
+ }
17
+
18
+ function normalizeAppId(raw: unknown): string {
19
+ if (raw === null || raw === undefined) return "";
20
+ return String(raw).trim();
21
+ }
22
+
23
+ function detectConfigPath(): string | null {
24
+ const home = process.env.HOME || "/home/ubuntu";
25
+ for (const app of ["openclaw", "clawdbot", "moltbot"]) {
26
+ const p = path.join(home, `.${app}`, `${app}.json`);
27
+ if (fs.existsSync(p)) return p;
28
+ }
29
+ return null;
30
+ }
31
+
32
+ function loadAccount(): ResolvedQQBotAccount | null {
33
+ const configPath = detectConfigPath();
34
+ try {
35
+ if (!configPath || !fs.existsSync(configPath)) {
36
+ const appId = process.env.QQBOT_APP_ID;
37
+ const clientSecret = process.env.QQBOT_CLIENT_SECRET;
38
+ if (appId && clientSecret) {
39
+ return { accountId: "default", appId: normalizeAppId(appId), clientSecret, enabled: true, secretSource: "env", markdownSupport: true, config: {} };
40
+ }
41
+ return null;
42
+ }
43
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
44
+ const qqbot = config.channels?.qqbot;
45
+ if (!qqbot) return null;
46
+ return {
47
+ accountId: "default",
48
+ appId: normalizeAppId(qqbot.appId ?? process.env.QQBOT_APP_ID),
49
+ clientSecret: qqbot.clientSecret || process.env.QQBOT_CLIENT_SECRET,
50
+ enabled: qqbot.enabled ?? true,
51
+ secretSource: qqbot.clientSecret ? "config" as const : "env" as const,
52
+ markdownSupport: qqbot.markdownSupport ?? true,
53
+ config: qqbot,
54
+ };
55
+ } catch (err) {
56
+ return null;
57
+ }
58
+ }
59
+
60
+ function sleep(ms: number) { return new Promise(r => setTimeout(r, ms)); }
61
+
62
+ async function main() {
63
+ // 清空日志
64
+ fs.writeFileSync(LOG_FILE, "");
65
+
66
+ const openid = process.argv[2];
67
+ if (!openid) { log("用法: npx tsx scripts/test-sendmedia.ts <openid>"); process.exit(1); }
68
+
69
+ const account = loadAccount();
70
+ if (!account) { log("无法加载账户配置"); process.exit(1); }
71
+
72
+ const to = `c2c:${openid}`;
73
+ log(`目标: ${to}\n`);
74
+
75
+ // ===== 1. 语音 =====
76
+ log("==================================================");
77
+ log("TEST 1: 语音 (本地 WAV 文件)");
78
+ log("==================================================");
79
+ const wavPath = "/tmp/test-voice.wav";
80
+ if (fs.existsSync(wavPath)) {
81
+ const r1 = await sendMedia({ to, text: "测试语音 sendMedia", mediaUrl: wavPath, account });
82
+ log("结果: " + JSON.stringify(r1, null, 2));
83
+ } else {
84
+ log("跳过: /tmp/test-voice.wav 不存在");
85
+ }
86
+
87
+ await sleep(2000);
88
+
89
+ // ===== 2. 视频 =====
90
+ log("\n==================================================");
91
+ log("TEST 2: 视频 (公网 MP4 URL)");
92
+ log("==================================================");
93
+ const videoUrl = "https://www.w3schools.com/html/mov_bbb.mp4";
94
+ const r2 = await sendMedia({ to, text: "测试视频 sendMedia", mediaUrl: videoUrl, account });
95
+ log("结果: " + JSON.stringify(r2, null, 2));
96
+
97
+ await sleep(2000);
98
+
99
+ // ===== 3. 文件 =====
100
+ log("\n==================================================");
101
+ log("TEST 3: 文件 (本地 TXT 文件)");
102
+ log("==================================================");
103
+ const txtPath = "/tmp/test-doc.txt";
104
+ if (fs.existsSync(txtPath)) {
105
+ const r3 = await sendMedia({ to, text: "测试文件 sendMedia", mediaUrl: txtPath, account });
106
+ log("结果: " + JSON.stringify(r3, null, 2));
107
+ } else {
108
+ log("跳过: /tmp/test-doc.txt 不存在");
109
+ }
110
+
111
+ log("\n==================================================");
112
+ log("全部测试完成");
113
+ log("==================================================");
114
+ }
115
+
116
+ main().catch(err => { log("脚本异常: " + err); process.exit(1); });