@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.
- package/LICENSE +22 -0
- package/README.md +470 -0
- package/README.zh.md +465 -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 +264 -0
- package/dist/src/api.js +777 -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 +2021 -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 +86 -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 +926 -0
- package/dist/src/proactive.d.ts +170 -0
- package/dist/src/proactive.js +399 -0
- package/dist/src/ref-index-store.d.ts +70 -0
- package/dist/src/ref-index-store.js +250 -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 +18 -0
- package/dist/src/request-context.js +30 -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 +1461 -0
- package/dist/src/startup-greeting.d.ts +30 -0
- package/dist/src/startup-greeting.js +97 -0
- package/dist/src/streaming.d.ts +250 -0
- package/dist/src/streaming.js +914 -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 +248 -0
- package/dist/src/types.d.ts +364 -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 +34 -0
- package/dist/src/update-checker.js +160 -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 +59 -0
- package/dist/src/utils/chunked-upload.js +289 -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 +148 -0
- package/dist/src/utils/media-send.js +456 -0
- package/dist/src/utils/media-tags.d.ts +14 -0
- package/dist/src/utils/media-tags.js +164 -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 +51 -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 +32 -0
- package/dist/src/utils/text-parsing.js +69 -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 +13 -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 +69 -0
- package/node_modules/ws/wrapper.mjs +8 -0
- package/openclaw.plugin.json +17 -0
- package/package.json +67 -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 +113 -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 +451 -0
- package/scripts/upgrade-via-npm.sh +528 -0
- package/scripts/upgrade-via-source.sh +916 -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 +149 -0
- package/src/admin-resolver.ts +181 -0
- package/src/api.ts +1138 -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 +2257 -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 +349 -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 +1119 -0
- package/src/proactive.ts +530 -0
- package/src/ref-index-store.ts +335 -0
- package/src/reply-dispatcher.ts +334 -0
- package/src/request-context.ts +39 -0
- package/src/runtime.ts +14 -0
- package/src/session-store.ts +303 -0
- package/src/slash-commands.ts +1615 -0
- package/src/startup-greeting.ts +120 -0
- package/src/streaming.ts +1102 -0
- package/src/stt.ts +86 -0
- package/src/tools/channel.ts +281 -0
- package/src/tools/remind.ts +300 -0
- package/src/types.ts +386 -0
- package/src/typing-keepalive.ts +59 -0
- package/src/update-checker.ts +174 -0
- package/src/utils/audio-convert.ts +859 -0
- package/src/utils/chunked-upload.ts +419 -0
- package/src/utils/file-utils.ts +193 -0
- package/src/utils/image-size.ts +266 -0
- package/src/utils/media-send.ts +585 -0
- package/src/utils/media-tags.ts +182 -0
- package/src/utils/payload.ts +265 -0
- package/src/utils/pkg-version.ts +54 -0
- package/src/utils/platform.ts +435 -0
- package/src/utils/ssrf-guard.ts +102 -0
- package/src/utils/text-parsing.ts +75 -0
- package/src/utils/upload-cache.ts +128 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,916 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# qqbot 一键更新并启动脚本
|
|
4
|
+
# 版本: 2.0 (增强错误处理版)
|
|
5
|
+
#
|
|
6
|
+
# 主要改进:
|
|
7
|
+
# 1. 详细的安装错误诊断和排查建议
|
|
8
|
+
# 2. 所有关键步骤的错误捕获和报告
|
|
9
|
+
# 3. 日志文件保存和错误摘要
|
|
10
|
+
# 4. 智能故障排查指南
|
|
11
|
+
# 5. 用户友好的交互提示
|
|
12
|
+
|
|
13
|
+
set -eo pipefail
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
+
# 如果脚本在 scripts/ 子目录里,往上一级就是项目根目录
|
|
17
|
+
if [ "$(basename "$SCRIPT_DIR")" = "scripts" ]; then
|
|
18
|
+
PROJ_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
19
|
+
else
|
|
20
|
+
PROJ_DIR="$SCRIPT_DIR"
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# 安全检查:PROJ_DIR 不应指向 extensions 目录(那是安装副本,cleanup 会删除)
|
|
24
|
+
if echo "$PROJ_DIR" | grep -q '/.openclaw/extensions\|/.clawdbot/extensions\|/.moltbot/extensions'; then
|
|
25
|
+
echo "❌ 错误: 脚本正在从 extensions 安装副本运行,而非源码仓库"
|
|
26
|
+
echo " 当前路径: $PROJ_DIR"
|
|
27
|
+
echo ""
|
|
28
|
+
echo "请从源码仓库运行:"
|
|
29
|
+
echo " cd /path/to/openclaw-qqbot && ./scripts/upgrade-via-source.sh"
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
cd "$PROJ_DIR"
|
|
34
|
+
|
|
35
|
+
# 解析命令行参数
|
|
36
|
+
APPID=""
|
|
37
|
+
SECRET=""
|
|
38
|
+
MARKDOWN=""
|
|
39
|
+
|
|
40
|
+
while [[ $# -gt 0 ]]; do
|
|
41
|
+
case $1 in
|
|
42
|
+
--appid)
|
|
43
|
+
APPID="$2"
|
|
44
|
+
shift 2
|
|
45
|
+
;;
|
|
46
|
+
--secret)
|
|
47
|
+
SECRET="$2"
|
|
48
|
+
shift 2
|
|
49
|
+
;;
|
|
50
|
+
--markdown)
|
|
51
|
+
MARKDOWN="$2"
|
|
52
|
+
shift 2
|
|
53
|
+
;;
|
|
54
|
+
-h|--help)
|
|
55
|
+
echo "用法: $0 [选项]"
|
|
56
|
+
echo ""
|
|
57
|
+
echo "选项:"
|
|
58
|
+
echo " --appid <appid> QQ机器人 appid"
|
|
59
|
+
echo " --secret <secret> QQ机器人 secret"
|
|
60
|
+
echo " --markdown <yes|no> 是否启用 markdown 消息格式(默认: no)"
|
|
61
|
+
echo " -h, --help 显示帮助信息"
|
|
62
|
+
echo ""
|
|
63
|
+
echo "也可以通过环境变量设置:"
|
|
64
|
+
echo " QQBOT_APPID QQ机器人 appid"
|
|
65
|
+
echo " QQBOT_SECRET QQ机器人 secret"
|
|
66
|
+
echo " QQBOT_TOKEN QQ机器人 token (appid:secret)"
|
|
67
|
+
echo " QQBOT_MARKDOWN 是否启用 markdown(yes/no)"
|
|
68
|
+
echo ""
|
|
69
|
+
echo "不带参数时,将使用已有配置直接启动。"
|
|
70
|
+
echo ""
|
|
71
|
+
echo "⚠️ 注意: 启用 markdown 需要在 QQ 开放平台申请 markdown 消息权限"
|
|
72
|
+
exit 0
|
|
73
|
+
;;
|
|
74
|
+
*)
|
|
75
|
+
echo "未知选项: $1"
|
|
76
|
+
echo "使用 --help 查看帮助信息"
|
|
77
|
+
exit 1
|
|
78
|
+
;;
|
|
79
|
+
esac
|
|
80
|
+
done
|
|
81
|
+
|
|
82
|
+
# 使用命令行参数或环境变量
|
|
83
|
+
APPID="${APPID:-$QQBOT_APPID}"
|
|
84
|
+
SECRET="${SECRET:-$QQBOT_SECRET}"
|
|
85
|
+
MARKDOWN="${MARKDOWN:-$QQBOT_MARKDOWN}"
|
|
86
|
+
|
|
87
|
+
echo "========================================="
|
|
88
|
+
echo " qqbot 一键更新启动脚本"
|
|
89
|
+
echo "========================================="
|
|
90
|
+
|
|
91
|
+
# 1. 备份已有 qqbot 通道配置,防止升级过程丢失
|
|
92
|
+
echo ""
|
|
93
|
+
echo "[1/6] 备份已有配置..."
|
|
94
|
+
SAVED_QQBOT_TOKEN=""
|
|
95
|
+
SAVED_QQBOT_CONFIG_FILE="" # 有 qqbot 配置的文件路径
|
|
96
|
+
SAVED_QQBOT_CHANNEL_JSON="" # 完整 channels.qqbot JSON
|
|
97
|
+
|
|
98
|
+
# 完整备份 channels.qqbot 对象(含 token / groups / env / allowFrom 等)
|
|
99
|
+
for APP_NAME in openclaw clawdbot moltbot; do
|
|
100
|
+
CONFIG_FILE="$HOME/.$APP_NAME/$APP_NAME.json"
|
|
101
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
102
|
+
SAVED_QQBOT_CHANNEL_JSON=$(node -e "
|
|
103
|
+
const cfg = JSON.parse(require('fs').readFileSync('$CONFIG_FILE', 'utf8'));
|
|
104
|
+
const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
|
|
105
|
+
for (const key of keys) {
|
|
106
|
+
const ch = cfg.channels && cfg.channels[key];
|
|
107
|
+
if (ch && Object.keys(ch).length > 0) {
|
|
108
|
+
process.stdout.write(JSON.stringify(ch));
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
" 2>/dev/null || true)
|
|
113
|
+
if [ -n "$SAVED_QQBOT_CHANNEL_JSON" ]; then
|
|
114
|
+
SAVED_QQBOT_CONFIG_FILE="$CONFIG_FILE"
|
|
115
|
+
# 提取 token 供 Step 4 fallback
|
|
116
|
+
SAVED_QQBOT_TOKEN=$(node -e "
|
|
117
|
+
const ch = JSON.parse(process.argv[1]);
|
|
118
|
+
if (ch.token) { process.stdout.write(ch.token); }
|
|
119
|
+
else if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId + ':' + ch.clientSecret); }
|
|
120
|
+
" "$SAVED_QQBOT_CHANNEL_JSON" 2>/dev/null || true)
|
|
121
|
+
echo "已备份完整 qqbot 通道配置"
|
|
122
|
+
if [ -n "$SAVED_QQBOT_TOKEN" ]; then
|
|
123
|
+
echo " token: ${SAVED_QQBOT_TOKEN:0:10}..."
|
|
124
|
+
fi
|
|
125
|
+
break
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
done
|
|
129
|
+
|
|
130
|
+
# 若当前配置中没有,从 openclaw 备份文件恢复
|
|
131
|
+
if [ -z "$SAVED_QQBOT_CHANNEL_JSON" ] && [ -d "$HOME/.openclaw" ]; then
|
|
132
|
+
SAVED_QQBOT_CHANNEL_JSON=$(node -e "
|
|
133
|
+
const fs = require('fs');
|
|
134
|
+
const path = require('path');
|
|
135
|
+
const dir = path.join(process.env.HOME, '.openclaw');
|
|
136
|
+
const files = fs.readdirSync(dir)
|
|
137
|
+
.filter((n) => /^openclaw\.json\.bak(\.\d+)?$/.test(n))
|
|
138
|
+
.map((n) => path.join(dir, n))
|
|
139
|
+
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
|
|
140
|
+
for (const f of files) {
|
|
141
|
+
try {
|
|
142
|
+
const cfg = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
143
|
+
const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
|
|
144
|
+
for (const key of keys) {
|
|
145
|
+
const ch = cfg.channels && cfg.channels[key];
|
|
146
|
+
if (ch && Object.keys(ch).length > 0) {
|
|
147
|
+
process.stdout.write(JSON.stringify(ch));
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch {}
|
|
152
|
+
}
|
|
153
|
+
" 2>/dev/null || true)
|
|
154
|
+
|
|
155
|
+
if [ -n "$SAVED_QQBOT_CHANNEL_JSON" ]; then
|
|
156
|
+
SAVED_QQBOT_TOKEN=$(node -e "
|
|
157
|
+
const ch = JSON.parse(process.argv[1]);
|
|
158
|
+
if (ch.token) { process.stdout.write(ch.token); }
|
|
159
|
+
else if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId + ':' + ch.clientSecret); }
|
|
160
|
+
" "$SAVED_QQBOT_CHANNEL_JSON" 2>/dev/null || true)
|
|
161
|
+
echo "已从 ~/.openclaw/openclaw.json.bak* 恢复 qqbot 通道配置"
|
|
162
|
+
if [ -n "$SAVED_QQBOT_TOKEN" ]; then
|
|
163
|
+
echo " token: ${SAVED_QQBOT_TOKEN:0:10}..."
|
|
164
|
+
fi
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
# 2. 移除老版本
|
|
169
|
+
echo ""
|
|
170
|
+
echo "[2/6] 移除老版本..."
|
|
171
|
+
if [ -f "$PROJ_DIR/scripts/cleanup-legacy-plugins.sh" ]; then
|
|
172
|
+
bash "$PROJ_DIR/scripts/cleanup-legacy-plugins.sh"
|
|
173
|
+
else
|
|
174
|
+
echo "警告: cleanup-legacy-plugins.sh 不存在,跳过移除步骤"
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
# cleanup 可能删除了当前 cwd 所在的目录(如 extensions 下的旧副本),
|
|
178
|
+
# 重新 cd 回源码目录以避免后续命令因 cwd 悬空而 ENOENT
|
|
179
|
+
cd "$PROJ_DIR"
|
|
180
|
+
|
|
181
|
+
# 3. 安装当前版本
|
|
182
|
+
echo ""
|
|
183
|
+
echo "[3/6] 安装当前版本(源码安装)..."
|
|
184
|
+
|
|
185
|
+
echo "检查当前目录: $(pwd)"
|
|
186
|
+
echo "检查openclaw版本: $(openclaw --version 2>/dev/null || echo 'openclaw not found')"
|
|
187
|
+
|
|
188
|
+
LOCAL_PACKAGE_VERSION=$(node -e "
|
|
189
|
+
try {
|
|
190
|
+
const fs = require('fs');
|
|
191
|
+
const path = require('path');
|
|
192
|
+
const p = path.join('$PROJ_DIR', 'package.json');
|
|
193
|
+
const v = JSON.parse(fs.readFileSync(p, 'utf8')).version;
|
|
194
|
+
if (v) process.stdout.write(String(v));
|
|
195
|
+
} catch {}
|
|
196
|
+
" 2>/dev/null || true)
|
|
197
|
+
if [ -n "$LOCAL_PACKAGE_VERSION" ]; then
|
|
198
|
+
echo "即将安装本地源码版本: $LOCAL_PACKAGE_VERSION"
|
|
199
|
+
else
|
|
200
|
+
echo "即将安装本地源码版本: unknown(未读取到 package.json version)"
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# 记录更新前的 qqbot 插件版本
|
|
204
|
+
OLD_QQBOT_VERSION=$(node -e '
|
|
205
|
+
try {
|
|
206
|
+
const fs = require("fs");
|
|
207
|
+
const path = require("path");
|
|
208
|
+
const candidates = ["openclaw-qqbot", "qqbot", "openclaw-qq"];
|
|
209
|
+
for (const name of candidates) {
|
|
210
|
+
const pkgPath = path.join(process.env.HOME, ".openclaw", "extensions", name, "package.json");
|
|
211
|
+
if (!fs.existsSync(pkgPath)) continue;
|
|
212
|
+
const p = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
213
|
+
process.stdout.write(p.version || "unknown");
|
|
214
|
+
process.exit(0);
|
|
215
|
+
}
|
|
216
|
+
process.stdout.write("not_installed");
|
|
217
|
+
} catch(e) { process.stdout.write("not_installed"); }
|
|
218
|
+
' 2>/dev/null || echo "not_installed")
|
|
219
|
+
|
|
220
|
+
echo "开始安装插件..."
|
|
221
|
+
echo "安装来源: 当前仓库源码(openclaw plugins install .)"
|
|
222
|
+
INSTALL_LOG="/tmp/openclaw-install-$(date +%s).log"
|
|
223
|
+
|
|
224
|
+
echo "安装日志文件: $INSTALL_LOG"
|
|
225
|
+
echo "详细信息将记录到日志文件中..."
|
|
226
|
+
|
|
227
|
+
# ── 临时移除 channels.qqbot 配置 ──
|
|
228
|
+
# openclaw CLI 任何子命令(包括 gateway stop、plugins install)启动时都会校验 openclaw.json,
|
|
229
|
+
# 如果 channels.qqbot 存在但插件还未安装,CLI 不认识该 channel id,
|
|
230
|
+
# 导致 "Config invalid: unknown channel id: qqbot" 而命令失败(鸡生蛋问题)。
|
|
231
|
+
# 解决方案:在所有 openclaw 命令之前把 channels.qqbot 暂存,完成后恢复。
|
|
232
|
+
_QQBOT_CHANNEL_STASH=""
|
|
233
|
+
for _app in openclaw clawdbot moltbot; do
|
|
234
|
+
_cfg="$HOME/.$_app/$_app.json"
|
|
235
|
+
if [ -f "$_cfg" ]; then
|
|
236
|
+
_QQBOT_CHANNEL_STASH=$(node -e "
|
|
237
|
+
const fs = require('fs');
|
|
238
|
+
const cfg = JSON.parse(fs.readFileSync('$_cfg', 'utf8'));
|
|
239
|
+
if (cfg.channels && cfg.channels.qqbot) {
|
|
240
|
+
const stashed = JSON.stringify(cfg.channels.qqbot);
|
|
241
|
+
delete cfg.channels.qqbot;
|
|
242
|
+
fs.writeFileSync('$_cfg', JSON.stringify(cfg, null, 4) + '\n');
|
|
243
|
+
process.stdout.write(stashed);
|
|
244
|
+
}
|
|
245
|
+
" 2>/dev/null || true)
|
|
246
|
+
if [ -n "$_QQBOT_CHANNEL_STASH" ]; then
|
|
247
|
+
_STASH_APP="$_app"
|
|
248
|
+
_STASH_CFG="$_cfg"
|
|
249
|
+
echo " 已暂存 channels.qqbot 配置(避免 CLI 校验失败)"
|
|
250
|
+
fi
|
|
251
|
+
break
|
|
252
|
+
fi
|
|
253
|
+
done
|
|
254
|
+
|
|
255
|
+
# 安装前先 stop gateway,防止 chokidar 在 plugins install 写入配置的中间状态
|
|
256
|
+
# 触发 restart,导致 "unknown channel id: qqbot" 等错误
|
|
257
|
+
_gw_was_running=0
|
|
258
|
+
if lsof -i :18789 -sTCP:LISTEN >/dev/null 2>&1; then
|
|
259
|
+
_gw_was_running=1
|
|
260
|
+
echo " 暂停 gateway 服务(避免安装过程中中间状态 restart)..."
|
|
261
|
+
openclaw gateway stop 2>/dev/null || true
|
|
262
|
+
sleep 1
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
# 清理之前可能残留的 staging 目录
|
|
266
|
+
find "$HOME/.openclaw/extensions/" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -rf {} + 2>/dev/null || true
|
|
267
|
+
|
|
268
|
+
# ── 清空并重新构建 dist/ ──
|
|
269
|
+
# openclaw plugins install . 只做文件复制,不执行 npm lifecycle scripts。
|
|
270
|
+
# 必须每次清空 dist/ 再重新构建,否则旧的编译产物会被原样拷贝到安装目录,
|
|
271
|
+
# 导致新增的模块(如 streaming.js)缺失。
|
|
272
|
+
echo " 清空 dist/ 确保全量重新构建..."
|
|
273
|
+
rm -rf "$PROJ_DIR/dist"
|
|
274
|
+
if [ ! -f "$PROJ_DIR/dist/index.js" ]; then
|
|
275
|
+
echo " dist/ 不存在,先执行构建..."
|
|
276
|
+
if [ ! -d "$PROJ_DIR/node_modules" ]; then
|
|
277
|
+
npm install --prefix "$PROJ_DIR" 2>&1 || true
|
|
278
|
+
fi
|
|
279
|
+
npm run --prefix "$PROJ_DIR" build 2>&1 || true
|
|
280
|
+
if [ ! -f "$PROJ_DIR/dist/index.js" ]; then
|
|
281
|
+
echo " ❌ 构建失败:dist/index.js 未生成"
|
|
282
|
+
echo " 请手动执行: cd $PROJ_DIR && npm install && npm run build"
|
|
283
|
+
exit 1
|
|
284
|
+
fi
|
|
285
|
+
echo " ✅ 构建完成"
|
|
286
|
+
fi
|
|
287
|
+
|
|
288
|
+
# 尝试安装并捕获详细输出
|
|
289
|
+
# 注意:openclaw plugins install 会在安装后尝试验证加载插件,
|
|
290
|
+
# 由于 CJS/ESM 混用问题(Node 22 ERR_INTERNAL_ASSERTION),验证阶段可能失败
|
|
291
|
+
# 但文件已成功拷贝。因此不依赖退出码,而是检查安装目录中关键文件是否存在。
|
|
292
|
+
_INSTALL_DIR="$HOME/.openclaw/extensions/openclaw-qqbot"
|
|
293
|
+
openclaw plugins install . 2>&1 | tee "$INSTALL_LOG" || true
|
|
294
|
+
if [ ! -f "$_INSTALL_DIR/dist/index.js" ] || [ ! -f "$_INSTALL_DIR/preload.cjs" ]; then
|
|
295
|
+
echo ""
|
|
296
|
+
echo "❌ 插件安装失败!"
|
|
297
|
+
echo "========================================="
|
|
298
|
+
echo "故障排查信息:"
|
|
299
|
+
echo "========================================="
|
|
300
|
+
|
|
301
|
+
# 分析错误原因
|
|
302
|
+
echo "1. 检查日志文件末尾: $INSTALL_LOG"
|
|
303
|
+
echo "2. 常见原因分析:"
|
|
304
|
+
|
|
305
|
+
# 检查网络连接
|
|
306
|
+
echo " - 网络问题: 测试 npm 仓库连接"
|
|
307
|
+
echo " curl -I https://registry.npmjs.org/ || curl -I https://registry.npmmirror.com/"
|
|
308
|
+
|
|
309
|
+
# 检查权限
|
|
310
|
+
echo " - 权限问题: 检查安装目录权限"
|
|
311
|
+
echo " ls -la ~/.openclaw/ 2>/dev/null || echo '目录不存在'"
|
|
312
|
+
|
|
313
|
+
# 检查npm配置
|
|
314
|
+
echo " - npm配置: 检查当前npm配置"
|
|
315
|
+
echo " npm config get registry"
|
|
316
|
+
|
|
317
|
+
# 显示错误摘要
|
|
318
|
+
echo ""
|
|
319
|
+
echo "3. 错误摘要:"
|
|
320
|
+
tail -20 "$INSTALL_LOG" | grep -i -E "(error|fail|warn|npm install)"
|
|
321
|
+
|
|
322
|
+
echo ""
|
|
323
|
+
echo "4. 可选解决方案:"
|
|
324
|
+
echo " a. 更换npm镜像源:"
|
|
325
|
+
echo " npm config set registry https://registry.npmmirror.com/"
|
|
326
|
+
echo " b. 清理npm缓存:"
|
|
327
|
+
echo " npm cache clean --force"
|
|
328
|
+
echo " c. 手动安装依赖:"
|
|
329
|
+
echo " cd $(pwd) && npm install --verbose"
|
|
330
|
+
|
|
331
|
+
echo ""
|
|
332
|
+
echo "========================================="
|
|
333
|
+
echo "建议: 先查看完整日志文件: cat $INSTALL_LOG"
|
|
334
|
+
echo "或者尝试手动安装: cd $(pwd) && npm install"
|
|
335
|
+
echo "========================================="
|
|
336
|
+
|
|
337
|
+
read -t 10 -p "是否继续配置其他步骤? (y/N): " continue_choice || continue_choice="N"
|
|
338
|
+
case "$continue_choice" in
|
|
339
|
+
[Yy]* )
|
|
340
|
+
echo "继续执行后续配置步骤..."
|
|
341
|
+
;;
|
|
342
|
+
* )
|
|
343
|
+
echo "安装失败,脚本退出。"
|
|
344
|
+
echo "请先解决安装问题后再运行此脚本。"
|
|
345
|
+
# 恢复 channels.qqbot 后再退出
|
|
346
|
+
if [ -n "$_QQBOT_CHANNEL_STASH" ] && [ -n "$_STASH_CFG" ] && [ -f "$_STASH_CFG" ]; then
|
|
347
|
+
node -e "
|
|
348
|
+
const fs = require('fs');
|
|
349
|
+
const cfg = JSON.parse(fs.readFileSync('$_STASH_CFG', 'utf8'));
|
|
350
|
+
if (!cfg.channels) cfg.channels = {};
|
|
351
|
+
cfg.channels.qqbot = $_QQBOT_CHANNEL_STASH;
|
|
352
|
+
fs.writeFileSync('$_STASH_CFG', JSON.stringify(cfg, null, 4) + '\n');
|
|
353
|
+
" 2>/dev/null || true
|
|
354
|
+
fi
|
|
355
|
+
exit 1
|
|
356
|
+
;;
|
|
357
|
+
esac
|
|
358
|
+
else
|
|
359
|
+
echo ""
|
|
360
|
+
echo "✅ 插件安装命令执行完成"
|
|
361
|
+
echo "安装日志已保存到: $INSTALL_LOG"
|
|
362
|
+
|
|
363
|
+
# 确保 openclaw.json 中的 source 为 path(从 npm 切回 path)
|
|
364
|
+
for _app in openclaw clawdbot moltbot; do
|
|
365
|
+
_cfg="$HOME/.$_app/$_app.json"
|
|
366
|
+
if [ -f "$_cfg" ]; then
|
|
367
|
+
node -e "
|
|
368
|
+
const fs = require('fs');
|
|
369
|
+
const cfg = JSON.parse(fs.readFileSync('$_cfg', 'utf8'));
|
|
370
|
+
const inst = cfg.plugins && cfg.plugins.installs && cfg.plugins.installs['openclaw-qqbot'];
|
|
371
|
+
if (inst && inst.source !== 'path') {
|
|
372
|
+
inst.source = 'path';
|
|
373
|
+
inst.sourcePath = '$PROJ_DIR';
|
|
374
|
+
fs.writeFileSync('$_cfg', JSON.stringify(cfg, null, 4) + '\n');
|
|
375
|
+
console.log(' 已将 plugins.installs.openclaw-qqbot.source 更新为 path');
|
|
376
|
+
}
|
|
377
|
+
" 2>/dev/null || true
|
|
378
|
+
break
|
|
379
|
+
fi
|
|
380
|
+
done
|
|
381
|
+
|
|
382
|
+
# 验证插件目录是否真正创建(防止 "安装成功" 但目录缺失的情况)
|
|
383
|
+
_plugin_dir_ok=0
|
|
384
|
+
for _candidate_name in openclaw-qqbot qqbot openclaw-qq; do
|
|
385
|
+
if [ -d "$HOME/.openclaw/extensions/$_candidate_name" ] && \
|
|
386
|
+
[ -f "$HOME/.openclaw/extensions/$_candidate_name/package.json" ]; then
|
|
387
|
+
_plugin_dir_ok=1
|
|
388
|
+
echo " ✅ 插件目录验证通过: ~/.openclaw/extensions/$_candidate_name/"
|
|
389
|
+
break
|
|
390
|
+
fi
|
|
391
|
+
done
|
|
392
|
+
if [ "$_plugin_dir_ok" -eq 0 ]; then
|
|
393
|
+
echo ""
|
|
394
|
+
echo "⚠️ 警告: 插件目录不存在!安装命令返回成功但目录未创建"
|
|
395
|
+
echo " 可能原因: staging 目录未正确 rename(参考 openclaw/issues)"
|
|
396
|
+
echo " 检查 staging 残留:"
|
|
397
|
+
ls -la "$HOME/.openclaw/extensions/" 2>/dev/null
|
|
398
|
+
echo ""
|
|
399
|
+
echo " 尝试自动修复: 清理残留并重试安装..."
|
|
400
|
+
find "$HOME/.openclaw/extensions/" -maxdepth 1 -name ".openclaw-install-stage-*" -exec rm -rf {} + 2>/dev/null || true
|
|
401
|
+
if openclaw plugins install . 2>&1 | tee -a "$INSTALL_LOG"; then
|
|
402
|
+
for _candidate_name in openclaw-qqbot qqbot openclaw-qq; do
|
|
403
|
+
if [ -d "$HOME/.openclaw/extensions/$_candidate_name" ]; then
|
|
404
|
+
_plugin_dir_ok=1
|
|
405
|
+
echo " ✅ 重试安装成功: ~/.openclaw/extensions/$_candidate_name/"
|
|
406
|
+
break
|
|
407
|
+
fi
|
|
408
|
+
done
|
|
409
|
+
fi
|
|
410
|
+
if [ "$_plugin_dir_ok" -eq 0 ]; then
|
|
411
|
+
echo " ❌ 重试安装仍失败,插件目录不存在"
|
|
412
|
+
echo " 请手动排查: ls -la ~/.openclaw/extensions/"
|
|
413
|
+
# 清理无效的配置条目,防止 gateway 启动时报错
|
|
414
|
+
echo " 清理无效的配置条目..."
|
|
415
|
+
node -e "
|
|
416
|
+
const fs = require('fs');
|
|
417
|
+
const path = require('path');
|
|
418
|
+
for (const app of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
419
|
+
const f = path.join(process.env.HOME, '.' + app, app + '.json');
|
|
420
|
+
if (!fs.existsSync(f)) continue;
|
|
421
|
+
const cfg = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
422
|
+
let changed = false;
|
|
423
|
+
// 移除 channels.qqbot(插件未加载时此配置会导致 unknown channel id 错误)
|
|
424
|
+
if (cfg.channels && cfg.channels.qqbot) { delete cfg.channels.qqbot; changed = true; }
|
|
425
|
+
// 清理 plugins.allow 中的 openclaw-qqbot
|
|
426
|
+
if (cfg.plugins && Array.isArray(cfg.plugins.allow)) {
|
|
427
|
+
cfg.plugins.allow = cfg.plugins.allow.filter(x => x !== 'openclaw-qqbot');
|
|
428
|
+
changed = true;
|
|
429
|
+
}
|
|
430
|
+
if (changed) fs.writeFileSync(f, JSON.stringify(cfg, null, 4) + '\n');
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
" 2>/dev/null || true
|
|
434
|
+
echo " 已清理无效配置,gateway 可正常启动(无 qqbot 插件状态)"
|
|
435
|
+
read -t 10 -p "是否继续? (y/N): " _cont || _cont="N"
|
|
436
|
+
case "$_cont" in
|
|
437
|
+
[Yy]* ) echo "继续..." ;;
|
|
438
|
+
* ) exit 1 ;;
|
|
439
|
+
esac
|
|
440
|
+
fi
|
|
441
|
+
fi
|
|
442
|
+
|
|
443
|
+
# 清理多余的 peerDependencies 传递依赖(兼容旧版 openclaw):
|
|
444
|
+
# openclaw v2026.3.4 之前的 plugins install 缺少 --omit=peer,会把 peerDeps
|
|
445
|
+
# (openclaw 平台及其 400+ 传递依赖)也安装到插件 node_modules 中。
|
|
446
|
+
# 新版已修复,此处通过阈值判断:包数量 > 50 才触发清理,避免对新版做无用操作。
|
|
447
|
+
PLUGIN_NM=""
|
|
448
|
+
for _candidate in openclaw-qqbot qqbot openclaw-qq; do
|
|
449
|
+
_nm="$HOME/.openclaw/extensions/$_candidate/node_modules"
|
|
450
|
+
[ -d "$_nm" ] && PLUGIN_NM="$_nm" && break
|
|
451
|
+
done
|
|
452
|
+
if [ -n "$PLUGIN_NM" ]; then
|
|
453
|
+
_before=$(ls -d "$PLUGIN_NM"/*/ "$PLUGIN_NM"/@*/*/ 2>/dev/null | wc -l | tr -d ' ')
|
|
454
|
+
if [ "$_before" -gt 50 ]; then
|
|
455
|
+
# 读取 bundledDependencies 列表,只保留这些包及其子依赖
|
|
456
|
+
_bundled_deps=$(node -e "
|
|
457
|
+
const fs = require('fs');
|
|
458
|
+
const path = require('path');
|
|
459
|
+
const pkgPath = path.join('$PLUGIN_NM', '..', 'package.json');
|
|
460
|
+
if (!fs.existsSync(pkgPath)) process.exit(0);
|
|
461
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
462
|
+
const bundled = pkg.bundledDependencies || pkg.bundleDependencies || [];
|
|
463
|
+
const keep = new Set();
|
|
464
|
+
const resolve = (name) => {
|
|
465
|
+
if (keep.has(name)) return;
|
|
466
|
+
keep.add(name);
|
|
467
|
+
const depPkg = path.join('$PLUGIN_NM', name, 'package.json');
|
|
468
|
+
if (!fs.existsSync(depPkg)) return;
|
|
469
|
+
const dep = JSON.parse(fs.readFileSync(depPkg, 'utf8'));
|
|
470
|
+
for (const d of Object.keys(dep.dependencies || {})) resolve(d);
|
|
471
|
+
};
|
|
472
|
+
bundled.forEach(resolve);
|
|
473
|
+
const installed = fs.readdirSync('$PLUGIN_NM').filter(n => !n.startsWith('.'));
|
|
474
|
+
const toRemove = [];
|
|
475
|
+
for (const item of installed) {
|
|
476
|
+
if (item.startsWith('@')) {
|
|
477
|
+
const scopeDir = path.join('$PLUGIN_NM', item);
|
|
478
|
+
const subs = fs.readdirSync(scopeDir);
|
|
479
|
+
const keepSubs = subs.filter(s => keep.has(item + '/' + s));
|
|
480
|
+
if (keepSubs.length === 0) toRemove.push(item);
|
|
481
|
+
else {
|
|
482
|
+
for (const s of subs) {
|
|
483
|
+
if (!keep.has(item + '/' + s)) toRemove.push(item + '/' + s);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
if (!keep.has(item)) toRemove.push(item);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
process.stdout.write(toRemove.join('\n'));
|
|
491
|
+
" 2>/dev/null || true)
|
|
492
|
+
if [ -n "$_bundled_deps" ]; then
|
|
493
|
+
echo ""
|
|
494
|
+
echo "检测到 ${_before} 个包(超过阈值 50),清理多余的 peerDep 传递依赖..."
|
|
495
|
+
echo "$_bundled_deps" | while IFS= read -r _pkg; do
|
|
496
|
+
rm -rf "$PLUGIN_NM/$_pkg"
|
|
497
|
+
done
|
|
498
|
+
find "$PLUGIN_NM" -maxdepth 1 -type d -name '@*' -empty -delete 2>/dev/null || true
|
|
499
|
+
_after=$(ls -d "$PLUGIN_NM"/*/ "$PLUGIN_NM"/@*/*/ 2>/dev/null | wc -l | tr -d ' ')
|
|
500
|
+
echo " 已清理: ${_before} → ${_after} 个包"
|
|
501
|
+
fi
|
|
502
|
+
else
|
|
503
|
+
echo " node_modules 包数量正常(${_before} 个),无需清理"
|
|
504
|
+
fi
|
|
505
|
+
fi
|
|
506
|
+
|
|
507
|
+
# gateway 已在安装前 stop,此时不会有自动 restart 的问题
|
|
508
|
+
# 所有配置写入完成后,在 Step 6 统一启动
|
|
509
|
+
|
|
510
|
+
# 确保 openclaw/plugin-sdk 可解析:
|
|
511
|
+
# openclaw plugins install 不会执行 npm lifecycle scripts,
|
|
512
|
+
# 需要手动调用 postinstall-link-sdk.js 创建 node_modules/openclaw → 全局 openclaw 的符号链接。
|
|
513
|
+
# 必须在 peerDeps 清理之后执行,否则 symlink 会被清理逻辑删除。
|
|
514
|
+
for _candidate_name in openclaw-qqbot qqbot openclaw-qq; do
|
|
515
|
+
_postinstall="$HOME/.openclaw/extensions/$_candidate_name/scripts/postinstall-link-sdk.js"
|
|
516
|
+
if [ -f "$_postinstall" ]; then
|
|
517
|
+
echo " 执行 postinstall-link-sdk..."
|
|
518
|
+
if node "$_postinstall" 2>&1; then
|
|
519
|
+
echo " ✅ plugin-sdk 链接就绪"
|
|
520
|
+
else
|
|
521
|
+
echo " ⚠️ postinstall-link-sdk 失败,插件可能无法加载"
|
|
522
|
+
fi
|
|
523
|
+
break
|
|
524
|
+
fi
|
|
525
|
+
done
|
|
526
|
+
|
|
527
|
+
# 清理 openclaw CLI install 留下的 backup 目录,
|
|
528
|
+
# 避免 gateway 发现两个同 id 插件不断刷 duplicate plugin id 警告
|
|
529
|
+
find "$HOME/.openclaw/extensions/" -maxdepth 1 -name ".openclaw-qqbot-backup-*" -exec rm -rf {} + 2>/dev/null || true
|
|
530
|
+
|
|
531
|
+
# 恢复 channels.qqbot 完整配置(防止 plugins install 意外覆盖)
|
|
532
|
+
# 策略:直接用备份完整覆盖回去,确保 groups / env / prompts / allowFrom 等所有用户配置不丢失。
|
|
533
|
+
if [ -n "$SAVED_QQBOT_CHANNEL_JSON" ]; then
|
|
534
|
+
node -e "
|
|
535
|
+
const fs = require('fs');
|
|
536
|
+
const path = require('path');
|
|
537
|
+
const saved = JSON.parse(process.argv[1]);
|
|
538
|
+
for (const app of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
539
|
+
const f = path.join(process.env.HOME, '.' + app, app + '.json');
|
|
540
|
+
if (!fs.existsSync(f)) continue;
|
|
541
|
+
const cfg = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
542
|
+
if (!cfg.channels) { cfg.channels = {}; }
|
|
543
|
+
if (JSON.stringify(cfg.channels.qqbot) === JSON.stringify(saved)) {
|
|
544
|
+
process.stderr.write(' channels.qqbot 配置无变化,无需恢复\\n');
|
|
545
|
+
} else {
|
|
546
|
+
cfg.channels.qqbot = saved;
|
|
547
|
+
fs.writeFileSync(f, JSON.stringify(cfg, null, 4) + '\\n');
|
|
548
|
+
process.stderr.write(' 已完整恢复 channels.qqbot 配置(' + Object.keys(saved).join(', ') + ')\\n');
|
|
549
|
+
}
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
" "$SAVED_QQBOT_CHANNEL_JSON" 2>&1 || true
|
|
553
|
+
fi
|
|
554
|
+
|
|
555
|
+
# 记录更新后的 qqbot 插件版本
|
|
556
|
+
NEW_QQBOT_VERSION=$(node -e '
|
|
557
|
+
try {
|
|
558
|
+
const fs = require("fs");
|
|
559
|
+
const path = require("path");
|
|
560
|
+
const candidates = ["openclaw-qqbot", "qqbot", "openclaw-qq"];
|
|
561
|
+
for (const name of candidates) {
|
|
562
|
+
const pkgPath = path.join(process.env.HOME, ".openclaw", "extensions", name, "package.json");
|
|
563
|
+
if (!fs.existsSync(pkgPath)) continue;
|
|
564
|
+
const p = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
565
|
+
process.stdout.write(p.version || "unknown");
|
|
566
|
+
process.exit(0);
|
|
567
|
+
}
|
|
568
|
+
process.stdout.write("unknown");
|
|
569
|
+
} catch(e) { process.stdout.write("unknown"); }
|
|
570
|
+
' 2>/dev/null || echo "unknown")
|
|
571
|
+
fi
|
|
572
|
+
|
|
573
|
+
# ── 暂不恢复 channels.qqbot 配置 ──
|
|
574
|
+
# openclaw 3.23+ 启动时在插件加载前就校验 channels,
|
|
575
|
+
# 如果此时恢复 channels.qqbot,gateway 会因 "unknown channel id: qqbot" 拒绝启动。
|
|
576
|
+
# 延迟到 gateway 启动、插件加载完成后再恢复(见 Step 6)。
|
|
577
|
+
echo " [兼容] channels.qqbot 将在 gateway 启动后恢复(避免启动校验失败)"
|
|
578
|
+
|
|
579
|
+
# ── Step 4/5:不在此时写入 channels.qqbot 配置 ──
|
|
580
|
+
# openclaw 3.23+ 在插件加载前校验 channels,此时写入 channels.qqbot 会导致
|
|
581
|
+
# "unknown channel id: qqbot" 错误。所有 channels.qqbot 相关配置延迟到
|
|
582
|
+
# gateway 启动、插件加载完成后统一写入(见 Step 6 末尾)。
|
|
583
|
+
#
|
|
584
|
+
# 这里只计算出 DESIRED_QQBOT_TOKEN 和 MARKDOWN_VALUE,不实际写入。
|
|
585
|
+
|
|
586
|
+
# 4. 确定机器人通道配置
|
|
587
|
+
echo ""
|
|
588
|
+
echo "[4/6] 准备机器人通道配置..."
|
|
589
|
+
|
|
590
|
+
# 读取当前 qqbot token(从暂存或配置文件)
|
|
591
|
+
# 注意:channels.qqbot 已被暂存移除,所以从 _QQBOT_CHANNEL_STASH 读取
|
|
592
|
+
CURRENT_QQBOT_TOKEN=""
|
|
593
|
+
if [ -n "$_QQBOT_CHANNEL_STASH" ]; then
|
|
594
|
+
CURRENT_QQBOT_TOKEN=$(node -e "
|
|
595
|
+
const ch = $_QQBOT_CHANNEL_STASH;
|
|
596
|
+
if (ch.token) { process.stdout.write(ch.token); }
|
|
597
|
+
else if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId + ':' + ch.clientSecret); }
|
|
598
|
+
" 2>/dev/null || true)
|
|
599
|
+
fi
|
|
600
|
+
|
|
601
|
+
DESIRED_QQBOT_TOKEN=""
|
|
602
|
+
if [ -n "$APPID" ] && [ -n "$SECRET" ]; then
|
|
603
|
+
DESIRED_QQBOT_TOKEN="${APPID}:${SECRET}"
|
|
604
|
+
echo "使用提供的 appid 和 secret 配置..."
|
|
605
|
+
elif [ -n "$QQBOT_TOKEN" ]; then
|
|
606
|
+
DESIRED_QQBOT_TOKEN="$QQBOT_TOKEN"
|
|
607
|
+
echo "使用环境变量 QQBOT_TOKEN 配置..."
|
|
608
|
+
elif [ -n "$SAVED_QQBOT_TOKEN" ]; then
|
|
609
|
+
DESIRED_QQBOT_TOKEN="$SAVED_QQBOT_TOKEN"
|
|
610
|
+
echo "未提供 appid/secret,使用备份 token 恢复配置..."
|
|
611
|
+
fi
|
|
612
|
+
|
|
613
|
+
if [ -n "$DESIRED_QQBOT_TOKEN" ]; then
|
|
614
|
+
echo "目标 Token: ${DESIRED_QQBOT_TOKEN:0:10}..."
|
|
615
|
+
echo " [兼容] 将在 gateway 启动后写入 channels.qqbot"
|
|
616
|
+
elif [ -z "$CURRENT_QQBOT_TOKEN" ] && [ -z "$_QQBOT_CHANNEL_STASH" ]; then
|
|
617
|
+
echo ""
|
|
618
|
+
echo "❌ 未检测到 qqbot 通道配置!"
|
|
619
|
+
echo ""
|
|
620
|
+
echo "首次运行请提供 appid 和 appsecret:"
|
|
621
|
+
echo ""
|
|
622
|
+
echo " bash $0 --appid <你的appid> --secret <你的appsecret>"
|
|
623
|
+
echo ""
|
|
624
|
+
echo "也可以通过环境变量:"
|
|
625
|
+
echo ""
|
|
626
|
+
echo " QQBOT_APPID=<appid> QQBOT_SECRET=<appsecret> bash $0"
|
|
627
|
+
echo ""
|
|
628
|
+
echo "appid 和 appsecret 可在 QQ 开放平台 (https://q.qq.com) 获取。"
|
|
629
|
+
exit 1
|
|
630
|
+
else
|
|
631
|
+
echo "使用已有配置(暂存中)"
|
|
632
|
+
fi
|
|
633
|
+
|
|
634
|
+
# 5. 确定 markdown 选项
|
|
635
|
+
echo ""
|
|
636
|
+
echo "[5/6] 准备 markdown 配置..."
|
|
637
|
+
|
|
638
|
+
MARKDOWN_VALUE=""
|
|
639
|
+
if [ -n "$MARKDOWN" ]; then
|
|
640
|
+
if [ "$MARKDOWN" = "yes" ] || [ "$MARKDOWN" = "y" ] || [ "$MARKDOWN" = "true" ]; then
|
|
641
|
+
MARKDOWN_VALUE="true"
|
|
642
|
+
echo "将启用 markdown 消息格式"
|
|
643
|
+
else
|
|
644
|
+
MARKDOWN_VALUE="false"
|
|
645
|
+
echo "将禁用 markdown 消息格式(使用纯文本)"
|
|
646
|
+
fi
|
|
647
|
+
echo " [兼容] 将在 gateway 启动后写入配置"
|
|
648
|
+
else
|
|
649
|
+
echo "未指定 markdown 选项,使用已有配置"
|
|
650
|
+
fi
|
|
651
|
+
|
|
652
|
+
# 6. 启动 openclaw
|
|
653
|
+
echo ""
|
|
654
|
+
echo "[6/6] 启动 openclaw..."
|
|
655
|
+
echo "========================================="
|
|
656
|
+
|
|
657
|
+
# 检查openclaw是否可用
|
|
658
|
+
if ! command -v openclaw &> /dev/null; then
|
|
659
|
+
echo "❌ 错误: openclaw 命令未找到!"
|
|
660
|
+
echo ""
|
|
661
|
+
echo "可能的原因:"
|
|
662
|
+
echo "1. openclaw未安装或安装失败"
|
|
663
|
+
echo "2. PATH环境变量未包含openclaw路径"
|
|
664
|
+
echo "3. 需要重新登录或重启终端"
|
|
665
|
+
echo ""
|
|
666
|
+
exit 1
|
|
667
|
+
fi
|
|
668
|
+
|
|
669
|
+
echo "openclaw版本: $(openclaw --version 2>/dev/null || echo '未知')"
|
|
670
|
+
|
|
671
|
+
# 显示 qqbot 插件更新信息
|
|
672
|
+
NEW_QQBOT_VERSION="${NEW_QQBOT_VERSION:-unknown}"
|
|
673
|
+
if [ "$OLD_QQBOT_VERSION" = "$NEW_QQBOT_VERSION" ]; then
|
|
674
|
+
echo "qqbot 插件版本: $NEW_QQBOT_VERSION (未变化)"
|
|
675
|
+
elif [ "$OLD_QQBOT_VERSION" = "not_installed" ]; then
|
|
676
|
+
echo "qqbot 插件版本: $NEW_QQBOT_VERSION (新安装)"
|
|
677
|
+
else
|
|
678
|
+
echo "qqbot 插件版本: $OLD_QQBOT_VERSION -> $NEW_QQBOT_VERSION"
|
|
679
|
+
fi
|
|
680
|
+
echo ""
|
|
681
|
+
read -t 120 -p "是否后台重启 openclaw 网关服务?[Y/n] " start_choice || start_choice="y"
|
|
682
|
+
start_choice="${start_choice:-y}"
|
|
683
|
+
start_choice=$(printf '%s' "$start_choice" | tr '[:upper:]' '[:lower:]')
|
|
684
|
+
|
|
685
|
+
# 辅助函数:带超时执行 openclaw 命令(防止 CLI 阻塞卡死脚本)
|
|
686
|
+
_openclaw_with_timeout() {
|
|
687
|
+
local _timeout_sec="${1:-30}"
|
|
688
|
+
shift
|
|
689
|
+
if command -v timeout &>/dev/null; then
|
|
690
|
+
timeout "$_timeout_sec" "$@" 2>&1 || true
|
|
691
|
+
elif command -v gtimeout &>/dev/null; then
|
|
692
|
+
gtimeout "$_timeout_sec" "$@" 2>&1 || true
|
|
693
|
+
else
|
|
694
|
+
"$@" 2>&1 &
|
|
695
|
+
local _pid=$!
|
|
696
|
+
local _elapsed=0
|
|
697
|
+
while kill -0 "$_pid" 2>/dev/null && [ "$_elapsed" -lt "$_timeout_sec" ]; do
|
|
698
|
+
sleep 1
|
|
699
|
+
_elapsed=$((_elapsed + 1))
|
|
700
|
+
done
|
|
701
|
+
if kill -0 "$_pid" 2>/dev/null; then
|
|
702
|
+
echo " ⚠️ 命令超时(${_timeout_sec}s),继续执行..."
|
|
703
|
+
kill "$_pid" 2>/dev/null || true
|
|
704
|
+
fi
|
|
705
|
+
wait "$_pid" 2>/dev/null || true
|
|
706
|
+
fi
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
case "$start_choice" in
|
|
710
|
+
y|yes)
|
|
711
|
+
echo ""
|
|
712
|
+
# gateway 在安装前已 stop(unload),直接 restart 会报 "not loaded"
|
|
713
|
+
# 因此先 install(注册服务)再 start,避免必现的恢复流程
|
|
714
|
+
echo "正在启动 openclaw 网关服务..."
|
|
715
|
+
openclaw gateway install 2>/dev/null || true
|
|
716
|
+
_start_output=$(_openclaw_with_timeout 30 openclaw gateway start)
|
|
717
|
+
echo "$_start_output"
|
|
718
|
+
|
|
719
|
+
if echo "$_start_output" | grep -qi "not loaded\|not found\|not installed\|error\|fail"; then
|
|
720
|
+
echo ""
|
|
721
|
+
echo "⚠️ 启动异常,尝试 restart 恢复..."
|
|
722
|
+
_restart_output=$(_openclaw_with_timeout 30 openclaw gateway restart)
|
|
723
|
+
echo "$_restart_output"
|
|
724
|
+
if echo "$_restart_output" | grep -qi "not loaded\|not found\|not installed"; then
|
|
725
|
+
echo ""
|
|
726
|
+
echo "⚠️ 自动恢复失败,请手动执行:"
|
|
727
|
+
echo " openclaw gateway install && openclaw gateway start"
|
|
728
|
+
else
|
|
729
|
+
echo ""
|
|
730
|
+
echo "✅ gateway 服务已启动"
|
|
731
|
+
fi
|
|
732
|
+
else
|
|
733
|
+
echo ""
|
|
734
|
+
echo "✅ openclaw 网关已在后台启动"
|
|
735
|
+
fi
|
|
736
|
+
echo ""
|
|
737
|
+
# 等待 gateway 端口就绪
|
|
738
|
+
echo "等待 gateway 就绪..."
|
|
739
|
+
echo "========================================="
|
|
740
|
+
_port_ready=0
|
|
741
|
+
for i in $(seq 1 30); do
|
|
742
|
+
if lsof -i :18789 -sTCP:LISTEN >/dev/null 2>&1; then
|
|
743
|
+
_port_ready=1
|
|
744
|
+
break
|
|
745
|
+
fi
|
|
746
|
+
printf "\r 等待端口 18789 就绪... (%d/30)" "$i"
|
|
747
|
+
sleep 2
|
|
748
|
+
done
|
|
749
|
+
echo ""
|
|
750
|
+
|
|
751
|
+
if [ "$_port_ready" -eq 0 ]; then
|
|
752
|
+
echo "⚠️ 等待超时,尝试 openclaw doctor --fix 自动修复..."
|
|
753
|
+
_doctor_output=$(openclaw doctor --fix 2>&1) || true
|
|
754
|
+
echo "$_doctor_output"
|
|
755
|
+
# doctor --fix 后再尝试 restart 一次
|
|
756
|
+
echo ""
|
|
757
|
+
echo "doctor 修复后重试 gateway restart..."
|
|
758
|
+
_openclaw_with_timeout 30 openclaw gateway restart
|
|
759
|
+
sleep 5
|
|
760
|
+
if lsof -i :18789 -sTCP:LISTEN >/dev/null 2>&1; then
|
|
761
|
+
echo "✅ doctor --fix 后 gateway 启动成功"
|
|
762
|
+
_port_ready=1
|
|
763
|
+
else
|
|
764
|
+
echo "❌ 仍然无法启动,请手动排查:"
|
|
765
|
+
echo " openclaw doctor"
|
|
766
|
+
echo " tail -f /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log"
|
|
767
|
+
fi
|
|
768
|
+
fi
|
|
769
|
+
|
|
770
|
+
# 端口就绪后:恢复 channels.qqbot 配置 + 检查连接 + 跟踪日志
|
|
771
|
+
if [ "$_port_ready" -eq 1 ]; then
|
|
772
|
+
echo "✅ Gateway 端口已就绪(插件已加载)"
|
|
773
|
+
echo ""
|
|
774
|
+
|
|
775
|
+
# ── 恢复 channels.qqbot 配置 ──
|
|
776
|
+
# gateway 已启动、插件已注册 qqbot channel,现在可以安全写回配置
|
|
777
|
+
_need_reload=0
|
|
778
|
+
_target_cfg=""
|
|
779
|
+
for _app in openclaw clawdbot moltbot; do
|
|
780
|
+
_cfg="$HOME/.$_app/$_app.json"
|
|
781
|
+
if [ -f "$_cfg" ]; then
|
|
782
|
+
_target_cfg="$_cfg"
|
|
783
|
+
break
|
|
784
|
+
fi
|
|
785
|
+
done
|
|
786
|
+
|
|
787
|
+
if [ -n "$_target_cfg" ]; then
|
|
788
|
+
# 构建完整的 channels.qqbot 对象(合并暂存配置 + 新 token + markdown)
|
|
789
|
+
node -e "
|
|
790
|
+
const fs = require('fs');
|
|
791
|
+
const cfg = JSON.parse(fs.readFileSync('$_target_cfg', 'utf8'));
|
|
792
|
+
if (!cfg.channels) cfg.channels = {};
|
|
793
|
+
|
|
794
|
+
// 从暂存恢复基础配置
|
|
795
|
+
const stash = '$_QQBOT_CHANNEL_STASH';
|
|
796
|
+
if (stash) {
|
|
797
|
+
try { cfg.channels.qqbot = JSON.parse(stash); } catch {}
|
|
798
|
+
}
|
|
799
|
+
if (!cfg.channels.qqbot) cfg.channels.qqbot = {};
|
|
800
|
+
|
|
801
|
+
// 覆盖 token(如果有新值)
|
|
802
|
+
const desired = '$DESIRED_QQBOT_TOKEN';
|
|
803
|
+
if (desired && desired.includes(':')) {
|
|
804
|
+
const [appId, ...rest] = desired.split(':');
|
|
805
|
+
cfg.channels.qqbot.appId = appId;
|
|
806
|
+
cfg.channels.qqbot.clientSecret = rest.join(':');
|
|
807
|
+
delete cfg.channels.qqbot.token;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// 覆盖 markdown(如果有指定)
|
|
811
|
+
const md = '$MARKDOWN_VALUE';
|
|
812
|
+
if (md === 'true') cfg.channels.qqbot.markdownSupport = true;
|
|
813
|
+
else if (md === 'false') cfg.channels.qqbot.markdownSupport = false;
|
|
814
|
+
|
|
815
|
+
fs.writeFileSync('$_target_cfg', JSON.stringify(cfg, null, 4) + '\n');
|
|
816
|
+
" 2>/dev/null || true
|
|
817
|
+
echo " ✅ 已恢复 channels.qqbot 配置(含 token/markdown)"
|
|
818
|
+
_need_reload=1
|
|
819
|
+
fi
|
|
820
|
+
|
|
821
|
+
# 配置写回后 reload gateway 使其生效
|
|
822
|
+
if [ "$_need_reload" -eq 1 ]; then
|
|
823
|
+
echo " 重载配置..."
|
|
824
|
+
sleep 1
|
|
825
|
+
# openclaw gateway restart 只是向 LaunchAgent 发送 restart 信号,
|
|
826
|
+
# CLI 本身可能阻塞在等待输出上,用短超时即可(信号已发出就够了)
|
|
827
|
+
_openclaw_with_timeout 10 openclaw gateway restart
|
|
828
|
+
# 等待重启后端口重新就绪(gateway 加载插件+连 WS 可能需要较长时间)
|
|
829
|
+
for _k in $(seq 1 30); do
|
|
830
|
+
if lsof -i :18789 -sTCP:LISTEN >/dev/null 2>&1; then
|
|
831
|
+
break
|
|
832
|
+
fi
|
|
833
|
+
printf "\r 等待端口 18789 重新就绪... (%d/30)" "$_k"
|
|
834
|
+
sleep 2
|
|
835
|
+
done
|
|
836
|
+
echo ""
|
|
837
|
+
fi
|
|
838
|
+
# 检查 qqbot WS 是否连接成功(最多等 20 秒)
|
|
839
|
+
echo "检查 qqbot 插件连接状态..."
|
|
840
|
+
_LOG_FILE="/tmp/openclaw/openclaw-$(date +%Y-%m-%d).log"
|
|
841
|
+
_restart_ts=$(date +%s)
|
|
842
|
+
_qqbot_ready=0
|
|
843
|
+
for _j in $(seq 1 10); do
|
|
844
|
+
if [ -f "$_LOG_FILE" ]; then
|
|
845
|
+
_last_line=$(grep "Gateway ready" "$_LOG_FILE" 2>/dev/null | tail -1 || true)
|
|
846
|
+
if [ -n "$_last_line" ]; then
|
|
847
|
+
_qqbot_ready=1
|
|
848
|
+
break
|
|
849
|
+
fi
|
|
850
|
+
fi
|
|
851
|
+
printf "\r 等待 qqbot WS 连接... (%d/10)" "$_j"
|
|
852
|
+
sleep 2
|
|
853
|
+
done
|
|
854
|
+
echo ""
|
|
855
|
+
|
|
856
|
+
if [ "$_qqbot_ready" -eq 0 ]; then
|
|
857
|
+
echo "⚠️ qqbot 插件可能未正确加载"
|
|
858
|
+
echo "请检查: openclaw doctor"
|
|
859
|
+
else
|
|
860
|
+
echo "✅ qqbot 插件已连接"
|
|
861
|
+
fi
|
|
862
|
+
echo ""
|
|
863
|
+
echo "正在跟踪日志输出(按 Ctrl+C 停止查看,不影响后台服务)..."
|
|
864
|
+
echo "========================================="
|
|
865
|
+
_retries=0
|
|
866
|
+
while ! openclaw logs --follow 2>&1; do
|
|
867
|
+
_retries=$((_retries + 1))
|
|
868
|
+
if [ $_retries -ge 5 ]; then
|
|
869
|
+
echo ""
|
|
870
|
+
echo "⚠️ 无法连接日志流,请手动执行: openclaw logs --follow"
|
|
871
|
+
echo "或直接查看日志文件: tail -f /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log"
|
|
872
|
+
break
|
|
873
|
+
fi
|
|
874
|
+
echo "等待日志流就绪... (${_retries}/5)"
|
|
875
|
+
sleep 3
|
|
876
|
+
done
|
|
877
|
+
fi
|
|
878
|
+
;;
|
|
879
|
+
n|no)
|
|
880
|
+
echo ""
|
|
881
|
+
echo "✅ 插件更新完毕,未启动服务"
|
|
882
|
+
# 不启动时也需要恢复 channels.qqbot,否则配置丢失
|
|
883
|
+
# 注意:下次 gateway 启动可能因 "unknown channel id" 失败,
|
|
884
|
+
# 需要用户手动 stop → 移除 channels.qqbot → start → 恢复
|
|
885
|
+
if [ -n "$_QQBOT_CHANNEL_STASH" ] && [ -n "$_STASH_CFG" ] && [ -f "$_STASH_CFG" ]; then
|
|
886
|
+
node -e "
|
|
887
|
+
const fs = require('fs');
|
|
888
|
+
const cfg = JSON.parse(fs.readFileSync('$_STASH_CFG', 'utf8'));
|
|
889
|
+
if (!cfg.channels) cfg.channels = {};
|
|
890
|
+
cfg.channels.qqbot = $_QQBOT_CHANNEL_STASH;
|
|
891
|
+
fs.writeFileSync('$_STASH_CFG', JSON.stringify(cfg, null, 4) + '\n');
|
|
892
|
+
" 2>/dev/null || true
|
|
893
|
+
echo " 已恢复 channels.qqbot 配置"
|
|
894
|
+
fi
|
|
895
|
+
echo ""
|
|
896
|
+
echo "后续启动方法(兼容 openclaw 3.23+):"
|
|
897
|
+
echo " 1. 先临时移除 channels.qqbot 再启动:"
|
|
898
|
+
echo " openclaw gateway restart"
|
|
899
|
+
echo " 2. 或使用本脚本自动处理:"
|
|
900
|
+
echo " bash $0"
|
|
901
|
+
echo " 3. 跟踪日志:"
|
|
902
|
+
echo " openclaw logs --follow"
|
|
903
|
+
;;
|
|
904
|
+
*)
|
|
905
|
+
echo "无效选择,按默认值 y 执行后台重启"
|
|
906
|
+
echo ""
|
|
907
|
+
echo "正在后台重启 openclaw 网关服务..."
|
|
908
|
+
if ! _openclaw_with_timeout 30 openclaw gateway restart; then
|
|
909
|
+
echo "⚠️ 后台重启失败,可能服务未安装"
|
|
910
|
+
echo "尝试: openclaw gateway install && openclaw gateway start"
|
|
911
|
+
fi
|
|
912
|
+
echo "✅ openclaw 网关已在后台重启"
|
|
913
|
+
;;
|
|
914
|
+
esac
|
|
915
|
+
|
|
916
|
+
echo "========================================="
|