@ryantest/openclaw-qqbot 1.6.7-beta.1 → 1.6.7-beta.10
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 +1 -1
- package/README.zh.md +1 -1
- package/dist/src/api.js +1 -1
- package/dist/src/slash-commands.js +271 -27
- package/dist/src/types.d.ts +9 -2
- package/dist/src/update-checker.d.ts +3 -1
- package/dist/src/update-checker.js +13 -2
- package/package.json +1 -1
- package/scripts/upgrade-via-npm.ps1 +9 -0
- package/scripts/upgrade-via-npm.sh +139 -30
- package/scripts/upgrade-via-source.sh +37 -30
- package/src/api.ts +1 -1
- package/src/slash-commands.ts +264 -26
- package/src/types.ts +9 -2
- package/src/update-checker.ts +14 -2
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.
|
|
13
|
+
### 🚀 Current Version: `v1.6.7`
|
|
14
14
|
|
|
15
15
|
[](./LICENSE)
|
|
16
16
|
[](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.
|
|
12
|
+
### 🚀 当前版本: `v1.6.7`
|
|
13
13
|
|
|
14
14
|
[](./LICENSE)
|
|
15
15
|
[](https://bot.q.qq.com/wiki/)
|
package/dist/src/api.js
CHANGED
|
@@ -106,7 +106,7 @@ async function doFetchToken(appId, clientSecret) {
|
|
|
106
106
|
const requestBody = { appId, clientSecret };
|
|
107
107
|
const requestHeaders = { "Content-Type": "application/json", "User-Agent": PLUGIN_USER_AGENT };
|
|
108
108
|
// 打印请求信息(隐藏敏感信息)
|
|
109
|
-
console.log(`[qqbot-api:${appId}] >>> POST ${TOKEN_URL}`);
|
|
109
|
+
console.log(`[qqbot-api:${appId}] >>> POST ${TOKEN_URL} [secret: ${clientSecret.slice(0, 6)}...len=${clientSecret.length}]`);
|
|
110
110
|
let response;
|
|
111
111
|
try {
|
|
112
112
|
response = await fetch(TOKEN_URL, {
|
|
@@ -152,7 +152,7 @@ function checkUpgradeCompatibility() {
|
|
|
152
152
|
// 3. 检查 Node.js 版本
|
|
153
153
|
const nodeVer = process.version.replace(/^v/, "");
|
|
154
154
|
if (compareSemver(nodeVer, req.minNodeVersion) < 0) {
|
|
155
|
-
errors.push(`❌
|
|
155
|
+
errors.push(`❌ NoVBNde.js 版本过低:当前 **v${nodeVer}**,热更新要求最低 **v${req.minNodeVersion}**`);
|
|
156
156
|
}
|
|
157
157
|
// 4. 检查系统架构(arm 等特殊架构提示)
|
|
158
158
|
const arch = process.arch;
|
|
@@ -642,15 +642,24 @@ function cleanupTempScript() {
|
|
|
642
642
|
*
|
|
643
643
|
* 安全机制:脚本会被复制到临时目录再执行,避免升级过程中插件目录被操作导致脚本自身丢失。
|
|
644
644
|
*/
|
|
645
|
-
function fireHotUpgrade(targetVersion) {
|
|
646
|
-
//
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
645
|
+
function fireHotUpgrade(targetVersion, pkg, useLocal) {
|
|
646
|
+
// --local: 直接使用本地脚本,跳过远端下载
|
|
647
|
+
// 默认: 优先从远端下载升级脚本,避免使用本地可能过时的版本
|
|
648
|
+
const scriptPath = useLocal
|
|
649
|
+
? (() => {
|
|
650
|
+
const local = getUpgradeScriptPath();
|
|
651
|
+
if (!local)
|
|
652
|
+
return null;
|
|
653
|
+
console.log(`[qqbot] fireHotUpgrade: --local specified, using local script: ${local}`);
|
|
654
|
+
return copyScriptToTemp(local) || local;
|
|
655
|
+
})()
|
|
656
|
+
: downloadRemoteUpgradeScript() || (() => {
|
|
657
|
+
const local = getUpgradeScriptPath();
|
|
658
|
+
if (!local)
|
|
659
|
+
return null;
|
|
660
|
+
console.log(`[qqbot] fireHotUpgrade: remote download failed, falling back to local script: ${local}`);
|
|
661
|
+
return copyScriptToTemp(local) || local;
|
|
662
|
+
})();
|
|
654
663
|
if (!scriptPath)
|
|
655
664
|
return { ok: false, reason: "no-script" };
|
|
656
665
|
const cli = findCli();
|
|
@@ -669,6 +678,7 @@ function fireHotUpgrade(targetVersion) {
|
|
|
669
678
|
"-File", scriptPath,
|
|
670
679
|
"-NoRestart",
|
|
671
680
|
...(targetVersion ? ["-Version", targetVersion] : []),
|
|
681
|
+
...(pkg ? ["-Pkg", pkg] : []),
|
|
672
682
|
];
|
|
673
683
|
}
|
|
674
684
|
else {
|
|
@@ -677,13 +687,106 @@ function fireHotUpgrade(targetVersion) {
|
|
|
677
687
|
if (!bash)
|
|
678
688
|
return { ok: false, reason: "no-bash" };
|
|
679
689
|
shell = bash;
|
|
680
|
-
shellArgs = [scriptPath, "--no-restart", ...(targetVersion ? ["--version", targetVersion] : [])];
|
|
690
|
+
shellArgs = [scriptPath, "--no-restart", ...(targetVersion ? ["--version", targetVersion] : []), ...(pkg ? ["--pkg", pkg] : [])];
|
|
691
|
+
}
|
|
692
|
+
console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}, pkg=${pkg || "default"}`);
|
|
693
|
+
// ── 兼容 openclaw 3.23+ 配置严格校验 ──
|
|
694
|
+
// openclaw plugins install/update 启动时会校验整个配置文件,
|
|
695
|
+
// 如果 channels.qqbot 已存在但 qqbot 插件尚未加载,校验会报 "unknown channel id: qqbot"。
|
|
696
|
+
//
|
|
697
|
+
// 在 TS 端(而非 shell 脚本端)创建临时配置,确保无论脚本版本新旧都能正确绕过校验。
|
|
698
|
+
// 通过 OPENCLAW_CONFIG_PATH 环境变量让 openclaw CLI 子进程使用临时配置。
|
|
699
|
+
const homeDir = getHomeDir();
|
|
700
|
+
const realConfigPath = path.join(homeDir, ".openclaw", "openclaw.json");
|
|
701
|
+
let tempConfigPath = null;
|
|
702
|
+
const childEnv = { ...process.env };
|
|
703
|
+
try {
|
|
704
|
+
if (fs.existsSync(realConfigPath)) {
|
|
705
|
+
const cfg = JSON.parse(fs.readFileSync(realConfigPath, "utf8"));
|
|
706
|
+
let needsTemp = false;
|
|
707
|
+
// 检查是否存在会导致校验失败的配置项
|
|
708
|
+
if (cfg.channels?.qqbot)
|
|
709
|
+
needsTemp = true;
|
|
710
|
+
if (Array.isArray(cfg.plugins?.allow) && cfg.plugins.allow.includes("openclaw-qqbot"))
|
|
711
|
+
needsTemp = true;
|
|
712
|
+
if (cfg.plugins?.entries?.["openclaw-qqbot"])
|
|
713
|
+
needsTemp = true;
|
|
714
|
+
if (needsTemp) {
|
|
715
|
+
const tmpCfg = JSON.parse(JSON.stringify(cfg)); // deep clone
|
|
716
|
+
// 移除 channels.qqbot
|
|
717
|
+
if (tmpCfg.channels?.qqbot) {
|
|
718
|
+
delete tmpCfg.channels.qqbot;
|
|
719
|
+
if (Object.keys(tmpCfg.channels).length === 0)
|
|
720
|
+
delete tmpCfg.channels;
|
|
721
|
+
}
|
|
722
|
+
// 移除 plugins.allow 中的 openclaw-qqbot
|
|
723
|
+
if (Array.isArray(tmpCfg.plugins?.allow)) {
|
|
724
|
+
tmpCfg.plugins.allow = tmpCfg.plugins.allow.filter((p) => p !== "openclaw-qqbot");
|
|
725
|
+
if (tmpCfg.plugins.allow.length === 0)
|
|
726
|
+
delete tmpCfg.plugins.allow;
|
|
727
|
+
}
|
|
728
|
+
// 移除 plugins.entries 中的 openclaw-qqbot
|
|
729
|
+
if (tmpCfg.plugins?.entries?.["openclaw-qqbot"]) {
|
|
730
|
+
delete tmpCfg.plugins.entries["openclaw-qqbot"];
|
|
731
|
+
if (Object.keys(tmpCfg.plugins.entries).length === 0)
|
|
732
|
+
delete tmpCfg.plugins.entries;
|
|
733
|
+
}
|
|
734
|
+
const tmpDir = path.join(homeDir, ".openclaw", ".qqbot-upgrade-tmp");
|
|
735
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
736
|
+
tempConfigPath = path.join(tmpDir, "openclaw-tmp.json");
|
|
737
|
+
fs.writeFileSync(tempConfigPath, JSON.stringify(tmpCfg, null, 4) + "\n");
|
|
738
|
+
childEnv.OPENCLAW_CONFIG_PATH = tempConfigPath;
|
|
739
|
+
console.log(`[qqbot] fireHotUpgrade: created temp config (without channels.qqbot) at ${tempConfigPath}`);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
catch (e) {
|
|
744
|
+
console.warn(`[qqbot] fireHotUpgrade: failed to create temp config: ${e.message}, proceeding with original`);
|
|
745
|
+
tempConfigPath = null;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* 将临时配置中 plugins install/update 写入的记录同步回真实配置,然后清理临时文件。
|
|
749
|
+
*/
|
|
750
|
+
function syncTempConfigAndCleanup() {
|
|
751
|
+
if (!tempConfigPath)
|
|
752
|
+
return;
|
|
753
|
+
try {
|
|
754
|
+
if (fs.existsSync(tempConfigPath) && fs.existsSync(realConfigPath)) {
|
|
755
|
+
const tmp = JSON.parse(fs.readFileSync(tempConfigPath, "utf8"));
|
|
756
|
+
const real = JSON.parse(fs.readFileSync(realConfigPath, "utf8"));
|
|
757
|
+
let changed = false;
|
|
758
|
+
// 同步 plugins.installs
|
|
759
|
+
if (tmp.plugins?.installs) {
|
|
760
|
+
if (!real.plugins)
|
|
761
|
+
real.plugins = {};
|
|
762
|
+
real.plugins.installs = { ...(real.plugins.installs || {}), ...tmp.plugins.installs };
|
|
763
|
+
changed = true;
|
|
764
|
+
}
|
|
765
|
+
// 同步 plugins.entries
|
|
766
|
+
if (tmp.plugins?.entries) {
|
|
767
|
+
if (!real.plugins)
|
|
768
|
+
real.plugins = {};
|
|
769
|
+
real.plugins.entries = { ...(real.plugins.entries || {}), ...tmp.plugins.entries };
|
|
770
|
+
changed = true;
|
|
771
|
+
}
|
|
772
|
+
if (changed) {
|
|
773
|
+
fs.writeFileSync(realConfigPath, JSON.stringify(real, null, 4) + "\n");
|
|
774
|
+
console.log("[qqbot] fireHotUpgrade: synced install/entries records from temp config to real config");
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
catch (e) {
|
|
779
|
+
console.warn(`[qqbot] fireHotUpgrade: failed to sync temp config: ${e.message}`);
|
|
780
|
+
}
|
|
781
|
+
try {
|
|
782
|
+
fs.unlinkSync(tempConfigPath);
|
|
783
|
+
}
|
|
784
|
+
catch { /* ignore */ }
|
|
681
785
|
}
|
|
682
|
-
console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}`);
|
|
683
786
|
// 异步执行升级脚本
|
|
684
787
|
execFile(shell, shellArgs, {
|
|
685
788
|
timeout: 120_000,
|
|
686
|
-
env:
|
|
789
|
+
env: childEnv,
|
|
687
790
|
...(isWindows() ? { windowsHide: true } : {}),
|
|
688
791
|
}, (error, stdout, _stderr) => {
|
|
689
792
|
if (error) {
|
|
@@ -692,6 +795,7 @@ function fireHotUpgrade(targetVersion) {
|
|
|
692
795
|
console.error(`[qqbot] fireHotUpgrade: stdout: ${stdout.slice(0, 2000)}`);
|
|
693
796
|
if (_stderr)
|
|
694
797
|
console.error(`[qqbot] fireHotUpgrade: stderr: ${_stderr.slice(0, 2000)}`);
|
|
798
|
+
syncTempConfigAndCleanup();
|
|
695
799
|
cleanupTempScript();
|
|
696
800
|
_upgrading = false;
|
|
697
801
|
return;
|
|
@@ -702,12 +806,14 @@ function fireHotUpgrade(targetVersion) {
|
|
|
702
806
|
const newVersion = versionMatch?.[1];
|
|
703
807
|
if (newVersion === "unknown") {
|
|
704
808
|
console.error(`[qqbot] fireHotUpgrade: script output QQBOT_NEW_VERSION=unknown, aborting restart`);
|
|
809
|
+
syncTempConfigAndCleanup();
|
|
705
810
|
cleanupTempScript();
|
|
706
811
|
_upgrading = false;
|
|
707
812
|
return;
|
|
708
813
|
}
|
|
709
814
|
console.log(`[qqbot] fireHotUpgrade: new version=${newVersion || "(not detected)"}, triggering restart...`);
|
|
710
|
-
//
|
|
815
|
+
// 脚本执行成功,同步临时配置记录并清理
|
|
816
|
+
syncTempConfigAndCleanup();
|
|
711
817
|
cleanupTempScript();
|
|
712
818
|
// 文件替换成功,在 restart 之前把 source 从 path 切换为 npm,
|
|
713
819
|
// 确保新进程启动时读到的是 npm source,不会被本地源码覆盖。
|
|
@@ -750,17 +856,122 @@ function fireHotUpgrade(targetVersion) {
|
|
|
750
856
|
}
|
|
751
857
|
}
|
|
752
858
|
else {
|
|
753
|
-
// Mac/Linux:
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
859
|
+
// Mac/Linux: 使用 detached shell 脚本执行 stop+start
|
|
860
|
+
//
|
|
861
|
+
// 兼容 openclaw 2026.3.24+ 配置严格校验:
|
|
862
|
+
// gateway restart 时 openclaw 先校验配置(loadConfig)再加载插件。
|
|
863
|
+
// 如果 channels.qqbot 存在但 qqbot channel type 尚未注册,校验会失败。
|
|
864
|
+
// 解决:stop 后临时移除 channels.qqbot → start(插件加载、qqbot type 注册)→ 恢复。
|
|
865
|
+
const cliInvoke = cli.endsWith(".mjs")
|
|
866
|
+
? `"${process.execPath}" "${cli}"`
|
|
867
|
+
: `"${cli}"`;
|
|
868
|
+
const homeDir = getHomeDir();
|
|
869
|
+
const configPath = path.join(homeDir, ".openclaw", "openclaw.json");
|
|
870
|
+
const qqbotChannelBackup = path.join(homeDir, ".openclaw", ".qqbot-channel-backup.json");
|
|
871
|
+
const restartScript = path.join(homeDir, ".openclaw", ".qqbot-restart.sh");
|
|
872
|
+
// 先保存 channels.qqbot 到临时文件(在当前进程中,JSON 处理更安全)
|
|
873
|
+
let hasChannel = false;
|
|
874
|
+
try {
|
|
875
|
+
const cfgRaw = fs.readFileSync(configPath, "utf8");
|
|
876
|
+
const cfg = JSON.parse(cfgRaw);
|
|
877
|
+
const qqbot = cfg?.channels?.qqbot;
|
|
878
|
+
if (qqbot) {
|
|
879
|
+
fs.writeFileSync(qqbotChannelBackup, JSON.stringify(qqbot, null, 2), "utf8");
|
|
880
|
+
hasChannel = true;
|
|
762
881
|
}
|
|
763
|
-
}
|
|
882
|
+
}
|
|
883
|
+
catch {
|
|
884
|
+
// 配置文件不存在或 JSON 解析失败,不做处理
|
|
885
|
+
}
|
|
886
|
+
const shContent = `#!/bin/bash
|
|
887
|
+
# 注意:不使用 set -e,因为 gateway start 失败时仍需恢复 channels.qqbot
|
|
888
|
+
CLI="${cliInvoke}"
|
|
889
|
+
CONFIG="${configPath}"
|
|
890
|
+
BACKUP="${qqbotChannelBackup}"
|
|
891
|
+
|
|
892
|
+
echo "[qqbot-upgrade] Stopping gateway..."
|
|
893
|
+
$CLI gateway stop 2>/dev/null || true
|
|
894
|
+
sleep 2
|
|
895
|
+
|
|
896
|
+
if [ -f "$BACKUP" ]; then
|
|
897
|
+
echo "[qqbot-upgrade] Temporarily removing channels.qqbot and plugins.entries for config validation bypass..."
|
|
898
|
+
node -e "
|
|
899
|
+
const fs = require('fs');
|
|
900
|
+
const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
901
|
+
let changed = false;
|
|
902
|
+
if (cfg.channels && cfg.channels.qqbot) {
|
|
903
|
+
delete cfg.channels.qqbot;
|
|
904
|
+
if (Object.keys(cfg.channels).length === 0) delete cfg.channels;
|
|
905
|
+
changed = true;
|
|
906
|
+
}
|
|
907
|
+
if (cfg.plugins && cfg.plugins.entries && cfg.plugins.entries['openclaw-qqbot']) {
|
|
908
|
+
delete cfg.plugins.entries['openclaw-qqbot'];
|
|
909
|
+
if (Object.keys(cfg.plugins.entries).length === 0) delete cfg.plugins.entries;
|
|
910
|
+
changed = true;
|
|
911
|
+
}
|
|
912
|
+
if (changed) {
|
|
913
|
+
fs.writeFileSync(process.argv[1], JSON.stringify(cfg, null, 4) + '\\n');
|
|
914
|
+
}
|
|
915
|
+
" "$CONFIG" 2>/dev/null
|
|
916
|
+
echo "[qqbot-upgrade] channels.qqbot temporarily removed"
|
|
917
|
+
fi
|
|
918
|
+
|
|
919
|
+
echo "[qqbot-upgrade] Starting gateway..."
|
|
920
|
+
START_OK=false
|
|
921
|
+
if $CLI gateway start 2>/dev/null; then
|
|
922
|
+
START_OK=true
|
|
923
|
+
echo "[qqbot-upgrade] Gateway started successfully"
|
|
924
|
+
else
|
|
925
|
+
echo "[qqbot-upgrade] WARNING: gateway start failed, will still restore config"
|
|
926
|
+
fi
|
|
927
|
+
|
|
928
|
+
echo "[qqbot-upgrade] Waiting for plugin to load (8s)..."
|
|
929
|
+
sleep 8
|
|
930
|
+
|
|
931
|
+
if [ -f "$BACKUP" ]; then
|
|
932
|
+
echo "[qqbot-upgrade] Restoring channels.qqbot and plugins.entries..."
|
|
933
|
+
node -e "
|
|
934
|
+
const fs = require('fs');
|
|
935
|
+
const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
936
|
+
const qqbot = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
937
|
+
if (!cfg.channels) cfg.channels = {};
|
|
938
|
+
cfg.channels.qqbot = qqbot;
|
|
939
|
+
// 恢复 plugins.entries
|
|
940
|
+
if (!cfg.plugins) cfg.plugins = {};
|
|
941
|
+
if (!cfg.plugins.entries) cfg.plugins.entries = {};
|
|
942
|
+
if (!cfg.plugins.entries['openclaw-qqbot']) {
|
|
943
|
+
cfg.plugins.entries['openclaw-qqbot'] = { enabled: true };
|
|
944
|
+
}
|
|
945
|
+
fs.writeFileSync(process.argv[1], JSON.stringify(cfg, null, 4) + '\\n');
|
|
946
|
+
" "$CONFIG" "$BACKUP" 2>/dev/null
|
|
947
|
+
rm -f "$BACKUP"
|
|
948
|
+
echo "[qqbot-upgrade] channels.qqbot restored"
|
|
949
|
+
fi
|
|
950
|
+
|
|
951
|
+
# 如果 start 失败,尝试再次启动(此时 channels.qqbot 已恢复,插件可能已注册)
|
|
952
|
+
if [ "$START_OK" != "true" ]; then
|
|
953
|
+
echo "[qqbot-upgrade] Retrying gateway start..."
|
|
954
|
+
sleep 2
|
|
955
|
+
$CLI gateway start 2>/dev/null || echo "[qqbot-upgrade] WARNING: retry also failed"
|
|
956
|
+
fi
|
|
957
|
+
|
|
958
|
+
# 清理自身
|
|
959
|
+
rm -f "$0"
|
|
960
|
+
echo "[qqbot-upgrade] Done."
|
|
961
|
+
`;
|
|
962
|
+
try {
|
|
963
|
+
fs.writeFileSync(restartScript, shContent, { mode: 0o755 });
|
|
964
|
+
const child = spawn("bash", [restartScript], {
|
|
965
|
+
detached: true,
|
|
966
|
+
stdio: "ignore",
|
|
967
|
+
});
|
|
968
|
+
child.unref();
|
|
969
|
+
console.log(`[qqbot] fireHotUpgrade: launched detached restart script (pid=${child.pid}), hasChannel=${hasChannel}`);
|
|
970
|
+
}
|
|
971
|
+
catch (shErr) {
|
|
972
|
+
console.error(`[qqbot] fireHotUpgrade: failed to launch restart script: ${shErr.message}, falling back to direct restart`);
|
|
973
|
+
execCliAsync(cli, ["gateway", "restart"], { timeout: 30_000 }, () => { });
|
|
974
|
+
}
|
|
764
975
|
}
|
|
765
976
|
});
|
|
766
977
|
return { ok: true };
|
|
@@ -786,11 +997,13 @@ registerCommand({
|
|
|
786
997
|
`/bot-upgrade 检查是否有新版本`,
|
|
787
998
|
`/bot-upgrade --latest 确认升级到最新版本(需 upgradeMode=hot-reload)`,
|
|
788
999
|
`/bot-upgrade --version X 升级到指定版本(需 upgradeMode=hot-reload)`,
|
|
1000
|
+
`/bot-upgrade --pkg scope/name 指定 npm 包(如 ryantest/openclaw-qqbot)`,
|
|
789
1001
|
`/bot-upgrade --force 强制重新安装当前版本(需 upgradeMode=hot-reload)`,
|
|
1002
|
+
`/bot-upgrade --local 使用本地升级脚本(跳过远端下载)`,
|
|
790
1003
|
].join("\n"),
|
|
791
1004
|
handler: async (ctx) => {
|
|
792
1005
|
const url = ctx.accountConfig?.upgradeUrl || DEFAULT_UPGRADE_URL;
|
|
793
|
-
const upgradeMode = ctx.accountConfig?.upgradeMode || "
|
|
1006
|
+
const upgradeMode = ctx.accountConfig?.upgradeMode || "hot-reload";
|
|
794
1007
|
const args = ctx.args.trim();
|
|
795
1008
|
const info = await getUpdateInfo();
|
|
796
1009
|
const GITHUB_URL = "https://github.com/tencent-connect/openclaw-qqbot/";
|
|
@@ -834,7 +1047,9 @@ registerCommand({
|
|
|
834
1047
|
}
|
|
835
1048
|
let isForce = false;
|
|
836
1049
|
let isLatest = false;
|
|
1050
|
+
let isLocal = false;
|
|
837
1051
|
let versionArg;
|
|
1052
|
+
let pkgArg;
|
|
838
1053
|
const tokens = args ? args.split(/\s+/).filter(Boolean) : [];
|
|
839
1054
|
for (let i = 0; i < tokens.length; i += 1) {
|
|
840
1055
|
const t = tokens[i];
|
|
@@ -846,6 +1061,27 @@ registerCommand({
|
|
|
846
1061
|
isLatest = true;
|
|
847
1062
|
continue;
|
|
848
1063
|
}
|
|
1064
|
+
if (t === "--local") {
|
|
1065
|
+
isLocal = true;
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
if (t === "--pkg") {
|
|
1069
|
+
const next = tokens[i + 1];
|
|
1070
|
+
if (!next || next.startsWith("--")) {
|
|
1071
|
+
return `❌ 参数错误:--pkg 需要包名\n\n示例:/bot-upgrade --pkg ryantest/openclaw-qqbot`;
|
|
1072
|
+
}
|
|
1073
|
+
pkgArg = next;
|
|
1074
|
+
i += 1;
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
if (t.startsWith("--pkg=")) {
|
|
1078
|
+
const v = t.slice("--pkg=".length).trim();
|
|
1079
|
+
if (!v) {
|
|
1080
|
+
return `❌ 参数错误:--pkg 需要包名\n\n示例:/bot-upgrade --pkg ryantest/openclaw-qqbot`;
|
|
1081
|
+
}
|
|
1082
|
+
pkgArg = v;
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
849
1085
|
if (t === "--version") {
|
|
850
1086
|
const next = tokens[i + 1];
|
|
851
1087
|
if (!next || next.startsWith("--")) {
|
|
@@ -904,9 +1140,17 @@ registerCommand({
|
|
|
904
1140
|
`🌟官方 GitHub 仓库:[点击前往](${GITHUB_URL})`,
|
|
905
1141
|
].join("\n");
|
|
906
1142
|
}
|
|
1143
|
+
// 解析 npm 包名:--pkg 参数 > 配置项 upgradePkg > 默认
|
|
1144
|
+
// 支持 "scope/name"(自动补 @)和 "@scope/name" 两种格式
|
|
1145
|
+
let upgradePkg = pkgArg || ctx.accountConfig?.upgradePkg;
|
|
1146
|
+
if (upgradePkg) {
|
|
1147
|
+
upgradePkg = upgradePkg.trim();
|
|
1148
|
+
if (!upgradePkg.startsWith("@"))
|
|
1149
|
+
upgradePkg = `@${upgradePkg}`;
|
|
1150
|
+
}
|
|
907
1151
|
// ── --version 指定版本:先校验版本号是否存在 ──
|
|
908
1152
|
if (versionArg) {
|
|
909
|
-
const exists = await checkVersionExists(versionArg);
|
|
1153
|
+
const exists = await checkVersionExists(versionArg, upgradePkg);
|
|
910
1154
|
if (!exists) {
|
|
911
1155
|
return `❌ 版本 ${versionArg} 不存在,请检查版本号`;
|
|
912
1156
|
}
|
|
@@ -951,7 +1195,7 @@ registerCommand({
|
|
|
951
1195
|
// 热更新前保存凭证快照,防止更新过程被打断导致 appId/secret 丢失
|
|
952
1196
|
preUpgradeCredentialBackup(ctx.accountId, ctx.appId);
|
|
953
1197
|
// 异步执行升级
|
|
954
|
-
const startResult = fireHotUpgrade(targetVersion);
|
|
1198
|
+
const startResult = fireHotUpgrade(targetVersion, upgradePkg, isLocal);
|
|
955
1199
|
if (!startResult.ok) {
|
|
956
1200
|
_upgrading = false;
|
|
957
1201
|
if (startResult.reason === "no-script") {
|
package/dist/src/types.d.ts
CHANGED
|
@@ -92,10 +92,17 @@ export interface QQBotAccountConfig {
|
|
|
92
92
|
upgradeUrl?: string;
|
|
93
93
|
/**
|
|
94
94
|
* /bot-upgrade 指令的行为模式
|
|
95
|
-
* - "doc"
|
|
96
|
-
* - "hot-reload":检测到新版本时直接执行 npm
|
|
95
|
+
* - "doc":展示升级文档链接(安全模式)
|
|
96
|
+
* - "hot-reload":检测到新版本时直接执行 npm 升级脚本进行热更新(默认)
|
|
97
97
|
*/
|
|
98
98
|
upgradeMode?: "doc" | "hot-reload";
|
|
99
|
+
/**
|
|
100
|
+
* /bot-upgrade 热更新时使用的 npm 包名
|
|
101
|
+
* 支持 "scope/name"(自动补 @)或 "@scope/name" 格式
|
|
102
|
+
* 默认: "@tencent-connect/openclaw-qqbot"
|
|
103
|
+
* 示例: "ryantest/openclaw-qqbot"
|
|
104
|
+
*/
|
|
105
|
+
upgradePkg?: string;
|
|
99
106
|
/**
|
|
100
107
|
* 出站消息合并回复(debounce)配置
|
|
101
108
|
* 当短时间内收到多次 deliver 时,将文本合并为一条消息发送,避免消息轰炸
|
|
@@ -30,5 +30,7 @@ export declare function getUpdateInfo(): Promise<UpdateInfo>;
|
|
|
30
30
|
/**
|
|
31
31
|
* 检查指定版本是否存在于 npm registry
|
|
32
32
|
* 用于 /bot-upgrade --version 的前置校验
|
|
33
|
+
* @param version 要检查的版本号
|
|
34
|
+
* @param pkgName 可选的包名(如 "@ryantest/openclaw-qqbot"),默认使用内置包名
|
|
33
35
|
*/
|
|
34
|
-
export declare function checkVersionExists(version: string): Promise<boolean>;
|
|
36
|
+
export declare function checkVersionExists(version: string, pkgName?: string): Promise<boolean>;
|
|
@@ -99,9 +99,12 @@ export async function getUpdateInfo() {
|
|
|
99
99
|
/**
|
|
100
100
|
* 检查指定版本是否存在于 npm registry
|
|
101
101
|
* 用于 /bot-upgrade --version 的前置校验
|
|
102
|
+
* @param version 要检查的版本号
|
|
103
|
+
* @param pkgName 可选的包名(如 "@ryantest/openclaw-qqbot"),默认使用内置包名
|
|
102
104
|
*/
|
|
103
|
-
export async function checkVersionExists(version) {
|
|
104
|
-
|
|
105
|
+
export async function checkVersionExists(version, pkgName) {
|
|
106
|
+
const registries = pkgName ? buildRegistries(pkgName) : REGISTRIES;
|
|
107
|
+
for (const baseUrl of registries) {
|
|
105
108
|
try {
|
|
106
109
|
const url = `${baseUrl}/${version}`;
|
|
107
110
|
const json = await fetchJson(url, 10_000);
|
|
@@ -114,6 +117,14 @@ export async function checkVersionExists(version) {
|
|
|
114
117
|
}
|
|
115
118
|
return false;
|
|
116
119
|
}
|
|
120
|
+
/** 根据自定义包名构建 registry URL 列表 */
|
|
121
|
+
function buildRegistries(pkgName) {
|
|
122
|
+
const encoded = encodeURIComponent(pkgName);
|
|
123
|
+
return [
|
|
124
|
+
`https://registry.npmjs.org/${encoded}`,
|
|
125
|
+
`https://registry.npmmirror.com/${encoded}`,
|
|
126
|
+
];
|
|
127
|
+
}
|
|
117
128
|
function compareVersions(a, b) {
|
|
118
129
|
const parse = (v) => {
|
|
119
130
|
const clean = v.replace(/^v/, "");
|
package/package.json
CHANGED
|
@@ -17,6 +17,7 @@ param(
|
|
|
17
17
|
[string]$Secret = "",
|
|
18
18
|
[switch]$NoRestart,
|
|
19
19
|
[string]$Tag = "",
|
|
20
|
+
[string]$Pkg = "",
|
|
20
21
|
[switch]$Help
|
|
21
22
|
)
|
|
22
23
|
|
|
@@ -25,6 +26,13 @@ $PKG_NAME = "@tencent-connect/openclaw-qqbot"
|
|
|
25
26
|
$SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
|
26
27
|
$PROJECT_DIR = Split-Path -Parent $SCRIPT_DIR
|
|
27
28
|
|
|
29
|
+
# -Pkg 覆盖包名(支持 "scope/name" 自动补 @)
|
|
30
|
+
if ($Pkg) {
|
|
31
|
+
$Pkg = $Pkg.Trim()
|
|
32
|
+
if (-not $Pkg.StartsWith("@")) { $Pkg = "@$Pkg" }
|
|
33
|
+
$PKG_NAME = $Pkg
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
# Read local version
|
|
29
37
|
$LOCAL_VERSION = ""
|
|
30
38
|
try {
|
|
@@ -41,6 +49,7 @@ if ($Help) {
|
|
|
41
49
|
Write-Host " .\upgrade-via-npm.ps1 -Version [version] # upgrade to specific version"
|
|
42
50
|
Write-Host " .\upgrade-via-npm.ps1 -SelfVersion # upgrade to repo version ($LOCAL_VERSION)"
|
|
43
51
|
Write-Host ""
|
|
52
|
+
Write-Host " -Pkg [scope/name] Custom npm package (e.g. ryantest/openclaw-qqbot)"
|
|
44
53
|
Write-Host " -AppId [appid] QQ bot appid (required on first install)"
|
|
45
54
|
Write-Host " -Secret [secret] QQ bot secret (required on first install)"
|
|
46
55
|
exit 0
|