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