@cxyhhhhh/openclaw-qqbot 1.6.7-alpha.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 (218) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +470 -0
  3. package/README.zh.md +465 -0
  4. package/bin/qqbot-cli.js +243 -0
  5. package/dist/index.d.ts +17 -0
  6. package/dist/index.js +26 -0
  7. package/dist/src/admin-resolver.d.ts +33 -0
  8. package/dist/src/admin-resolver.js +157 -0
  9. package/dist/src/api.d.ts +264 -0
  10. package/dist/src/api.js +777 -0
  11. package/dist/src/channel.d.ts +29 -0
  12. package/dist/src/channel.js +452 -0
  13. package/dist/src/config.d.ts +56 -0
  14. package/dist/src/config.js +278 -0
  15. package/dist/src/credential-backup.d.ts +31 -0
  16. package/dist/src/credential-backup.js +66 -0
  17. package/dist/src/deliver-debounce.d.ts +74 -0
  18. package/dist/src/deliver-debounce.js +174 -0
  19. package/dist/src/gateway.d.ts +18 -0
  20. package/dist/src/gateway.js +2021 -0
  21. package/dist/src/group-history.d.ts +136 -0
  22. package/dist/src/group-history.js +226 -0
  23. package/dist/src/image-server.d.ts +87 -0
  24. package/dist/src/image-server.js +570 -0
  25. package/dist/src/inbound-attachments.d.ts +60 -0
  26. package/dist/src/inbound-attachments.js +248 -0
  27. package/dist/src/known-users.d.ts +100 -0
  28. package/dist/src/known-users.js +263 -0
  29. package/dist/src/message-gating.d.ts +53 -0
  30. package/dist/src/message-gating.js +107 -0
  31. package/dist/src/message-queue.d.ts +86 -0
  32. package/dist/src/message-queue.js +257 -0
  33. package/dist/src/onboarding.d.ts +10 -0
  34. package/dist/src/onboarding.js +203 -0
  35. package/dist/src/outbound-deliver.d.ts +48 -0
  36. package/dist/src/outbound-deliver.js +392 -0
  37. package/dist/src/outbound.d.ts +205 -0
  38. package/dist/src/outbound.js +926 -0
  39. package/dist/src/proactive.d.ts +170 -0
  40. package/dist/src/proactive.js +399 -0
  41. package/dist/src/ref-index-store.d.ts +70 -0
  42. package/dist/src/ref-index-store.js +250 -0
  43. package/dist/src/reply-dispatcher.d.ts +35 -0
  44. package/dist/src/reply-dispatcher.js +311 -0
  45. package/dist/src/request-context.d.ts +18 -0
  46. package/dist/src/request-context.js +30 -0
  47. package/dist/src/runtime.d.ts +3 -0
  48. package/dist/src/runtime.js +10 -0
  49. package/dist/src/session-store.d.ts +52 -0
  50. package/dist/src/session-store.js +254 -0
  51. package/dist/src/slash-commands.d.ts +77 -0
  52. package/dist/src/slash-commands.js +1461 -0
  53. package/dist/src/startup-greeting.d.ts +30 -0
  54. package/dist/src/startup-greeting.js +97 -0
  55. package/dist/src/streaming.d.ts +250 -0
  56. package/dist/src/streaming.js +914 -0
  57. package/dist/src/stt.d.ts +21 -0
  58. package/dist/src/stt.js +70 -0
  59. package/dist/src/tools/channel.d.ts +16 -0
  60. package/dist/src/tools/channel.js +234 -0
  61. package/dist/src/tools/remind.d.ts +2 -0
  62. package/dist/src/tools/remind.js +248 -0
  63. package/dist/src/types.d.ts +364 -0
  64. package/dist/src/types.js +17 -0
  65. package/dist/src/typing-keepalive.d.ts +27 -0
  66. package/dist/src/typing-keepalive.js +64 -0
  67. package/dist/src/update-checker.d.ts +34 -0
  68. package/dist/src/update-checker.js +160 -0
  69. package/dist/src/utils/audio-convert.d.ts +98 -0
  70. package/dist/src/utils/audio-convert.js +755 -0
  71. package/dist/src/utils/chunked-upload.d.ts +59 -0
  72. package/dist/src/utils/chunked-upload.js +289 -0
  73. package/dist/src/utils/file-utils.d.ts +61 -0
  74. package/dist/src/utils/file-utils.js +172 -0
  75. package/dist/src/utils/image-size.d.ts +51 -0
  76. package/dist/src/utils/image-size.js +234 -0
  77. package/dist/src/utils/media-send.d.ts +148 -0
  78. package/dist/src/utils/media-send.js +456 -0
  79. package/dist/src/utils/media-tags.d.ts +14 -0
  80. package/dist/src/utils/media-tags.js +164 -0
  81. package/dist/src/utils/payload.d.ts +112 -0
  82. package/dist/src/utils/payload.js +186 -0
  83. package/dist/src/utils/pkg-version.d.ts +5 -0
  84. package/dist/src/utils/pkg-version.js +51 -0
  85. package/dist/src/utils/platform.d.ts +137 -0
  86. package/dist/src/utils/platform.js +390 -0
  87. package/dist/src/utils/ssrf-guard.d.ts +25 -0
  88. package/dist/src/utils/ssrf-guard.js +91 -0
  89. package/dist/src/utils/text-parsing.d.ts +32 -0
  90. package/dist/src/utils/text-parsing.js +69 -0
  91. package/dist/src/utils/upload-cache.d.ts +34 -0
  92. package/dist/src/utils/upload-cache.js +93 -0
  93. package/index.ts +31 -0
  94. package/node_modules/@eshaz/web-worker/LICENSE +201 -0
  95. package/node_modules/@eshaz/web-worker/README.md +134 -0
  96. package/node_modules/@eshaz/web-worker/browser.js +17 -0
  97. package/node_modules/@eshaz/web-worker/cjs/browser.js +16 -0
  98. package/node_modules/@eshaz/web-worker/cjs/node.js +219 -0
  99. package/node_modules/@eshaz/web-worker/index.d.ts +4 -0
  100. package/node_modules/@eshaz/web-worker/node.js +223 -0
  101. package/node_modules/@eshaz/web-worker/package.json +54 -0
  102. package/node_modules/@wasm-audio-decoders/common/index.js +5 -0
  103. package/node_modules/@wasm-audio-decoders/common/package.json +36 -0
  104. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +231 -0
  105. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +129 -0
  106. package/node_modules/@wasm-audio-decoders/common/src/puff/README +67 -0
  107. package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +31 -0
  108. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +863 -0
  109. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +35 -0
  110. package/node_modules/@wasm-audio-decoders/common/src/utilities.js +3 -0
  111. package/node_modules/@wasm-audio-decoders/common/types.d.ts +7 -0
  112. package/node_modules/mpg123-decoder/README.md +265 -0
  113. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +185 -0
  114. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +1 -0
  115. package/node_modules/mpg123-decoder/index.js +8 -0
  116. package/node_modules/mpg123-decoder/package.json +58 -0
  117. package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +464 -0
  118. package/node_modules/mpg123-decoder/src/MPEGDecoder.js +200 -0
  119. package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +21 -0
  120. package/node_modules/mpg123-decoder/types.d.ts +30 -0
  121. package/node_modules/silk-wasm/LICENSE +21 -0
  122. package/node_modules/silk-wasm/README.md +85 -0
  123. package/node_modules/silk-wasm/lib/index.cjs +16 -0
  124. package/node_modules/silk-wasm/lib/index.d.ts +70 -0
  125. package/node_modules/silk-wasm/lib/index.mjs +16 -0
  126. package/node_modules/silk-wasm/lib/silk.wasm +0 -0
  127. package/node_modules/silk-wasm/lib/utils.d.ts +4 -0
  128. package/node_modules/silk-wasm/package.json +39 -0
  129. package/node_modules/simple-yenc/.github/FUNDING.yml +1 -0
  130. package/node_modules/simple-yenc/.prettierignore +1 -0
  131. package/node_modules/simple-yenc/LICENSE +7 -0
  132. package/node_modules/simple-yenc/README.md +163 -0
  133. package/node_modules/simple-yenc/dist/esm.js +1 -0
  134. package/node_modules/simple-yenc/dist/index.js +1 -0
  135. package/node_modules/simple-yenc/package.json +50 -0
  136. package/node_modules/simple-yenc/rollup.config.js +27 -0
  137. package/node_modules/simple-yenc/src/simple-yenc.js +302 -0
  138. package/node_modules/ws/LICENSE +20 -0
  139. package/node_modules/ws/README.md +548 -0
  140. package/node_modules/ws/browser.js +8 -0
  141. package/node_modules/ws/index.js +13 -0
  142. package/node_modules/ws/lib/buffer-util.js +131 -0
  143. package/node_modules/ws/lib/constants.js +19 -0
  144. package/node_modules/ws/lib/event-target.js +292 -0
  145. package/node_modules/ws/lib/extension.js +203 -0
  146. package/node_modules/ws/lib/limiter.js +55 -0
  147. package/node_modules/ws/lib/permessage-deflate.js +528 -0
  148. package/node_modules/ws/lib/receiver.js +706 -0
  149. package/node_modules/ws/lib/sender.js +602 -0
  150. package/node_modules/ws/lib/stream.js +161 -0
  151. package/node_modules/ws/lib/subprotocol.js +62 -0
  152. package/node_modules/ws/lib/validation.js +152 -0
  153. package/node_modules/ws/lib/websocket-server.js +554 -0
  154. package/node_modules/ws/lib/websocket.js +1393 -0
  155. package/node_modules/ws/package.json +69 -0
  156. package/node_modules/ws/wrapper.mjs +8 -0
  157. package/openclaw.plugin.json +17 -0
  158. package/package.json +67 -0
  159. package/preload.cjs +33 -0
  160. package/scripts/cleanup-legacy-plugins.sh +124 -0
  161. package/scripts/link-sdk-core.cjs +185 -0
  162. package/scripts/postinstall-link-sdk.js +113 -0
  163. package/scripts/proactive-api-server.ts +369 -0
  164. package/scripts/send-proactive.ts +293 -0
  165. package/scripts/set-markdown.sh +156 -0
  166. package/scripts/test-sendmedia.ts +116 -0
  167. package/scripts/upgrade-via-npm.ps1 +451 -0
  168. package/scripts/upgrade-via-npm.sh +528 -0
  169. package/scripts/upgrade-via-source.sh +916 -0
  170. package/skills/qqbot-channel/SKILL.md +263 -0
  171. package/skills/qqbot-channel/references/api_references.md +521 -0
  172. package/skills/qqbot-media/SKILL.md +60 -0
  173. package/skills/qqbot-remind/SKILL.md +149 -0
  174. package/src/admin-resolver.ts +181 -0
  175. package/src/api.ts +1138 -0
  176. package/src/channel.ts +477 -0
  177. package/src/config.ts +347 -0
  178. package/src/credential-backup.ts +72 -0
  179. package/src/deliver-debounce.ts +229 -0
  180. package/src/gateway.ts +2257 -0
  181. package/src/group-history.ts +328 -0
  182. package/src/image-server.ts +675 -0
  183. package/src/inbound-attachments.ts +321 -0
  184. package/src/known-users.ts +353 -0
  185. package/src/message-gating.ts +190 -0
  186. package/src/message-queue.ts +349 -0
  187. package/src/onboarding.ts +274 -0
  188. package/src/openclaw-plugin-sdk.d.ts +587 -0
  189. package/src/outbound-deliver.ts +473 -0
  190. package/src/outbound.ts +1119 -0
  191. package/src/proactive.ts +530 -0
  192. package/src/ref-index-store.ts +335 -0
  193. package/src/reply-dispatcher.ts +334 -0
  194. package/src/request-context.ts +39 -0
  195. package/src/runtime.ts +14 -0
  196. package/src/session-store.ts +303 -0
  197. package/src/slash-commands.ts +1615 -0
  198. package/src/startup-greeting.ts +120 -0
  199. package/src/streaming.ts +1102 -0
  200. package/src/stt.ts +86 -0
  201. package/src/tools/channel.ts +281 -0
  202. package/src/tools/remind.ts +300 -0
  203. package/src/types.ts +386 -0
  204. package/src/typing-keepalive.ts +59 -0
  205. package/src/update-checker.ts +174 -0
  206. package/src/utils/audio-convert.ts +859 -0
  207. package/src/utils/chunked-upload.ts +419 -0
  208. package/src/utils/file-utils.ts +193 -0
  209. package/src/utils/image-size.ts +266 -0
  210. package/src/utils/media-send.ts +585 -0
  211. package/src/utils/media-tags.ts +182 -0
  212. package/src/utils/payload.ts +265 -0
  213. package/src/utils/pkg-version.ts +54 -0
  214. package/src/utils/platform.ts +435 -0
  215. package/src/utils/ssrf-guard.ts +102 -0
  216. package/src/utils/text-parsing.ts +75 -0
  217. package/src/utils/upload-cache.ts +128 -0
  218. package/tsconfig.json +16 -0
@@ -0,0 +1,528 @@
1
+ #!/bin/bash
2
+
3
+ # qqbot 通过 openclaw 原生插件指令升级
4
+ #
5
+ # 使用 openclaw plugins install/update 原生命令进行安装和升级,
6
+ # 保留 appid/secret 配置写入、热更新 (--no-restart)、结构化输出等功能。
7
+ #
8
+ # 升级策略:
9
+ # 1. 已安装(plugins.installs 有记录)→ openclaw plugins update
10
+ # 2. 未安装 / update 失败 → 删除旧目录 + openclaw plugins install
11
+ #
12
+ # 用法:
13
+ # upgrade-via-npm.sh # 升级到 latest(默认)
14
+ # upgrade-via-npm.sh --version <version> # 升级到指定版本
15
+ # upgrade-via-npm.sh --self-version # 升级到当前仓库 package.json 版本
16
+ # upgrade-via-npm.sh --appid <appid> --secret <secret> # 首次安装时配置 appid/secret
17
+ # upgrade-via-npm.sh --no-restart # 只做文件替换,不重启 gateway(供热更指令使用)
18
+
19
+ set -eo pipefail
20
+
21
+ # 异常退出时清理临时配置文件(防止泄露或残留)
22
+ cleanup_on_exit() {
23
+ if [ -n "$TEMP_CONFIG_FILE" ] && [ -f "$TEMP_CONFIG_FILE" ]; then
24
+ rm -f "$TEMP_CONFIG_FILE" 2>/dev/null || true
25
+ fi
26
+ }
27
+ trap cleanup_on_exit EXIT
28
+
29
+ PKG_NAME="@tencent-connect/openclaw-qqbot"
30
+ PLUGIN_ID="openclaw-qqbot"
31
+ INSTALL_SRC=""
32
+ TARGET_VERSION=""
33
+ APPID=""
34
+ SECRET=""
35
+ NO_RESTART=false
36
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
37
+ PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
38
+
39
+ LOCAL_VERSION="$(node -e "
40
+ try {
41
+ const fs = require('fs');
42
+ const path = require('path');
43
+ const p = path.join('$PROJECT_DIR', 'package.json');
44
+ const v = JSON.parse(fs.readFileSync(p, 'utf8')).version;
45
+ if (v) process.stdout.write(String(v));
46
+ } catch {}
47
+ " 2>/dev/null || true)"
48
+
49
+ print_usage() {
50
+ echo "用法:"
51
+ echo " upgrade-via-npm.sh # 升级到 latest(默认)"
52
+ echo " upgrade-via-npm.sh --version <版本号> # 升级到指定版本"
53
+ if [ -n "$LOCAL_VERSION" ]; then
54
+ echo " upgrade-via-npm.sh --self-version # 升级到当前仓库版本($LOCAL_VERSION)"
55
+ else
56
+ echo " upgrade-via-npm.sh --self-version # 升级到当前仓库版本"
57
+ fi
58
+ echo ""
59
+ echo " --appid <appid> QQ机器人 appid(首次安装时必填)"
60
+ echo " --secret <secret> QQ机器人 secret(首次安装时必填)"
61
+ echo ""
62
+ echo "也可以通过环境变量设置:"
63
+ echo " QQBOT_APPID QQ机器人 appid"
64
+ echo " QQBOT_SECRET QQ机器人 secret"
65
+ echo " QQBOT_TOKEN QQ机器人 token (appid:secret)"
66
+ }
67
+
68
+ while [[ $# -gt 0 ]]; do
69
+ case "$1" in
70
+ --tag)
71
+ [ -z "$2" ] && echo "❌ --tag 需要参数" && exit 1
72
+ _ver="${2#v}" # 去掉 v 前缀(npm 版本号不带 v)
73
+ TARGET_VERSION="$_ver"
74
+ INSTALL_SRC="${PKG_NAME}@$_ver"
75
+ shift 2
76
+ ;;
77
+ --version)
78
+ [ -z "$2" ] && echo "❌ --version 需要参数" && exit 1
79
+ _ver="${2#v}" # 去掉 v 前缀(npm 版本号不带 v)
80
+ TARGET_VERSION="$_ver"
81
+ INSTALL_SRC="${PKG_NAME}@$_ver"
82
+ shift 2
83
+ ;;
84
+ --self-version)
85
+ [ -z "$LOCAL_VERSION" ] && echo "❌ 无法从 package.json 读取版本" && exit 1
86
+ TARGET_VERSION="$LOCAL_VERSION"
87
+ INSTALL_SRC="${PKG_NAME}@${LOCAL_VERSION}"
88
+ shift 1
89
+ ;;
90
+ --appid)
91
+ [ -z "$2" ] && echo "❌ --appid 需要参数" && exit 1
92
+ APPID="$2"
93
+ shift 2
94
+ ;;
95
+ --secret)
96
+ [ -z "$2" ] && echo "❌ --secret 需要参数" && exit 1
97
+ SECRET="$2"
98
+ shift 2
99
+ ;;
100
+ --no-restart)
101
+ NO_RESTART=true
102
+ shift 1
103
+ ;;
104
+ -h|--help)
105
+ print_usage
106
+ exit 0
107
+ ;;
108
+ *) echo "未知选项: $1"; print_usage; exit 1 ;;
109
+ esac
110
+ done
111
+ INSTALL_SRC="${INSTALL_SRC:-${PKG_NAME}@latest}"
112
+
113
+ # 环境变量 fallback
114
+ APPID="${APPID:-$QQBOT_APPID}"
115
+ SECRET="${SECRET:-$QQBOT_SECRET}"
116
+ if [ -z "$APPID" ] && [ -z "$SECRET" ] && [ -n "$QQBOT_TOKEN" ]; then
117
+ APPID="${QQBOT_TOKEN%%:*}"
118
+ SECRET="${QQBOT_TOKEN#*:}"
119
+ fi
120
+
121
+ # 检测 CLI
122
+ CMD=""
123
+ for name in openclaw clawdbot moltbot; do
124
+ command -v "$name" &>/dev/null && CMD="$name" && break
125
+ done
126
+ [ -z "$CMD" ] && echo "❌ 未找到 openclaw / clawdbot / moltbot" && exit 1
127
+
128
+ EXTENSIONS_DIR="$HOME/.$CMD/extensions"
129
+
130
+ # 检测 openclaw 版本
131
+ OPENCLAW_VERSION="$($CMD --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1 || true)"
132
+
133
+ echo "==========================================="
134
+ echo " qqbot 升级: $INSTALL_SRC"
135
+ echo " openclaw 版本: ${OPENCLAW_VERSION:-unknown}"
136
+ echo "==========================================="
137
+ echo ""
138
+
139
+ # 记录升级前的版本
140
+ OLD_VERSION=""
141
+ OLD_PKG="$EXTENSIONS_DIR/$PLUGIN_ID/package.json"
142
+ if [ -f "$OLD_PKG" ]; then
143
+ OLD_VERSION="$(node -e "
144
+ try {
145
+ const v = JSON.parse(require('fs').readFileSync('$OLD_PKG', 'utf8')).version;
146
+ if (v) process.stdout.write(String(v));
147
+ } catch {}
148
+ " 2>/dev/null || true)"
149
+ echo " 当前版本: ${OLD_VERSION:-unknown}"
150
+ fi
151
+
152
+ # [1/4] 通过 openclaw 原生指令安装/升级
153
+ echo ""
154
+ echo "[1/4] 安装/升级插件..."
155
+
156
+ # ── 兼容 openclaw 3.23+ 配置严格校验 ──
157
+ # 3.23+ 在 plugins install/update 时会校验整个配置文件,
158
+ # 如果 channels.qqbot 已存在但 qqbot 插件尚未加载,校验会失败。
159
+ #
160
+ # ⚠️ 关键:绝不能直接修改真实的 openclaw.json,否则 gateway 的 config file watcher
161
+ # 会检测到变更并触发 SIGUSR1 重启,导致正在执行的升级脚本被杀死。
162
+ #
163
+ # 解决:创建临时配置副本(不含 channels.qqbot),通过 OPENCLAW_CONFIG_PATH
164
+ # 环境变量让 plugins install/update 使用临时配置,真实配置文件不受影响。
165
+ CONFIG_FILE="$HOME/.$CMD/$CMD.json"
166
+ TEMP_CONFIG_FILE=""
167
+ HAS_QQBOT_CHANNEL=false
168
+
169
+ if [ -f "$CONFIG_FILE" ]; then
170
+ HAS_QQBOT_CHANNEL="$(node -e "
171
+ try {
172
+ const fs = require('fs');
173
+ const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
174
+ if (cfg.channels && cfg.channels.qqbot) process.stdout.write('true');
175
+ } catch {}
176
+ " 2>/dev/null || true)"
177
+
178
+ if [ "$HAS_QQBOT_CHANNEL" = "true" ]; then
179
+ TEMP_CONFIG_FILE="$(mktemp)"
180
+ node -e "
181
+ try {
182
+ const fs = require('fs');
183
+ const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
184
+ delete cfg.channels.qqbot;
185
+ if (Object.keys(cfg.channels).length === 0) delete cfg.channels;
186
+ fs.writeFileSync('$TEMP_CONFIG_FILE', JSON.stringify(cfg, null, 4) + '\n');
187
+ } catch(e) { process.exit(1); }
188
+ " 2>/dev/null
189
+ if [ $? -eq 0 ]; then
190
+ echo " [兼容] 创建临时配置副本(不含 channels.qqbot)以通过配置校验"
191
+ export OPENCLAW_CONFIG_PATH="$TEMP_CONFIG_FILE"
192
+ else
193
+ echo " ⚠️ 创建临时配置失败,继续使用原配置"
194
+ TEMP_CONFIG_FILE=""
195
+ fi
196
+ fi
197
+ fi
198
+
199
+ # 清理临时配置的函数
200
+ # plugins install/update 可能把 install 记录写入了临时配置,需要同步回真实配置
201
+ restore_qqbot_channel() {
202
+ if [ -n "$TEMP_CONFIG_FILE" ] && [ -f "$TEMP_CONFIG_FILE" ]; then
203
+ # 将临时配置中 plugins.installs 的变更同步回真实配置
204
+ node -e "
205
+ try {
206
+ const fs = require('fs');
207
+ const tmp = JSON.parse(fs.readFileSync('$TEMP_CONFIG_FILE', 'utf8'));
208
+ const real = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
209
+ if (tmp.plugins && tmp.plugins.installs) {
210
+ if (!real.plugins) real.plugins = {};
211
+ real.plugins.installs = { ...(real.plugins.installs || {}), ...tmp.plugins.installs };
212
+ fs.writeFileSync('$CONFIG_FILE', JSON.stringify(real, null, 4) + '\n');
213
+ }
214
+ } catch {}
215
+ " 2>/dev/null || true
216
+ rm -f "$TEMP_CONFIG_FILE"
217
+ unset OPENCLAW_CONFIG_PATH
218
+ echo " [兼容] 已同步 install 记录并清理临时配置副本"
219
+ fi
220
+ }
221
+
222
+ UPGRADE_OK=false
223
+
224
+ # 检测安装状态:同时检查配置记录和磁盘目录
225
+ HAS_INSTALL_RECORD="$(node -e "
226
+ try {
227
+ const fs = require('fs');
228
+ const p = '$HOME/.$CMD/$CMD.json';
229
+ const cfg = JSON.parse(fs.readFileSync(p, 'utf8'));
230
+ const inst = cfg.plugins && cfg.plugins.installs && cfg.plugins.installs['$PLUGIN_ID'];
231
+ if (inst) process.stdout.write('yes');
232
+ } catch {}
233
+ " 2>/dev/null || true)"
234
+ HAS_PLUGIN_DIR=false
235
+ [ -d "$EXTENSIONS_DIR/$PLUGIN_ID" ] && [ -f "$EXTENSIONS_DIR/$PLUGIN_ID/package.json" ] && HAS_PLUGIN_DIR=true
236
+
237
+ # 决策矩阵:
238
+ # 配置有记录 + 目录存在 → update(最佳路径)
239
+ # 配置有记录 + 目录不存在 → 清理残留记录,走 install
240
+ # 配置无记录 + 目录存在 → 删目录,走 install(配置与文件不一致)
241
+ # 配置无记录 + 目录不存在 → 走 install(全新安装)
242
+ #
243
+ # 指定了具体版本(--version/--tag/--self-version)时:
244
+ # update 不支持指定版本,直接走 删除 + install
245
+
246
+ USE_UPDATE=false
247
+
248
+ if [ "$HAS_INSTALL_RECORD" = "yes" ] && [ "$HAS_PLUGIN_DIR" = "true" ] && [ -z "$TARGET_VERSION" ]; then
249
+ # 配置和目录都齐全,且未指定版本 → 走 update
250
+ USE_UPDATE=true
251
+ echo " [检测] 配置记录 ✓ | 插件目录 ✓ | 未指定版本 → 使用 update"
252
+ elif [ "$HAS_INSTALL_RECORD" = "yes" ] && [ "$HAS_PLUGIN_DIR" = "true" ]; then
253
+ echo " [检测] 配置记录 ✓ | 插件目录 ✓ | 指定版本 $TARGET_VERSION → 使用 reinstall"
254
+ elif [ "$HAS_INSTALL_RECORD" = "yes" ]; then
255
+ echo " [检测] 配置记录 ✓ | 插件目录 ✗ → 配置与文件不一致,使用 install"
256
+ elif [ "$HAS_PLUGIN_DIR" = "true" ]; then
257
+ echo " [检测] 配置记录 ✗ | 插件目录 ✓ → 目录残留,清理后 install"
258
+ else
259
+ echo " [检测] 配置记录 ✗ | 插件目录 ✗ → 全新安装"
260
+ fi
261
+
262
+ if [ "$USE_UPDATE" = "true" ]; then
263
+ echo " 尝试 update..."
264
+ if $CMD plugins update "$PLUGIN_ID" 2>&1; then
265
+ # update 返回 0 不一定真的更新了,检查版本是否变化
266
+ POST_UPDATE_VERSION=""
267
+ if [ -f "$OLD_PKG" ]; then
268
+ POST_UPDATE_VERSION="$(node -e "
269
+ try {
270
+ const v = JSON.parse(require('fs').readFileSync('$OLD_PKG', 'utf8')).version;
271
+ if (v) process.stdout.write(String(v));
272
+ } catch {}
273
+ " 2>/dev/null || true)"
274
+ fi
275
+ if [ -n "$POST_UPDATE_VERSION" ] && [ "$POST_UPDATE_VERSION" != "$OLD_VERSION" ]; then
276
+ UPGRADE_OK=true
277
+ echo " ✅ update 成功 ($OLD_VERSION → $POST_UPDATE_VERSION)"
278
+ elif [ -z "$OLD_VERSION" ]; then
279
+ # 之前没有旧版本,无法比较,信任 update 结果
280
+ UPGRADE_OK=true
281
+ echo " ✅ update 成功"
282
+ else
283
+ echo " ⚠️ update 返回成功但版本未变 ($POST_UPDATE_VERSION),回退到 reinstall..."
284
+ fi
285
+ else
286
+ echo " ⚠️ update 失败,回退到 reinstall..."
287
+ fi
288
+ fi
289
+
290
+ if [ "$UPGRADE_OK" != "true" ]; then
291
+ # 备份旧目录(而非直接删除),install 失败时可回滚
292
+ BACKUP_DIR=""
293
+ if [ -d "$EXTENSIONS_DIR/$PLUGIN_ID" ]; then
294
+ BACKUP_DIR="$EXTENSIONS_DIR/.openclaw-qqbot-backup-$$"
295
+ mv "$EXTENSIONS_DIR/$PLUGIN_ID" "$BACKUP_DIR"
296
+ echo " 已备份旧目录: $BACKUP_DIR"
297
+ fi
298
+
299
+ # 清理历史遗留名称(这些不需要回滚)
300
+ for dir_name in qqbot openclaw-qq; do
301
+ [ -d "$EXTENSIONS_DIR/$dir_name" ] && rm -rf "$EXTENSIONS_DIR/$dir_name" && echo " 已清理历史目录: $EXTENSIONS_DIR/$dir_name"
302
+ done
303
+
304
+ echo " 执行 install: $INSTALL_SRC"
305
+
306
+ if $CMD plugins install "$INSTALL_SRC" --pin 2>&1; then
307
+ UPGRADE_OK=true
308
+ echo " ✅ install 成功"
309
+ # install 成功,清理备份
310
+ if [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ]; then
311
+ rm -rf "$BACKUP_DIR"
312
+ echo " 已清理旧版备份"
313
+ fi
314
+ # 清理 openclaw CLI install 可能留下的额外 backup 目录
315
+ find "$EXTENSIONS_DIR" -maxdepth 1 -name ".openclaw-qqbot-backup-*" -exec rm -rf {} + 2>/dev/null || true
316
+ else
317
+ echo " ❌ install 失败"
318
+ # 回滚:恢复旧目录
319
+ if [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ]; then
320
+ mv "$BACKUP_DIR" "$EXTENSIONS_DIR/$PLUGIN_ID"
321
+ echo " ↩️ 已回滚到旧版本"
322
+ fi
323
+ restore_qqbot_channel
324
+ echo "QQBOT_NEW_VERSION=unknown"
325
+ echo "QQBOT_REPORT=❌ QQBot 安装失败(已回滚到旧版本),请检查网络和 npm registry"
326
+ exit 1
327
+ fi
328
+ fi
329
+
330
+ # install/update 完成,恢复 channels.qqbot
331
+ restore_qqbot_channel
332
+
333
+ # [2/4] 验证安装
334
+ echo ""
335
+ echo "[2/4] 验证安装..."
336
+
337
+ PKG_JSON="$EXTENSIONS_DIR/$PLUGIN_ID/package.json"
338
+ if [ -f "$PKG_JSON" ]; then
339
+ NEW_VERSION="$(node -e "process.stdout.write(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).version||'')" "$PKG_JSON" 2>/dev/null || true)"
340
+ fi
341
+
342
+ # Preflight 检查
343
+ PREFLIGHT_OK=true
344
+ TARGET_DIR="$EXTENSIONS_DIR/$PLUGIN_ID"
345
+
346
+ if [ -z "$NEW_VERSION" ]; then
347
+ echo " ❌ 无法读取新版本号"
348
+ PREFLIGHT_OK=false
349
+ else
350
+ echo " ✅ 版本号: $NEW_VERSION"
351
+ fi
352
+
353
+ # 入口文件
354
+ ENTRY_FILE=""
355
+ for candidate in "dist/index.js" "index.js"; do
356
+ if [ -f "$TARGET_DIR/$candidate" ]; then
357
+ ENTRY_FILE="$candidate"
358
+ break
359
+ fi
360
+ done
361
+ if [ -z "$ENTRY_FILE" ]; then
362
+ echo " ❌ 缺少入口文件(dist/index.js 或 index.js)"
363
+ PREFLIGHT_OK=false
364
+ else
365
+ echo " ✅ 入口文件: $ENTRY_FILE"
366
+ fi
367
+
368
+ # 核心目录
369
+ if [ -d "$TARGET_DIR/dist/src" ]; then
370
+ CORE_JS_COUNT=$(find "$TARGET_DIR/dist/src" -name "*.js" -type f 2>/dev/null | wc -l | tr -d ' ')
371
+ echo " ✅ dist/src/ 包含 ${CORE_JS_COUNT} 个 JS 文件"
372
+ if [ "$CORE_JS_COUNT" -lt 5 ]; then
373
+ echo " ❌ JS 文件数量异常偏少(预期 ≥ 5,实际 ${CORE_JS_COUNT})"
374
+ PREFLIGHT_OK=false
375
+ fi
376
+ else
377
+ echo " ❌ 缺少核心目录 dist/src/"
378
+ PREFLIGHT_OK=false
379
+ fi
380
+
381
+ # 关键模块
382
+ MISSING_MODULES=""
383
+ for module in "dist/src/gateway.js" "dist/src/api.js" "dist/src/admin-resolver.js"; do
384
+ if [ ! -f "$TARGET_DIR/$module" ]; then
385
+ MISSING_MODULES="$MISSING_MODULES $module"
386
+ fi
387
+ done
388
+ if [ -n "$MISSING_MODULES" ]; then
389
+ echo " ❌ 缺少关键模块:$MISSING_MODULES"
390
+ PREFLIGHT_OK=false
391
+ else
392
+ echo " ✅ 关键模块完整"
393
+ fi
394
+
395
+ # bundled 依赖
396
+ if [ -d "$TARGET_DIR/node_modules" ]; then
397
+ BUNDLED_OK=true
398
+ for dep in "ws" "silk-wasm"; do
399
+ if [ ! -d "$TARGET_DIR/node_modules/$dep" ]; then
400
+ echo " ⚠️ bundled 依赖缺失: $dep"
401
+ BUNDLED_OK=false
402
+ fi
403
+ done
404
+ if $BUNDLED_OK; then
405
+ echo " ✅ 核心 bundled 依赖完整"
406
+ fi
407
+ fi
408
+
409
+ if [ "$PREFLIGHT_OK" != "true" ]; then
410
+ echo ""
411
+ echo "❌ 验证未通过"
412
+ echo "QQBOT_NEW_VERSION=unknown"
413
+ echo "QQBOT_REPORT=⚠️ QQBot 升级异常,验证未通过"
414
+ exit 1
415
+ fi
416
+ echo " ✅ 验证全部通过"
417
+
418
+ # 确保 openclaw/plugin-sdk 可解析:
419
+ # openclaw plugins install 不会执行 npm lifecycle scripts,
420
+ # 需要手动调用 postinstall-link-sdk.js 创建 node_modules/openclaw → 全局 openclaw 的符号链接
421
+ POSTINSTALL_SCRIPT="$TARGET_DIR/scripts/postinstall-link-sdk.js"
422
+ if [ -f "$POSTINSTALL_SCRIPT" ]; then
423
+ echo " 执行 postinstall-link-sdk..."
424
+ if node "$POSTINSTALL_SCRIPT" 2>&1; then
425
+ echo " ✅ plugin-sdk 链接就绪"
426
+ else
427
+ echo " ⚠️ postinstall-link-sdk 失败,插件可能无法加载"
428
+ fi
429
+ fi
430
+
431
+ # [3/4] 输出结构化信息(供 TS handler 解析)
432
+ echo ""
433
+ echo "[3/4] 升级结果..."
434
+ echo "QQBOT_NEW_VERSION=${NEW_VERSION:-unknown}"
435
+
436
+ if [ -n "$NEW_VERSION" ] && [ "$NEW_VERSION" != "unknown" ]; then
437
+ echo "QQBOT_REPORT=✅ QQBot 升级完成: v${NEW_VERSION}"
438
+ else
439
+ echo "QQBOT_REPORT=⚠️ QQBot 升级异常,无法确认新版本"
440
+ fi
441
+
442
+ echo ""
443
+ echo "==========================================="
444
+ echo " ✅ 安装完成"
445
+ echo "==========================================="
446
+
447
+ # --no-restart 模式(热更新场景):立即退出,让调用方触发 gateway restart
448
+ if [ "$NO_RESTART" = "true" ]; then
449
+ echo ""
450
+ echo "[跳过重启] --no-restart 已指定,脚本立即退出以便调用方触发 gateway restart"
451
+ exit 0
452
+ fi
453
+
454
+ # 以下步骤仅在非热更新(手动执行)场景中执行
455
+
456
+ # [配置] appid/secret(仅在提供了参数时执行)
457
+ if [ -n "$APPID" ] && [ -n "$SECRET" ]; then
458
+ echo ""
459
+ echo "[配置] 写入 qqbot 通道配置..."
460
+ DESIRED_TOKEN="${APPID}:${SECRET}"
461
+
462
+ # 读取当前已有的 token
463
+ CURRENT_TOKEN=""
464
+ for _app in openclaw clawdbot moltbot; do
465
+ _cfg="$HOME/.$_app/$_app.json"
466
+ if [ -f "$_cfg" ]; then
467
+ CURRENT_TOKEN=$(node -e "
468
+ const cfg = JSON.parse(require('fs').readFileSync('$_cfg', 'utf8'));
469
+ const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
470
+ for (const key of keys) {
471
+ const ch = cfg.channels && cfg.channels[key];
472
+ if (!ch) continue;
473
+ if (ch.token) { process.stdout.write(ch.token); process.exit(0); }
474
+ if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId + ':' + ch.clientSecret); process.exit(0); }
475
+ }
476
+ " 2>/dev/null || true)
477
+ [ -n "$CURRENT_TOKEN" ] && break
478
+ fi
479
+ done
480
+
481
+ if [ "$CURRENT_TOKEN" = "$DESIRED_TOKEN" ]; then
482
+ echo " ✅ 当前配置已是目标值,跳过写入"
483
+ else
484
+ # qqbot 是插件自定义通道,openclaw channels add --channel 不支持,
485
+ # 直接编辑配置文件写入 channels.qqbot
486
+ CONFIG_FILE="$HOME/.$CMD/$CMD.json"
487
+ if [ -f "$CONFIG_FILE" ] && node -e "
488
+ const fs = require('fs');
489
+ const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
490
+ if (!cfg.channels) cfg.channels = {};
491
+ if (!cfg.channels.qqbot) cfg.channels.qqbot = {};
492
+ cfg.channels.qqbot.appId = '$APPID';
493
+ cfg.channels.qqbot.clientSecret = '$SECRET';
494
+ fs.writeFileSync('$CONFIG_FILE', JSON.stringify(cfg, null, 4) + '\n');
495
+ " 2>&1; then
496
+ echo " ✅ 通道配置写入成功"
497
+ else
498
+ echo " ❌ 配置写入失败,请手动编辑 $CONFIG_FILE 添加 channels.qqbot:"
499
+ echo " { \"channels\": { \"qqbot\": { \"appId\": \"$APPID\", \"clientSecret\": \"...\" } } }"
500
+ fi
501
+ fi
502
+ elif [ -n "$APPID" ] || [ -n "$SECRET" ]; then
503
+ echo ""
504
+ echo "⚠️ --appid 和 --secret 必须同时提供"
505
+ fi
506
+
507
+ # [4/4] 重启 gateway 使新版本生效
508
+ echo ""
509
+
510
+ # 手动升级场景:提前写入 startup-marker,阻止重启后 bot 重复推送升级通知
511
+ if [ -n "$NEW_VERSION" ] && [ "$NEW_VERSION" != "unknown" ]; then
512
+ MARKER_DIR="$HOME/.openclaw/qqbot/data"
513
+ mkdir -p "$MARKER_DIR"
514
+ MARKER_FILE="$MARKER_DIR/startup-marker.json"
515
+ NOW="$(date -u +%Y-%m-%dT%H:%M:%S.000Z 2>/dev/null || date +%Y-%m-%dT%H:%M:%SZ)"
516
+ echo "{\"version\":\"$NEW_VERSION\",\"startedAt\":\"$NOW\",\"greetedAt\":\"$NOW\"}" > "$MARKER_FILE"
517
+ fi
518
+
519
+ echo "[重启] 重启 gateway 使新版本生效..."
520
+ if $CMD gateway restart 2>&1; then
521
+ echo " ✅ gateway 已重启"
522
+ echo ""
523
+ if [ -n "$NEW_VERSION" ] && [ "$NEW_VERSION" != "unknown" ]; then
524
+ echo "🎉 QQBot 插件已更新至 v${NEW_VERSION},在线等候你的吩咐。"
525
+ fi
526
+ else
527
+ echo " ⚠️ gateway 重启失败,请手动执行: $CMD gateway restart"
528
+ fi