@tencent-connect/openclaw-qqbot 1.6.5-alpha.4 → 1.6.5-alpha.6

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/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  **Connect your AI assistant to QQ — private chat, group chat, and rich media, all in one plugin.**
12
12
 
13
- ### 🚀 Current Version: `v1.6.4`
13
+ ### 🚀 Current Version: `v1.6.5`
14
14
 
15
15
  [![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
16
16
  [![QQ Bot](https://img.shields.io/badge/QQ_Bot-API_v2-red)](https://bot.q.qq.com/wiki/)
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  **让你的 AI 助手接入 QQ — 私聊、群聊、富媒体,一个插件全搞定。**
11
11
 
12
- ### 🚀 当前版本: `v1.6.4`
12
+ ### 🚀 当前版本: `v1.6.5`
13
13
 
14
14
  [![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
15
15
  [![QQ Bot](https://img.shields.io/badge/QQ_Bot-API_v2-red)](https://bot.q.qq.com/wiki/)
@@ -251,7 +251,7 @@ registerCommand({
251
251
  return lines.join("\n");
252
252
  },
253
253
  });
254
- const DEFAULT_UPGRADE_URL = "https://doc.weixin.qq.com/doc/w3_AKEAGQaeACgCNHrh1CbHzTAKtT2gB?scode=AJEAIQdfAAozxFEnLZAKEAGQaeACg";
254
+ const DEFAULT_UPGRADE_URL = "https://docs.qq.com/doc/DSGxOZk1oVnVKVkpq";
255
255
  function saveUpgradeGreetingTarget(accountId, appId, openid) {
256
256
  const safeAccountId = accountId.replace(/[^a-zA-Z0-9._-]/g, "_");
257
257
  const safeAppId = appId.replace(/[^a-zA-Z0-9._-]/g, "_");
@@ -561,6 +561,38 @@ function findPowerShell() {
561
561
  }
562
562
  return null;
563
563
  }
564
+ /**
565
+ * 将升级脚本复制到临时位置,避免升级过程中插件目录被清理后脚本丢失。
566
+ * 返回临时脚本路径,失败返回 null。
567
+ */
568
+ function copyScriptToTemp(scriptPath) {
569
+ try {
570
+ const ext = path.extname(scriptPath);
571
+ const tmpDir = path.join(getHomeDir(), ".openclaw", ".qqbot-upgrade-tmp");
572
+ fs.mkdirSync(tmpDir, { recursive: true });
573
+ const tmpScript = path.join(tmpDir, `upgrade-via-npm${ext}`);
574
+ fs.copyFileSync(scriptPath, tmpScript);
575
+ if (!isWindows()) {
576
+ fs.chmodSync(tmpScript, 0o755);
577
+ }
578
+ return tmpScript;
579
+ }
580
+ catch {
581
+ return null;
582
+ }
583
+ }
584
+ /**
585
+ * 清理临时升级脚本目录
586
+ */
587
+ function cleanupTempScript() {
588
+ try {
589
+ const tmpDir = path.join(getHomeDir(), ".openclaw", ".qqbot-upgrade-tmp");
590
+ fs.rmSync(tmpDir, { recursive: true, force: true });
591
+ }
592
+ catch {
593
+ // 非关键,静默忽略
594
+ }
595
+ }
564
596
  /**
565
597
  * 执行热更新:执行脚本(--no-restart) → 立即触发 gateway restart
566
598
  *
@@ -570,11 +602,15 @@ function findPowerShell() {
570
602
  * - 新进程启动时 getStartupGreeting() 检测到版本变更,自动通知管理员
571
603
  *
572
604
  * Windows 使用 PowerShell 执行 .ps1 脚本,Mac/Linux 使用 bash 执行 .sh 脚本。
605
+ *
606
+ * 安全机制:脚本会被复制到临时目录再执行,避免升级过程中插件目录被操作导致脚本自身丢失。
573
607
  */
574
608
  function fireHotUpgrade(targetVersion) {
575
- const scriptPath = getUpgradeScriptPath();
576
- if (!scriptPath)
609
+ const originalScriptPath = getUpgradeScriptPath();
610
+ if (!originalScriptPath)
577
611
  return { ok: false, reason: "no-script" };
612
+ // 将脚本复制到临时位置,避免升级过程中脚本被删除
613
+ const scriptPath = copyScriptToTemp(originalScriptPath) || originalScriptPath;
578
614
  const cli = findCli();
579
615
  if (!cli)
580
616
  return { ok: false, reason: "no-cli" };
@@ -601,7 +637,7 @@ function fireHotUpgrade(targetVersion) {
601
637
  shell = bash;
602
638
  shellArgs = [scriptPath, "--no-restart", ...(targetVersion ? ["--version", targetVersion] : [])];
603
639
  }
604
- console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}`);
640
+ console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath} (original: ${originalScriptPath}), cli=${cli}, target=${targetVersion || "latest"}`);
605
641
  // 异步执行升级脚本
606
642
  execFile(shell, shellArgs, {
607
643
  timeout: 120_000,
@@ -614,6 +650,7 @@ function fireHotUpgrade(targetVersion) {
614
650
  console.error(`[qqbot] fireHotUpgrade: stdout: ${stdout.slice(0, 2000)}`);
615
651
  if (_stderr)
616
652
  console.error(`[qqbot] fireHotUpgrade: stderr: ${_stderr.slice(0, 2000)}`);
653
+ cleanupTempScript();
617
654
  _upgrading = false;
618
655
  return;
619
656
  }
@@ -623,10 +660,13 @@ function fireHotUpgrade(targetVersion) {
623
660
  const newVersion = versionMatch?.[1];
624
661
  if (newVersion === "unknown") {
625
662
  console.error(`[qqbot] fireHotUpgrade: script output QQBOT_NEW_VERSION=unknown, aborting restart`);
663
+ cleanupTempScript();
626
664
  _upgrading = false;
627
665
  return;
628
666
  }
629
667
  console.log(`[qqbot] fireHotUpgrade: new version=${newVersion || "(not detected)"}, triggering restart...`);
668
+ // 脚本执行成功,清理临时脚本副本
669
+ cleanupTempScript();
630
670
  // 文件替换成功,在 restart 之前把 source 从 path 切换为 npm,
631
671
  // 确保新进程启动时读到的是 npm source,不会被本地源码覆盖。
632
672
  switchPluginSourceToNpm();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencent-connect/openclaw-qqbot",
3
- "version": "1.6.5-alpha.4",
3
+ "version": "1.6.5-alpha.6",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -139,6 +139,46 @@ fi
139
139
  echo ""
140
140
  echo "[1/4] 安装/升级插件..."
141
141
 
142
+ # ── 兼容 openclaw 3.23+ 配置严格校验 ──
143
+ # 3.23+ 在 plugins install/update 时会校验整个配置文件,
144
+ # 如果 channels.qqbot 已存在但 qqbot 插件尚未加载,校验会失败。
145
+ # 解决:install/update 前临时移除 channels.qqbot,成功后恢复。
146
+ CONFIG_FILE="$HOME/.$CMD/$CMD.json"
147
+ QQBOT_CHANNEL_BACKUP=""
148
+ if [ -f "$CONFIG_FILE" ]; then
149
+ QQBOT_CHANNEL_BACKUP="$(node -e "
150
+ try {
151
+ const fs = require('fs');
152
+ const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
153
+ if (cfg.channels && cfg.channels.qqbot) {
154
+ process.stdout.write(JSON.stringify(cfg.channels.qqbot));
155
+ delete cfg.channels.qqbot;
156
+ if (Object.keys(cfg.channels).length === 0) delete cfg.channels;
157
+ fs.writeFileSync('$CONFIG_FILE', JSON.stringify(cfg, null, 4) + '\n');
158
+ }
159
+ } catch {}
160
+ " 2>/dev/null || true)"
161
+ if [ -n "$QQBOT_CHANNEL_BACKUP" ]; then
162
+ echo " [兼容] 临时移除 channels.qqbot 以通过配置校验"
163
+ fi
164
+ fi
165
+
166
+ # 恢复 channels.qqbot 的函数(install/update 完成后调用)
167
+ restore_qqbot_channel() {
168
+ if [ -n "$QQBOT_CHANNEL_BACKUP" ] && [ -f "$CONFIG_FILE" ]; then
169
+ node -e "
170
+ try {
171
+ const fs = require('fs');
172
+ const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
173
+ if (!cfg.channels) cfg.channels = {};
174
+ cfg.channels.qqbot = $QQBOT_CHANNEL_BACKUP;
175
+ fs.writeFileSync('$CONFIG_FILE', JSON.stringify(cfg, null, 4) + '\n');
176
+ } catch {}
177
+ " 2>/dev/null || true
178
+ echo " [兼容] 已恢复 channels.qqbot 配置"
179
+ fi
180
+ }
181
+
142
182
  UPGRADE_OK=false
143
183
 
144
184
  # 检测安装状态:同时检查配置记录和磁盘目录
@@ -190,38 +230,55 @@ if [ "$USE_UPDATE" = "true" ]; then
190
230
  fi
191
231
 
192
232
  if [ "$UPGRADE_OK" != "true" ]; then
193
- # 清理旧目录(包含当前插件和历史遗留名称)
194
- for dir_name in "$PLUGIN_ID" qqbot openclaw-qq; do
195
- [ -d "$EXTENSIONS_DIR/$dir_name" ] && rm -rf "$EXTENSIONS_DIR/$dir_name" && echo " 已清理: $EXTENSIONS_DIR/$dir_name"
233
+ # 备份旧目录(而非直接删除),install 失败时可回滚
234
+ BACKUP_DIR=""
235
+ if [ -d "$EXTENSIONS_DIR/$PLUGIN_ID" ]; then
236
+ BACKUP_DIR="$EXTENSIONS_DIR/.openclaw-qqbot-backup-$$"
237
+ mv "$EXTENSIONS_DIR/$PLUGIN_ID" "$BACKUP_DIR"
238
+ echo " 已备份旧目录: $BACKUP_DIR"
239
+ fi
240
+
241
+ # 清理历史遗留名称(这些不需要回滚)
242
+ for dir_name in qqbot openclaw-qq; do
243
+ [ -d "$EXTENSIONS_DIR/$dir_name" ] && rm -rf "$EXTENSIONS_DIR/$dir_name" && echo " 已清理历史目录: $EXTENSIONS_DIR/$dir_name"
196
244
  done
197
245
 
198
246
  echo " 执行 install: $INSTALL_SRC"
199
247
  if $CMD plugins install "$INSTALL_SRC" --pin 2>&1; then
200
248
  UPGRADE_OK=true
201
249
  echo " ✅ install 成功"
250
+ # install 成功,清理备份
251
+ if [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ]; then
252
+ rm -rf "$BACKUP_DIR"
253
+ echo " 已清理旧版备份"
254
+ fi
255
+ # 清理 openclaw CLI install 可能留下的额外 backup 目录
256
+ find "$EXTENSIONS_DIR" -maxdepth 1 -name ".openclaw-qqbot-backup-*" -exec rm -rf {} + 2>/dev/null || true
202
257
  else
203
- echo "❌ install 失败"
258
+ echo " ❌ install 失败"
259
+ # 回滚:恢复旧目录
260
+ if [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ]; then
261
+ mv "$BACKUP_DIR" "$EXTENSIONS_DIR/$PLUGIN_ID"
262
+ echo " ↩️ 已回滚到旧版本"
263
+ fi
264
+ restore_qqbot_channel
204
265
  echo "QQBOT_NEW_VERSION=unknown"
205
- echo "QQBOT_REPORT=❌ QQBot 安装失败,请检查网络和 npm registry"
266
+ echo "QQBOT_REPORT=❌ QQBot 安装失败(已回滚到旧版本),请检查网络和 npm registry"
206
267
  exit 1
207
268
  fi
208
269
  fi
209
270
 
271
+ # install/update 完成,恢复 channels.qqbot
272
+ restore_qqbot_channel
273
+
210
274
  # [2/4] 验证安装
211
275
  echo ""
212
276
  echo "[2/4] 验证安装..."
213
277
 
214
- NEW_VERSION="$(node -e "
215
- try {
216
- const fs = require('fs');
217
- const path = require('path');
218
- const p = path.join('$EXTENSIONS_DIR', '$PLUGIN_ID', 'package.json');
219
- if (fs.existsSync(p)) {
220
- const v = JSON.parse(fs.readFileSync(p, 'utf8')).version;
221
- if (v) { process.stdout.write(v); process.exit(0); }
222
- }
223
- } catch {}
224
- " 2>/dev/null || true)"
278
+ PKG_JSON="$EXTENSIONS_DIR/$PLUGIN_ID/package.json"
279
+ if [ -f "$PKG_JSON" ]; then
280
+ NEW_VERSION="$(node -e "process.stdout.write(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).version||'')" "$PKG_JSON" 2>/dev/null || true)"
281
+ fi
225
282
 
226
283
  # Preflight 检查
227
284
  PREFLIGHT_OK=true
@@ -299,6 +356,19 @@ if [ "$PREFLIGHT_OK" != "true" ]; then
299
356
  fi
300
357
  echo " ✅ 验证全部通过"
301
358
 
359
+ # 确保 openclaw/plugin-sdk 可解析:
360
+ # openclaw plugins install 不会执行 npm lifecycle scripts,
361
+ # 需要手动调用 postinstall-link-sdk.js 创建 node_modules/openclaw → 全局 openclaw 的符号链接
362
+ POSTINSTALL_SCRIPT="$TARGET_DIR/scripts/postinstall-link-sdk.js"
363
+ if [ -f "$POSTINSTALL_SCRIPT" ]; then
364
+ echo " 执行 postinstall-link-sdk..."
365
+ if node "$POSTINSTALL_SCRIPT" 2>&1; then
366
+ echo " ✅ plugin-sdk 链接就绪"
367
+ else
368
+ echo " ⚠️ postinstall-link-sdk 失败,插件可能无法加载"
369
+ fi
370
+ fi
371
+
302
372
  # [3/4] 输出结构化信息(供 TS handler 解析)
303
373
  echo ""
304
374
  echo "[3/4] 升级结果..."
@@ -444,6 +444,27 @@ else
444
444
  # gateway 已在安装前 stop,此时不会有自动 restart 的问题
445
445
  # 所有配置写入完成后,在 Step 6 统一启动
446
446
 
447
+ # 确保 openclaw/plugin-sdk 可解析:
448
+ # openclaw plugins install 不会执行 npm lifecycle scripts,
449
+ # 需要手动调用 postinstall-link-sdk.js 创建 node_modules/openclaw → 全局 openclaw 的符号链接。
450
+ # 必须在 peerDeps 清理之后执行,否则 symlink 会被清理逻辑删除。
451
+ for _candidate_name in openclaw-qqbot qqbot openclaw-qq; do
452
+ _postinstall="$HOME/.openclaw/extensions/$_candidate_name/scripts/postinstall-link-sdk.js"
453
+ if [ -f "$_postinstall" ]; then
454
+ echo " 执行 postinstall-link-sdk..."
455
+ if node "$_postinstall" 2>&1; then
456
+ echo " ✅ plugin-sdk 链接就绪"
457
+ else
458
+ echo " ⚠️ postinstall-link-sdk 失败,插件可能无法加载"
459
+ fi
460
+ break
461
+ fi
462
+ done
463
+
464
+ # 清理 openclaw CLI install 留下的 backup 目录,
465
+ # 避免 gateway 发现两个同 id 插件不断刷 duplicate plugin id 警告
466
+ find "$HOME/.openclaw/extensions/" -maxdepth 1 -name ".openclaw-qqbot-backup-*" -exec rm -rf {} + 2>/dev/null || true
467
+
447
468
  # 记录更新后的 qqbot 插件版本
448
469
  NEW_QQBOT_VERSION=$(node -e '
449
470
  try {
@@ -337,7 +337,7 @@ registerCommand({
337
337
  },
338
338
  });
339
339
 
340
- const DEFAULT_UPGRADE_URL = "https://doc.weixin.qq.com/doc/w3_AKEAGQaeACgCNHrh1CbHzTAKtT2gB?scode=AJEAIQdfAAozxFEnLZAKEAGQaeACg";
340
+ const DEFAULT_UPGRADE_URL = "https://docs.qq.com/doc/DSGxOZk1oVnVKVkpq";
341
341
 
342
342
  function saveUpgradeGreetingTarget(accountId: string, appId: string, openid: string): void {
343
343
  const safeAccountId = accountId.replace(/[^a-zA-Z0-9._-]/g, "_");
@@ -660,6 +660,38 @@ function findPowerShell(): string | null {
660
660
  return null;
661
661
  }
662
662
 
663
+ /**
664
+ * 将升级脚本复制到临时位置,避免升级过程中插件目录被清理后脚本丢失。
665
+ * 返回临时脚本路径,失败返回 null。
666
+ */
667
+ function copyScriptToTemp(scriptPath: string): string | null {
668
+ try {
669
+ const ext = path.extname(scriptPath);
670
+ const tmpDir = path.join(getHomeDir(), ".openclaw", ".qqbot-upgrade-tmp");
671
+ fs.mkdirSync(tmpDir, { recursive: true });
672
+ const tmpScript = path.join(tmpDir, `upgrade-via-npm${ext}`);
673
+ fs.copyFileSync(scriptPath, tmpScript);
674
+ if (!isWindows()) {
675
+ fs.chmodSync(tmpScript, 0o755);
676
+ }
677
+ return tmpScript;
678
+ } catch {
679
+ return null;
680
+ }
681
+ }
682
+
683
+ /**
684
+ * 清理临时升级脚本目录
685
+ */
686
+ function cleanupTempScript(): void {
687
+ try {
688
+ const tmpDir = path.join(getHomeDir(), ".openclaw", ".qqbot-upgrade-tmp");
689
+ fs.rmSync(tmpDir, { recursive: true, force: true });
690
+ } catch {
691
+ // 非关键,静默忽略
692
+ }
693
+ }
694
+
663
695
  /**
664
696
  * 执行热更新:执行脚本(--no-restart) → 立即触发 gateway restart
665
697
  *
@@ -669,10 +701,15 @@ function findPowerShell(): string | null {
669
701
  * - 新进程启动时 getStartupGreeting() 检测到版本变更,自动通知管理员
670
702
  *
671
703
  * Windows 使用 PowerShell 执行 .ps1 脚本,Mac/Linux 使用 bash 执行 .sh 脚本。
704
+ *
705
+ * 安全机制:脚本会被复制到临时目录再执行,避免升级过程中插件目录被操作导致脚本自身丢失。
672
706
  */
673
707
  function fireHotUpgrade(targetVersion?: string): HotUpgradeStartResult {
674
- const scriptPath = getUpgradeScriptPath();
675
- if (!scriptPath) return { ok: false, reason: "no-script" };
708
+ const originalScriptPath = getUpgradeScriptPath();
709
+ if (!originalScriptPath) return { ok: false, reason: "no-script" };
710
+
711
+ // 将脚本复制到临时位置,避免升级过程中脚本被删除
712
+ const scriptPath = copyScriptToTemp(originalScriptPath) || originalScriptPath;
676
713
 
677
714
  const cli = findCli();
678
715
  if (!cli) return { ok: false, reason: "no-cli" };
@@ -699,7 +736,7 @@ function fireHotUpgrade(targetVersion?: string): HotUpgradeStartResult {
699
736
  shellArgs = [scriptPath, "--no-restart", ...(targetVersion ? ["--version", targetVersion] : [])];
700
737
  }
701
738
 
702
- console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}`);
739
+ console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath} (original: ${originalScriptPath}), cli=${cli}, target=${targetVersion || "latest"}`);
703
740
 
704
741
  // 异步执行升级脚本
705
742
  execFile(shell, shellArgs, {
@@ -711,6 +748,7 @@ function fireHotUpgrade(targetVersion?: string): HotUpgradeStartResult {
711
748
  console.error(`[qqbot] fireHotUpgrade: script failed: ${error.message}`);
712
749
  if (stdout) console.error(`[qqbot] fireHotUpgrade: stdout: ${stdout.slice(0, 2000)}`);
713
750
  if (_stderr) console.error(`[qqbot] fireHotUpgrade: stderr: ${_stderr.slice(0, 2000)}`);
751
+ cleanupTempScript();
714
752
  _upgrading = false;
715
753
  return;
716
754
  }
@@ -722,12 +760,16 @@ function fireHotUpgrade(targetVersion?: string): HotUpgradeStartResult {
722
760
  const newVersion = versionMatch?.[1];
723
761
  if (newVersion === "unknown") {
724
762
  console.error(`[qqbot] fireHotUpgrade: script output QQBOT_NEW_VERSION=unknown, aborting restart`);
763
+ cleanupTempScript();
725
764
  _upgrading = false;
726
765
  return;
727
766
  }
728
767
 
729
768
  console.log(`[qqbot] fireHotUpgrade: new version=${newVersion || "(not detected)"}, triggering restart...`);
730
769
 
770
+ // 脚本执行成功,清理临时脚本副本
771
+ cleanupTempScript();
772
+
731
773
  // 文件替换成功,在 restart 之前把 source 从 path 切换为 npm,
732
774
  // 确保新进程启动时读到的是 npm source,不会被本地源码覆盖。
733
775
  switchPluginSourceToNpm();
@@ -1,307 +0,0 @@
1
- #!/bin/bash
2
-
3
- # qqbot 测试脚本:自由切换不同的 QQBot npm 包
4
- #
5
- # 支持从任意 npm 包安装指定版本到 openclaw extensions 目录。
6
- # 可用于在不同包之间切换测试,如 @sliverp/qqbot、@tencent-connect/openclaw-qqbot 等。
7
- #
8
- # 用法:
9
- # upgrade-via-alt-pkg.sh --pkg <包名> --version <version> # 指定包+版本
10
- # upgrade-via-alt-pkg.sh --pkg <包名> # 指定包,安装 latest
11
- # upgrade-via-alt-pkg.sh --appid <appid> --secret <secret> # 首次安装时配置
12
- # upgrade-via-alt-pkg.sh --no-restart # 只做文件替换,不重启
13
- #
14
- # 示例:
15
- # bash scripts/upgrade-via-alt-pkg.sh --pkg @sliverp/qqbot --version 1.5.1
16
- # bash scripts/upgrade-via-alt-pkg.sh --pkg @sliverp/qqbot --version 1.5.4
17
- # bash scripts/upgrade-via-alt-pkg.sh --pkg @tencent-connect/openclaw-qqbot --version 1.6.4
18
- # bash scripts/upgrade-via-alt-pkg.sh --pkg @sliverp/qqbot
19
- # bash scripts/upgrade-via-alt-pkg.sh --pkg @sliverp/qqbot --version 1.5.4 --appid 12345 --secret abc123
20
-
21
- set -eo pipefail
22
-
23
- PKG_NAME=""
24
- VERSION=""
25
- INSTALL_SRC=""
26
- APPID=""
27
- SECRET=""
28
- NO_RESTART=false
29
-
30
- print_usage() {
31
- echo "用法:"
32
- echo " upgrade-via-alt-pkg.sh --pkg <包名> --version <版本号>"
33
- echo " upgrade-via-alt-pkg.sh --pkg <包名> # 安装 latest"
34
- echo ""
35
- echo "选项:"
36
- echo " --pkg <name> npm 包名(必填,如 @sliverp/qqbot、@tencent-connect/openclaw-qqbot)"
37
- echo " --version <version> 指定版本号(如 1.5.1, 1.5.4, 1.6.4)"
38
- echo " --appid <appid> QQ机器人 appid(首次安装时必填)"
39
- echo " --secret <secret> QQ机器人 secret(首次安装时必填)"
40
- echo " --no-restart 只做文件替换,不重启 gateway"
41
- echo " -h, --help 显示帮助信息"
42
- echo ""
43
- echo "环境变量:"
44
- echo " QQBOT_APPID QQ机器人 appid"
45
- echo " QQBOT_SECRET QQ机器人 secret"
46
- echo " QQBOT_TOKEN QQ机器人 token (appid:secret)"
47
- echo ""
48
- echo "示例:"
49
- echo " # 从 @sliverp/qqbot 包安装 v1.5.1"
50
- echo " bash scripts/upgrade-via-alt-pkg.sh --pkg @sliverp/qqbot --version 1.5.1"
51
- echo ""
52
- echo " # 从 @sliverp/qqbot 包安装 v1.5.4"
53
- echo " bash scripts/upgrade-via-alt-pkg.sh --pkg @sliverp/qqbot --version 1.5.4"
54
- echo ""
55
- echo " # 从官方包安装 v1.6.4"
56
- echo " bash scripts/upgrade-via-alt-pkg.sh --pkg @tencent-connect/openclaw-qqbot --version 1.6.4"
57
- echo ""
58
- echo " # 切回 @sliverp/qqbot 包的 latest"
59
- echo " bash scripts/upgrade-via-alt-pkg.sh --pkg @sliverp/qqbot"
60
- }
61
-
62
- while [[ $# -gt 0 ]]; do
63
- case "$1" in
64
- --pkg|--package)
65
- [ -z "$2" ] && echo "❌ --pkg 需要参数" && exit 1
66
- PKG_NAME="$2"
67
- shift 2
68
- ;;
69
- --version)
70
- [ -z "$2" ] && echo "❌ --version 需要参数" && exit 1
71
- VERSION="$2"
72
- shift 2
73
- ;;
74
- --tag)
75
- [ -z "$2" ] && echo "❌ --tag 需要参数" && exit 1
76
- VERSION="$2"
77
- shift 2
78
- ;;
79
- --appid)
80
- [ -z "$2" ] && echo "❌ --appid 需要参数" && exit 1
81
- APPID="$2"
82
- shift 2
83
- ;;
84
- --secret)
85
- [ -z "$2" ] && echo "❌ --secret 需要参数" && exit 1
86
- SECRET="$2"
87
- shift 2
88
- ;;
89
- --no-restart)
90
- NO_RESTART=true
91
- shift 1
92
- ;;
93
- -h|--help)
94
- print_usage
95
- exit 0
96
- ;;
97
- *) echo "未知选项: $1"; print_usage; exit 1 ;;
98
- esac
99
- done
100
-
101
- # --pkg 必填
102
- if [ -z "$PKG_NAME" ]; then
103
- echo "❌ 必须指定 --pkg 参数"
104
- echo ""
105
- print_usage
106
- exit 1
107
- fi
108
-
109
- if [ -n "$VERSION" ]; then
110
- INSTALL_SRC="${PKG_NAME}@${VERSION}"
111
- else
112
- INSTALL_SRC="${PKG_NAME}@latest"
113
- fi
114
-
115
- # 环境变量 fallback
116
- APPID="${APPID:-$QQBOT_APPID}"
117
- SECRET="${SECRET:-$QQBOT_SECRET}"
118
- if [ -z "$APPID" ] && [ -z "$SECRET" ] && [ -n "$QQBOT_TOKEN" ]; then
119
- APPID="${QQBOT_TOKEN%%:*}"
120
- SECRET="${QQBOT_TOKEN#*:}"
121
- fi
122
-
123
- # 检测 CLI(仅用于确定 extensions 目录路径)
124
- CMD=""
125
- for name in openclaw clawdbot moltbot; do
126
- command -v "$name" &>/dev/null && CMD="$name" && break
127
- done
128
- [ -z "$CMD" ] && echo "❌ 未找到 openclaw / clawdbot / moltbot" && exit 1
129
-
130
- EXTENSIONS_DIR="$HOME/.$CMD/extensions"
131
-
132
- echo "==========================================="
133
- echo " qqbot 测试升级: $INSTALL_SRC"
134
- echo "==========================================="
135
- echo ""
136
-
137
- # [1/3] 下载并安装新版本到临时目录
138
- echo "[1/3] 下载新版本..."
139
- TMPDIR_PACK=$(mktemp -d)
140
- EXTRACT_DIR=$(mktemp -d)
141
- trap "rm -rf '$TMPDIR_PACK' '$EXTRACT_DIR'" EXIT
142
-
143
- cd "$TMPDIR_PACK"
144
- # 多 registry fallback:npmjs.org → npmmirror(国内镜像)→ 默认 registry
145
- PACK_OK=false
146
- for _registry in "https://registry.npmjs.org/" "https://registry.npmmirror.com/" ""; do
147
- if [ -n "$_registry" ]; then
148
- echo " 尝试 registry: $_registry"
149
- npm pack "$INSTALL_SRC" --registry "$_registry" --quiet 2>&1 && PACK_OK=true && break
150
- else
151
- echo " 尝试默认 registry..."
152
- npm pack "$INSTALL_SRC" --quiet 2>&1 && PACK_OK=true && break
153
- fi
154
- done
155
- $PACK_OK || { echo "❌ npm pack 失败(所有 registry 均不可用)"; exit 1; }
156
- TGZ_FILE=$(ls -1 *.tgz 2>/dev/null | head -1)
157
- [ -z "$TGZ_FILE" ] && echo "❌ 未找到下载的 tgz 文件" && exit 1
158
- echo " 已下载: $TGZ_FILE"
159
-
160
- tar xzf "$TGZ_FILE" -C "$EXTRACT_DIR"
161
- PACKAGE_DIR="$EXTRACT_DIR/package"
162
- [ ! -d "$PACKAGE_DIR" ] && echo "❌ 解压失败,未找到 package 目录" && exit 1
163
-
164
- # 准备 staging 目录
165
- STAGING_DIR="$(dirname "$EXTENSIONS_DIR")/.qqbot-upgrade-staging"
166
- rm -rf "$STAGING_DIR"
167
- mkdir -p "$STAGING_DIR"
168
- cp -R "$PACKAGE_DIR/." "$STAGING_DIR/"
169
-
170
- # 依赖处理
171
- if [ -d "$STAGING_DIR/node_modules" ]; then
172
- BUNDLED_COUNT=$(find "$STAGING_DIR/node_modules" -mindepth 1 -maxdepth 2 -type d | wc -l | tr -d ' ')
173
- echo " bundled 依赖已就绪(${BUNDLED_COUNT} 个包)"
174
- else
175
- echo " ⚠️ 未找到 bundled node_modules,尝试安装依赖..."
176
- NPM_TMP_CACHE=$(mktemp -d)
177
- (cd "$STAGING_DIR" && npm install --omit=dev --omit=peer --ignore-scripts --cache="$NPM_TMP_CACHE" --quiet 2>&1) || echo " ⚠️ 依赖安装失败"
178
- rm -rf "$NPM_TMP_CACHE"
179
- fi
180
-
181
- # 清理下载临时文件
182
- rm -rf "$TMPDIR_PACK" "$EXTRACT_DIR"
183
- cd "$HOME"
184
-
185
- # [2/3] 原子替换插件目录
186
- echo ""
187
- echo "[2/3] 原子替换插件目录..."
188
- TARGET_DIR="$EXTENSIONS_DIR/openclaw-qqbot"
189
- OLD_DIR="$(dirname "$EXTENSIONS_DIR")/.qqbot-upgrade-old"
190
-
191
- rm -rf "$OLD_DIR"
192
-
193
- STAGING_IN_EXT="$EXTENSIONS_DIR/.openclaw-qqbot-new"
194
- rm -rf "$STAGING_IN_EXT"
195
- mv "$STAGING_DIR" "$STAGING_IN_EXT"
196
-
197
- if [ -d "$TARGET_DIR" ]; then
198
- mv "$TARGET_DIR" "$OLD_DIR" && mv "$STAGING_IN_EXT" "$TARGET_DIR"
199
- else
200
- mv "$STAGING_IN_EXT" "$TARGET_DIR"
201
- fi
202
- rm -rf "$OLD_DIR"
203
-
204
- # 清理可能残留的旧版 staging 目录
205
- rm -rf "$EXTENSIONS_DIR/openclaw-qqbot.staging"
206
- rm -rf "$EXTENSIONS_DIR/.qqbot-upgrade-staging"
207
- rm -rf "$EXTENSIONS_DIR/.qqbot-upgrade-old"
208
-
209
- # 清理历史遗留的其他目录名
210
- for dir_name in qqbot openclaw-qq; do
211
- [ -d "$EXTENSIONS_DIR/$dir_name" ] && rm -rf "$EXTENSIONS_DIR/$dir_name"
212
- done
213
- echo " 已安装到: $TARGET_DIR"
214
-
215
- # [3/3] 输出新版本号和升级报告
216
- echo ""
217
- echo "[3/3] 验证安装..."
218
- NEW_VERSION="$(node -e "
219
- try {
220
- const fs = require('fs');
221
- const path = require('path');
222
- const p = path.join('$EXTENSIONS_DIR', 'openclaw-qqbot', 'package.json');
223
- if (fs.existsSync(p)) {
224
- const v = JSON.parse(fs.readFileSync(p, 'utf8')).version;
225
- if (v) { process.stdout.write(v); process.exit(0); }
226
- }
227
- } catch {}
228
- " 2>/dev/null || true)"
229
- echo "QQBOT_NEW_VERSION=${NEW_VERSION:-unknown}"
230
-
231
- if [ -n "$NEW_VERSION" ] && [ "$NEW_VERSION" != "unknown" ]; then
232
- echo "QQBOT_REPORT=✅ QQBot 升级完成 ($PKG_NAME): v${NEW_VERSION}"
233
- else
234
- echo "QQBOT_REPORT=⚠️ QQBot 升级异常,无法确认新版本"
235
- fi
236
-
237
- echo ""
238
- echo "==========================================="
239
- echo " ✅ 文件安装完成"
240
- echo "==========================================="
241
-
242
- # --no-restart 模式
243
- if [ "$NO_RESTART" = "true" ]; then
244
- echo ""
245
- echo "[跳过重启] --no-restart 已指定,脚本立即退出以便调用方触发 gateway restart"
246
- exit 0
247
- fi
248
-
249
- # [4/4] 配置 appid/secret(仅在提供了参数时执行)
250
- if [ -n "$APPID" ] && [ -n "$SECRET" ]; then
251
- echo ""
252
- echo "[配置] 写入 qqbot 通道配置..."
253
- DESIRED_TOKEN="${APPID}:${SECRET}"
254
-
255
- CURRENT_TOKEN=""
256
- for _app in openclaw clawdbot moltbot; do
257
- _cfg="$HOME/.$_app/$_app.json"
258
- if [ -f "$_cfg" ]; then
259
- CURRENT_TOKEN=$(node -e "
260
- const cfg = JSON.parse(require('fs').readFileSync('$_cfg', 'utf8'));
261
- const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
262
- for (const key of keys) {
263
- const ch = cfg.channels && cfg.channels[key];
264
- if (!ch) continue;
265
- if (ch.token) { process.stdout.write(ch.token); process.exit(0); }
266
- if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId + ':' + ch.clientSecret); process.exit(0); }
267
- }
268
- " 2>/dev/null || true)
269
- [ -n "$CURRENT_TOKEN" ] && break
270
- fi
271
- done
272
-
273
- if [ "$CURRENT_TOKEN" = "$DESIRED_TOKEN" ]; then
274
- echo " ✅ 当前配置已是目标值,跳过写入"
275
- elif $CMD channels add --channel qqbot --token "$DESIRED_TOKEN" 2>&1; then
276
- echo " ✅ 通道配置写入成功"
277
- else
278
- echo " ⚠️ $CMD channels add 失败,尝试直接编辑配置文件..."
279
- CONFIG_FILE="$HOME/.$CMD/$CMD.json"
280
- if [ -f "$CONFIG_FILE" ] && node -e "
281
- const fs = require('fs');
282
- const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
283
- if (!cfg.channels) cfg.channels = {};
284
- if (!cfg.channels.qqbot) cfg.channels.qqbot = {};
285
- cfg.channels.qqbot.appId = '$APPID';
286
- cfg.channels.qqbot.clientSecret = '$SECRET';
287
- fs.writeFileSync('$CONFIG_FILE', JSON.stringify(cfg, null, 4) + '\n');
288
- " 2>&1; then
289
- echo " ✅ 通道配置写入成功(直接编辑配置文件)"
290
- else
291
- echo " ❌ 配置写入失败,请手动配置:"
292
- echo " $CMD channels add --channel qqbot --token \"${APPID}:${SECRET}\""
293
- fi
294
- fi
295
- elif [ -n "$APPID" ] || [ -n "$SECRET" ]; then
296
- echo ""
297
- echo "⚠️ --appid 和 --secret 必须同时提供"
298
- fi
299
-
300
- # [5/5] 重启 gateway 使新版本生效
301
- echo ""
302
- echo "[重启] 重启 gateway 使新版本生效..."
303
- if $CMD gateway restart 2>&1; then
304
- echo " ✅ gateway 已重启"
305
- else
306
- echo " ⚠️ gateway 重启失败,请手动执行: $CMD gateway restart"
307
- fi