@tencent-connect/openclaw-qqbot 1.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +393 -0
  3. package/README.zh.md +390 -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 +22 -0
  8. package/dist/src/api.d.ts +138 -0
  9. package/dist/src/api.js +523 -0
  10. package/dist/src/channel.d.ts +3 -0
  11. package/dist/src/channel.js +337 -0
  12. package/dist/src/config.d.ts +25 -0
  13. package/dist/src/config.js +156 -0
  14. package/dist/src/gateway.d.ts +18 -0
  15. package/dist/src/gateway.js +2315 -0
  16. package/dist/src/image-server.d.ts +62 -0
  17. package/dist/src/image-server.js +401 -0
  18. package/dist/src/known-users.d.ts +100 -0
  19. package/dist/src/known-users.js +263 -0
  20. package/dist/src/onboarding.d.ts +10 -0
  21. package/dist/src/onboarding.js +203 -0
  22. package/dist/src/outbound.d.ts +150 -0
  23. package/dist/src/outbound.js +1175 -0
  24. package/dist/src/proactive.d.ts +170 -0
  25. package/dist/src/proactive.js +399 -0
  26. package/dist/src/runtime.d.ts +3 -0
  27. package/dist/src/runtime.js +10 -0
  28. package/dist/src/session-store.d.ts +52 -0
  29. package/dist/src/session-store.js +254 -0
  30. package/dist/src/types.d.ts +145 -0
  31. package/dist/src/types.js +1 -0
  32. package/dist/src/utils/audio-convert.d.ts +73 -0
  33. package/dist/src/utils/audio-convert.js +645 -0
  34. package/dist/src/utils/file-utils.d.ts +46 -0
  35. package/dist/src/utils/file-utils.js +107 -0
  36. package/dist/src/utils/image-size.d.ts +51 -0
  37. package/dist/src/utils/image-size.js +234 -0
  38. package/dist/src/utils/media-tags.d.ts +14 -0
  39. package/dist/src/utils/media-tags.js +120 -0
  40. package/dist/src/utils/payload.d.ts +112 -0
  41. package/dist/src/utils/payload.js +186 -0
  42. package/dist/src/utils/platform.d.ts +126 -0
  43. package/dist/src/utils/platform.js +358 -0
  44. package/dist/src/utils/upload-cache.d.ts +34 -0
  45. package/dist/src/utils/upload-cache.js +93 -0
  46. package/index.ts +27 -0
  47. package/moltbot.plugin.json +16 -0
  48. package/node_modules/@eshaz/web-worker/LICENSE +201 -0
  49. package/node_modules/@eshaz/web-worker/README.md +134 -0
  50. package/node_modules/@eshaz/web-worker/browser.js +17 -0
  51. package/node_modules/@eshaz/web-worker/cjs/browser.js +16 -0
  52. package/node_modules/@eshaz/web-worker/cjs/node.js +219 -0
  53. package/node_modules/@eshaz/web-worker/index.d.ts +4 -0
  54. package/node_modules/@eshaz/web-worker/node.js +223 -0
  55. package/node_modules/@eshaz/web-worker/package.json +54 -0
  56. package/node_modules/@wasm-audio-decoders/common/index.js +5 -0
  57. package/node_modules/@wasm-audio-decoders/common/package.json +36 -0
  58. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +231 -0
  59. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +129 -0
  60. package/node_modules/@wasm-audio-decoders/common/src/puff/README +67 -0
  61. package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +31 -0
  62. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +863 -0
  63. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +35 -0
  64. package/node_modules/@wasm-audio-decoders/common/src/utilities.js +3 -0
  65. package/node_modules/@wasm-audio-decoders/common/types.d.ts +7 -0
  66. package/node_modules/mpg123-decoder/README.md +265 -0
  67. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +185 -0
  68. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +1 -0
  69. package/node_modules/mpg123-decoder/index.js +8 -0
  70. package/node_modules/mpg123-decoder/package.json +58 -0
  71. package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +464 -0
  72. package/node_modules/mpg123-decoder/src/MPEGDecoder.js +200 -0
  73. package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +21 -0
  74. package/node_modules/mpg123-decoder/types.d.ts +30 -0
  75. package/node_modules/silk-wasm/LICENSE +21 -0
  76. package/node_modules/silk-wasm/README.md +85 -0
  77. package/node_modules/silk-wasm/lib/index.cjs +16 -0
  78. package/node_modules/silk-wasm/lib/index.d.ts +70 -0
  79. package/node_modules/silk-wasm/lib/index.mjs +16 -0
  80. package/node_modules/silk-wasm/lib/silk.wasm +0 -0
  81. package/node_modules/silk-wasm/lib/utils.d.ts +4 -0
  82. package/node_modules/silk-wasm/package.json +39 -0
  83. package/node_modules/simple-yenc/.github/FUNDING.yml +1 -0
  84. package/node_modules/simple-yenc/.prettierignore +1 -0
  85. package/node_modules/simple-yenc/LICENSE +7 -0
  86. package/node_modules/simple-yenc/README.md +163 -0
  87. package/node_modules/simple-yenc/dist/esm.js +1 -0
  88. package/node_modules/simple-yenc/dist/index.js +1 -0
  89. package/node_modules/simple-yenc/package.json +50 -0
  90. package/node_modules/simple-yenc/rollup.config.js +27 -0
  91. package/node_modules/simple-yenc/src/simple-yenc.js +302 -0
  92. package/node_modules/ws/LICENSE +20 -0
  93. package/node_modules/ws/README.md +548 -0
  94. package/node_modules/ws/browser.js +8 -0
  95. package/node_modules/ws/index.js +13 -0
  96. package/node_modules/ws/lib/buffer-util.js +131 -0
  97. package/node_modules/ws/lib/constants.js +19 -0
  98. package/node_modules/ws/lib/event-target.js +292 -0
  99. package/node_modules/ws/lib/extension.js +203 -0
  100. package/node_modules/ws/lib/limiter.js +55 -0
  101. package/node_modules/ws/lib/permessage-deflate.js +528 -0
  102. package/node_modules/ws/lib/receiver.js +706 -0
  103. package/node_modules/ws/lib/sender.js +602 -0
  104. package/node_modules/ws/lib/stream.js +161 -0
  105. package/node_modules/ws/lib/subprotocol.js +62 -0
  106. package/node_modules/ws/lib/validation.js +152 -0
  107. package/node_modules/ws/lib/websocket-server.js +554 -0
  108. package/node_modules/ws/lib/websocket.js +1393 -0
  109. package/node_modules/ws/package.json +69 -0
  110. package/node_modules/ws/wrapper.mjs +8 -0
  111. package/openclaw.plugin.json +16 -0
  112. package/package.json +76 -0
  113. package/scripts/proactive-api-server.ts +356 -0
  114. package/scripts/pull-latest.sh +316 -0
  115. package/scripts/send-proactive.ts +273 -0
  116. package/scripts/set-markdown.sh +156 -0
  117. package/scripts/upgrade-and-run.sh +525 -0
  118. package/scripts/upgrade.sh +127 -0
  119. package/skills/qqbot-cron/SKILL.md +513 -0
  120. package/skills/qqbot-media/SKILL.md +194 -0
  121. package/src/api.ts +704 -0
  122. package/src/channel.ts +368 -0
  123. package/src/config.ts +182 -0
  124. package/src/gateway.ts +2459 -0
  125. package/src/image-server.ts +474 -0
  126. package/src/known-users.ts +353 -0
  127. package/src/onboarding.ts +274 -0
  128. package/src/openclaw-plugin-sdk.d.ts +483 -0
  129. package/src/outbound.ts +1301 -0
  130. package/src/proactive.ts +530 -0
  131. package/src/runtime.ts +14 -0
  132. package/src/session-store.ts +303 -0
  133. package/src/types.ts +153 -0
  134. package/src/utils/audio-convert.ts +738 -0
  135. package/src/utils/file-utils.ts +122 -0
  136. package/src/utils/image-size.ts +266 -0
  137. package/src/utils/media-tags.ts +134 -0
  138. package/src/utils/payload.ts +265 -0
  139. package/src/utils/platform.ts +404 -0
  140. package/src/utils/upload-cache.ts +128 -0
  141. package/tsconfig.json +16 -0
@@ -0,0 +1,316 @@
1
+ #!/bin/bash
2
+
3
+ # QQBot 拉取最新源码并更新
4
+ # 从 GitHub 拉取最新代码,安装依赖并重启网关
5
+ # 兼容 clawdbot / openclaw / moltbot,macOS 开箱即用
6
+ #
7
+ # 用法:
8
+ # pull-latest.sh # 拉取最新代码并更新
9
+ # pull-latest.sh --branch main # 指定分支(默认 main)
10
+ # pull-latest.sh --force # 跳过交互,强制更新
11
+ # pull-latest.sh --repo <git-url> # 指定仓库地址
12
+
13
+ set -euo pipefail
14
+
15
+ ##############################################################################
16
+ # 常量 & 参数
17
+ ##############################################################################
18
+ readonly DEFAULT_REPO="https://github.com/tencent-connect/openclaw-qqbot.git"
19
+ readonly GATEWAY_PORT=18789
20
+ readonly SUPPORTED_CLIS=(openclaw clawdbot moltbot)
21
+
22
+ FORCE=false
23
+ BRANCH="main"
24
+ REPO_URL=""
25
+
26
+ while [[ $# -gt 0 ]]; do
27
+ case "$1" in
28
+ -f|--force) FORCE=true; shift ;;
29
+ -b|--branch) BRANCH="$2"; shift 2 ;;
30
+ --repo) REPO_URL="$2"; shift 2 ;;
31
+ -h|--help)
32
+ echo "QQBot 拉取最新源码并更新"
33
+ echo ""
34
+ echo "用法:"
35
+ echo " pull-latest.sh # 拉取最新代码并更新"
36
+ echo " pull-latest.sh --branch main # 指定分支(默认 main)"
37
+ echo " pull-latest.sh --force # 跳过交互,强制更新"
38
+ echo " pull-latest.sh --repo <git-url> # 指定仓库地址"
39
+ exit 0
40
+ ;;
41
+ *)
42
+ echo "未知选项: $1 (使用 --help 查看帮助)"
43
+ exit 1
44
+ ;;
45
+ esac
46
+ done
47
+ REPO_URL="${REPO_URL:-$DEFAULT_REPO}"
48
+
49
+ ##############################################################################
50
+ # 工具函数
51
+ ##############################################################################
52
+ json_get() {
53
+ local file="$1" expr="$2"
54
+ node -e "process.stdout.write(String((function(){$expr})(JSON.parse(require('fs').readFileSync('$file','utf8')))||''))" 2>/dev/null || true
55
+ }
56
+
57
+ ##############################################################################
58
+ # 环境检查
59
+ ##############################################################################
60
+ printf "%b\n" "\033[32m=========================================\033[0m"
61
+ printf "%b\n" "\033[32m QQBot 拉取最新源码并更新\033[0m"
62
+ printf "%b\n" "\033[32m=========================================\033[0m"
63
+ echo ""
64
+
65
+ # 检查必要命令
66
+ for dep in node npm git; do
67
+ if ! command -v "$dep" &>/dev/null; then
68
+ printf "%b\n" "\033[31m❌ 未检测到 $dep,请先安装\033[0m"
69
+ exit 1
70
+ fi
71
+ done
72
+
73
+ printf "%b\n" "\033[34m系统信息:\033[0m"
74
+ echo " macOS $(sw_vers -productVersion 2>/dev/null || echo '未知')"
75
+ echo " Node $(node -v)"
76
+ echo " npm $(npm -v)"
77
+ echo " Git $(git --version 2>/dev/null | awk '{print $3}')"
78
+ echo " 仓库 $REPO_URL"
79
+ echo " 分支 $BRANCH"
80
+
81
+ # 检测 CLI
82
+ CMD=""
83
+ for name in "${SUPPORTED_CLIS[@]}"; do
84
+ if command -v "$name" &>/dev/null; then
85
+ CMD="$name"
86
+ break
87
+ fi
88
+ done
89
+ if [ -z "$CMD" ]; then
90
+ printf "%b\n" "\033[31m❌ 未找到 openclaw / clawdbot / moltbot 命令,请先安装其中之一\033[0m"
91
+ exit 1
92
+ fi
93
+ echo " CLI $CMD ($($CMD --version 2>/dev/null || echo '未知版本'))"
94
+
95
+ APP_HOME="$HOME/.$CMD"
96
+ APP_CONFIG="$APP_HOME/$CMD.json"
97
+
98
+ ##############################################################################
99
+ # 定位插件目录
100
+ ##############################################################################
101
+ PROJ_DIR=""
102
+ FRESH_INSTALL=false
103
+
104
+ for app in "${SUPPORTED_CLIS[@]}"; do
105
+ for plugin_dir in openclaw-qqbot qqbot openclaw-qq; do
106
+ ext_dir="$HOME/.$app/extensions/$plugin_dir"
107
+ if [ -d "$ext_dir" ] && [ -f "$ext_dir/package.json" ]; then
108
+ PROJ_DIR="$ext_dir"
109
+ break 2
110
+ fi
111
+ done
112
+ done
113
+
114
+ if [ -z "$PROJ_DIR" ]; then
115
+ PROJ_DIR="$APP_HOME/extensions/openclaw-qqbot"
116
+ FRESH_INSTALL=true
117
+ echo " 插件 未安装(首次安装)"
118
+ else
119
+ echo " 插件 $PROJ_DIR"
120
+ fi
121
+
122
+ ##############################################################################
123
+ # 第一步:获取当前版本
124
+ ##############################################################################
125
+ echo ""
126
+ printf "%b\n" "\033[34m1. 获取当前版本...\033[0m"
127
+
128
+ LOCAL_VER=""
129
+ LOCAL_COMMIT=""
130
+ if [ "$FRESH_INSTALL" = true ]; then
131
+ echo " 首次安装,无本地版本"
132
+ else
133
+ [ -f "$PROJ_DIR/package.json" ] && LOCAL_VER=$(json_get "$PROJ_DIR/package.json" "c => c.version")
134
+ [ -d "$PROJ_DIR/.git" ] && LOCAL_COMMIT=$(cd "$PROJ_DIR" && git rev-parse --short HEAD 2>/dev/null || echo "")
135
+ echo " 当前版本: ${LOCAL_VER:-未知}${LOCAL_COMMIT:+ (${LOCAL_COMMIT})}"
136
+ fi
137
+
138
+ ##############################################################################
139
+ # 第二步:备份通道配置
140
+ ##############################################################################
141
+ echo ""
142
+ printf "%b\n" "\033[34m2. 备份通道配置...\033[0m"
143
+
144
+ SAVED_CHANNELS_JSON=""
145
+ for app in "${SUPPORTED_CLIS[@]}"; do
146
+ cfg="$HOME/.$app/$app.json"
147
+ [ -f "$cfg" ] || continue
148
+ SAVED_CHANNELS_JSON=$(node -e "
149
+ const cfg = JSON.parse(require('fs').readFileSync('$cfg', 'utf8'));
150
+ // 尝试所有可能的 channel key(原仓库 + 本仓库)
151
+ const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
152
+ for (const key of keys) {
153
+ const ch = cfg.channels && cfg.channels[key];
154
+ if (ch) { process.stdout.write(JSON.stringify(ch)); process.exit(0); }
155
+ }
156
+ " 2>/dev/null || true)
157
+ [ -n "$SAVED_CHANNELS_JSON" ] && break
158
+ done
159
+
160
+ if [ -n "$SAVED_CHANNELS_JSON" ]; then
161
+ echo " ✅ 已备份 qqbot 通道配置"
162
+ else
163
+ echo " ℹ️ 未找到已有通道配置"
164
+ fi
165
+
166
+ ##############################################################################
167
+ # 第三步:拉取最新代码
168
+ ##############################################################################
169
+ echo ""
170
+ printf "%b\n" "\033[34m3. 拉取最新代码...\033[0m"
171
+
172
+ TMP_DIR="${TMPDIR:-/tmp}/qqbot-update-$$"
173
+ cleanup() { rm -rf "$TMP_DIR" 2>/dev/null; }
174
+ trap cleanup EXIT INT TERM
175
+
176
+ if [ -d "$PROJ_DIR/.git" ] && [ "$FRESH_INSTALL" = false ]; then
177
+ cd "$PROJ_DIR"
178
+
179
+ # 有本地修改直接重置,插件目录不需要保留用户改动
180
+ if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
181
+ echo " 检测到本地修改,自动重置..."
182
+ git checkout -- . 2>/dev/null
183
+ git clean -fd 2>/dev/null
184
+ fi
185
+
186
+ echo " 切换到分支 $BRANCH..."
187
+ git fetch --all --prune 2>&1 | tail -3
188
+ git checkout "$BRANCH" 2>/dev/null || git checkout -b "$BRANCH" "origin/$BRANCH" 2>/dev/null || true
189
+ git reset --hard "origin/$BRANCH" 2>/dev/null
190
+
191
+ REMOTE_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "")
192
+ NEW_VER=$(json_get "$PROJ_DIR/package.json" "c => c.version")
193
+
194
+ if [ -n "$LOCAL_COMMIT" ] && [ "$LOCAL_COMMIT" = "$REMOTE_COMMIT" ]; then
195
+ echo " ✅ 已是最新 ($LOCAL_VER, commit: $LOCAL_COMMIT),继续检查依赖..."
196
+ else
197
+ echo " 更新: ${LOCAL_COMMIT:-???} → ${REMOTE_COMMIT}"
198
+ git --no-pager log --oneline "${LOCAL_COMMIT}..HEAD" 2>/dev/null | head -10 || true
199
+ fi
200
+ else
201
+ rm -rf "$TMP_DIR"
202
+ echo " 克隆仓库..."
203
+ if ! git clone --branch "$BRANCH" --depth 1 "$REPO_URL" "$TMP_DIR" 2>&1 | tail -3; then
204
+ printf "%b\n" "\033[31m❌ Git clone 失败\033[0m"
205
+ echo ""
206
+ echo "请排查:"
207
+ echo " 1. 检查网络: curl -I https://github.com"
208
+ echo " 2. 检查仓库地址: $REPO_URL"
209
+ echo " 3. 如果是私有仓库,确认已配置 SSH key 或 token"
210
+ exit 1
211
+ fi
212
+
213
+ mkdir -p "$PROJ_DIR"
214
+ rsync -a --delete --exclude 'node_modules' "$TMP_DIR/" "$PROJ_DIR/"
215
+
216
+ cd "$PROJ_DIR"
217
+ REMOTE_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "")
218
+ NEW_VER=$(json_get "$PROJ_DIR/package.json" "c => c.version")
219
+ echo " 已克隆到版本: ${NEW_VER:-未知} (${REMOTE_COMMIT})"
220
+ cleanup
221
+ fi
222
+
223
+ NEW_VER="${NEW_VER:-未知}"
224
+ printf "%b\n" "\033[32m ✅ 代码已更新到 $NEW_VER\033[0m"
225
+
226
+ ##############################################################################
227
+ # 第四步:安装依赖
228
+ ##############################################################################
229
+ echo ""
230
+ printf "%b\n" "\033[34m4. 安装依赖...\033[0m"
231
+
232
+ cd "$PROJ_DIR"
233
+ if ! npm install --omit=dev 2>&1 | tail -5; then
234
+ printf "%b\n" "\033[31m❌ npm 依赖安装失败\033[0m"
235
+ echo ""
236
+ echo "请排查:"
237
+ echo " 1. 手动重试: cd $PROJ_DIR && npm install --omit=dev"
238
+ echo " 2. 清理后重试: rm -rf $PROJ_DIR/node_modules && npm install --omit=dev"
239
+ echo " 3. 切换镜像: npm config set registry https://registry.npmmirror.com/"
240
+ exit 1
241
+ fi
242
+ echo " ✅ 依赖安装完成"
243
+
244
+ ##############################################################################
245
+ # 第五步:恢复配置 → 重启网关
246
+ ##############################################################################
247
+ echo ""
248
+ printf "%b\n" "\033[34m5. 恢复配置并重启网关...\033[0m"
249
+
250
+ # 恢复通道配置
251
+ if [ -n "$SAVED_CHANNELS_JSON" ]; then
252
+ if node -e "
253
+ const fs = require('fs');
254
+ const cfg = JSON.parse(fs.readFileSync('$APP_CONFIG', 'utf8'));
255
+ cfg.channels = cfg.channels || {};
256
+ cfg.channels.qqbot = $SAVED_CHANNELS_JSON;
257
+ fs.writeFileSync('$APP_CONFIG', JSON.stringify(cfg, null, 4) + '\n');
258
+ " 2>/dev/null; then
259
+ echo " ✅ 通道配置已恢复"
260
+ else
261
+ printf "%b\n" "\033[33m ⚠️ 通道配置写入失败,请手动检查: $APP_CONFIG\033[0m"
262
+ fi
263
+ elif [ "$FRESH_INSTALL" = true ]; then
264
+ echo ""
265
+ printf "%b\n" "\033[33m ⚠️ 首次安装,请配置 QQ Bot 凭据:\033[0m"
266
+ echo " $CMD channels add --channel qqbot --token 'YOUR_APPID:YOUR_SECRET'"
267
+ echo ""
268
+ fi
269
+
270
+ # 停止旧 gateway
271
+ echo " 停止旧网关..."
272
+ $CMD gateway stop 2>/dev/null || true
273
+ sleep 1
274
+
275
+ # 强制杀占用端口的进程
276
+ PORT_PID=$(lsof -ti:"$GATEWAY_PORT" 2>/dev/null || true)
277
+ if [ -n "$PORT_PID" ]; then
278
+ printf "%b\n" "\033[33m ⚠️ 端口 $GATEWAY_PORT 仍被占用 (PID: $PORT_PID),强制终止...\033[0m"
279
+ kill -9 $PORT_PID 2>/dev/null || true
280
+ sleep 1
281
+ fi
282
+
283
+ # 卸载 launchd 服务(防止自动拉起旧进程)
284
+ for svc in ai.openclaw.gateway ai.clawdbot.gateway ai.moltbot.gateway; do
285
+ launchctl bootout "gui/$(id -u)/$svc" 2>/dev/null || true
286
+ done
287
+
288
+ # 启动网关
289
+ echo " 启动网关..."
290
+ if $CMD gateway 2>&1; then
291
+ printf "%b\n" "\033[32m ✅ 网关已启动\033[0m"
292
+ else
293
+ echo ""
294
+ printf "%b\n" "\033[33m ⚠️ 网关启动失败(不影响已安装的插件)\033[0m"
295
+ echo ""
296
+ echo " 请手动启动:"
297
+ echo " 1. 安装服务: $CMD gateway install"
298
+ echo " 2. 启动网关: $CMD gateway"
299
+ echo " 3. 查看日志: $CMD logs --follow"
300
+ fi
301
+
302
+ ##############################################################################
303
+ # 完成
304
+ ##############################################################################
305
+ echo ""
306
+ printf "%b\n" "\033[32m=========================================\033[0m"
307
+ printf "%b\n" "\033[32m ✅ QQBot 已更新到 ${NEW_VER}${REMOTE_COMMIT:+ (${REMOTE_COMMIT})}\033[0m"
308
+ [ -n "$LOCAL_VER" ] && printf "%b\n" "\033[32m (从 ${LOCAL_VER}${LOCAL_COMMIT:+ (${LOCAL_COMMIT})} 升级)\033[0m"
309
+ printf "%b\n" "\033[32m=========================================\033[0m"
310
+ echo ""
311
+ echo "常用命令:"
312
+ echo " $CMD logs --follow # 跟踪日志"
313
+ echo " $CMD gateway restart # 重启服务"
314
+ echo " $CMD plugins list # 查看插件列表"
315
+ echo " cd $PROJ_DIR && git log # 查看更新历史"
316
+ echo "========================================="
@@ -0,0 +1,273 @@
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
+ // 从配置文件加载账户信息
55
+ function loadAccount(accountId = "default"): ResolvedQQBotAccount | null {
56
+ const configPath = path.join(process.env.HOME || "/home/ubuntu", "clawd", "config.json");
57
+
58
+ try {
59
+ if (!configPath || !fs.existsSync(configPath)) {
60
+ // 尝试从环境变量获取
61
+ const appId = process.env.QQBOT_APP_ID;
62
+ const clientSecret = process.env.QQBOT_CLIENT_SECRET;
63
+
64
+ if (appId && clientSecret) {
65
+ return {
66
+ accountId,
67
+ appId,
68
+ clientSecret,
69
+ enabled: true,
70
+ secretSource: "env",
71
+ };
72
+ }
73
+
74
+ console.error("配置文件不存在且环境变量未设置");
75
+ return null;
76
+ }
77
+
78
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
79
+ const qqbot = config.channels?.qqbot;
80
+
81
+ if (!qqbot) {
82
+ console.error("配置中没有 qqbot 配置");
83
+ return null;
84
+ }
85
+
86
+ // 解析账户配置
87
+ if (accountId === "default") {
88
+ return {
89
+ accountId: "default",
90
+ appId: qqbot.appId || process.env.QQBOT_APP_ID,
91
+ clientSecret: qqbot.clientSecret || process.env.QQBOT_CLIENT_SECRET,
92
+ enabled: qqbot.enabled ?? true,
93
+ secretSource: qqbot.clientSecret ? "config" : "env",
94
+ };
95
+ }
96
+
97
+ const accountConfig = qqbot.accounts?.[accountId];
98
+ if (accountConfig) {
99
+ return {
100
+ accountId,
101
+ appId: accountConfig.appId || qqbot.appId || process.env.QQBOT_APP_ID,
102
+ clientSecret: accountConfig.clientSecret || qqbot.clientSecret || process.env.QQBOT_CLIENT_SECRET,
103
+ enabled: accountConfig.enabled ?? true,
104
+ secretSource: accountConfig.clientSecret ? "config" : "env",
105
+ };
106
+ }
107
+
108
+ console.error(`账户 ${accountId} 不存在`);
109
+ return null;
110
+ } catch (err) {
111
+ console.error(`加载配置失败: ${err}`);
112
+ return null;
113
+ }
114
+ }
115
+
116
+ async function main() {
117
+ const args = parseArgs();
118
+
119
+ // 显示帮助
120
+ if (args.help || args.h) {
121
+ console.log(`
122
+ QQBot 主动消息 CLI 工具
123
+
124
+ 用法:
125
+ npx ts-node scripts/send-proactive.ts [选项]
126
+
127
+ 选项:
128
+ --to <openid> 目标用户或群组的 openid
129
+ --text <message> 要发送的消息内容
130
+ --type <type> 消息类型: c2c (私聊) 或 group (群聊),默认 c2c
131
+ --account <id> 账户 ID,默认 default
132
+
133
+ --list 列出已知用户
134
+ --stats 显示用户统计
135
+ --broadcast 广播消息给所有已知用户
136
+ --limit <n> 限制数量
137
+
138
+ --help, -h 显示帮助
139
+
140
+ 示例:
141
+ # 发送私聊消息
142
+ npx ts-node scripts/send-proactive.ts --to "0Eda5EA7-xxx" --text "你好!"
143
+
144
+ # 发送群聊消息
145
+ npx ts-node scripts/send-proactive.ts --to "A1B2C3D4" --type group --text "群公告"
146
+
147
+ # 列出最近 10 个私聊用户
148
+ npx ts-node scripts/send-proactive.ts --list --type c2c --limit 10
149
+
150
+ # 广播消息
151
+ npx ts-node scripts/send-proactive.ts --broadcast --text "系统公告" --limit 5
152
+ `);
153
+ return;
154
+ }
155
+
156
+ const accountId = (args.account as string) || "default";
157
+ const type = (args.type as "c2c" | "group") || "c2c";
158
+ const limit = args.limit ? parseInt(args.limit as string, 10) : undefined;
159
+
160
+ // 列出已知用户
161
+ if (args.list) {
162
+ const users = listKnownUsers({
163
+ type: args.type as "c2c" | "group" | "channel" | undefined,
164
+ accountId: args.account as string | undefined,
165
+ limit,
166
+ });
167
+
168
+ if (users.length === 0) {
169
+ console.log("没有已知用户");
170
+ return;
171
+ }
172
+
173
+ console.log(`\n已知用户列表 (共 ${users.length} 个):\n`);
174
+ console.log("类型\t\tOpenID\t\t\t\t\t\t昵称\t\t最后交互时间");
175
+ console.log("─".repeat(100));
176
+
177
+ for (const user of users) {
178
+ const lastTime = new Date(user.lastInteractionAt).toLocaleString();
179
+ console.log(`${user.type}\t\t${user.openid.slice(0, 20)}...\t${user.nickname || "-"}\t\t${lastTime}`);
180
+ }
181
+ return;
182
+ }
183
+
184
+ // 显示统计
185
+ if (args.stats) {
186
+ const stats = getKnownUsersStats(args.account as string | undefined);
187
+ console.log(`\n用户统计:`);
188
+ console.log(` 总计: ${stats.total}`);
189
+ console.log(` 私聊: ${stats.c2c}`);
190
+ console.log(` 群聊: ${stats.group}`);
191
+ console.log(` 频道: ${stats.channel}`);
192
+ return;
193
+ }
194
+
195
+ // 广播消息
196
+ if (args.broadcast) {
197
+ if (!args.text) {
198
+ console.error("请指定消息内容 (--text)");
199
+ process.exit(1);
200
+ }
201
+
202
+ // 加载配置用于广播
203
+ const configPath = detectConfigPath();
204
+ let cfg: Record<string, unknown> = {};
205
+ try {
206
+ if (configPath && fs.existsSync(configPath)) {
207
+ cfg = JSON.parse(fs.readFileSync(configPath, "utf-8"));
208
+ }
209
+ } catch {}
210
+
211
+ console.log(`\n开始广播消息...\n`);
212
+ const result = await broadcastMessage(args.text as string, cfg as any, {
213
+ type,
214
+ accountId,
215
+ limit,
216
+ });
217
+
218
+ console.log(`\n广播完成:`);
219
+ console.log(` 发送总数: ${result.total}`);
220
+ console.log(` 成功: ${result.success}`);
221
+ console.log(` 失败: ${result.failed}`);
222
+
223
+ if (result.failed > 0) {
224
+ console.log(`\n失败详情:`);
225
+ for (const r of result.results) {
226
+ if (!r.result.success) {
227
+ console.log(` ${r.to}: ${r.result.error}`);
228
+ }
229
+ }
230
+ }
231
+ return;
232
+ }
233
+
234
+ // 发送单条消息
235
+ if (args.to && args.text) {
236
+ const account = loadAccount(accountId);
237
+ if (!account) {
238
+ console.error("无法加载账户配置");
239
+ process.exit(1);
240
+ }
241
+
242
+ console.log(`\n发送消息...`);
243
+ console.log(` 目标: ${args.to}`);
244
+ console.log(` 类型: ${type}`);
245
+ console.log(` 内容: ${args.text}`);
246
+
247
+ const result = await sendProactiveMessageDirect(
248
+ account,
249
+ args.to as string,
250
+ args.text as string,
251
+ type
252
+ );
253
+
254
+ if (result.success) {
255
+ console.log(`\n✅ 发送成功!`);
256
+ console.log(` 消息ID: ${result.messageId}`);
257
+ console.log(` 时间戳: ${result.timestamp}`);
258
+ } else {
259
+ console.log(`\n❌ 发送失败: ${result.error}`);
260
+ process.exit(1);
261
+ }
262
+ return;
263
+ }
264
+
265
+ // 没有有效参数
266
+ console.error("请指定操作。使用 --help 查看帮助。");
267
+ process.exit(1);
268
+ }
269
+
270
+ main().catch((err) => {
271
+ console.error(`执行失败: ${err}`);
272
+ process.exit(1);
273
+ });