@ryantest/openclaw-qqbot 1.6.7-beta.2 → 1.6.7-beta.4
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/dist/src/api.js +1 -1
- package/dist/src/slash-commands.js +57 -14
- package/dist/src/types.d.ts +7 -0
- 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 +17 -8
- package/scripts/upgrade-via-source.sh +34 -29
- package/src/api.ts +1 -1
- package/src/slash-commands.ts +55 -13
- package/src/types.ts +7 -0
- package/src/update-checker.ts +14 -2
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,9 +687,9 @@ 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] : [])];
|
|
681
691
|
}
|
|
682
|
-
console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}`);
|
|
692
|
+
console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}, pkg=${pkg || "default"}`);
|
|
683
693
|
// 异步执行升级脚本
|
|
684
694
|
execFile(shell, shellArgs, {
|
|
685
695
|
timeout: 120_000,
|
|
@@ -786,7 +796,9 @@ registerCommand({
|
|
|
786
796
|
`/bot-upgrade 检查是否有新版本`,
|
|
787
797
|
`/bot-upgrade --latest 确认升级到最新版本(需 upgradeMode=hot-reload)`,
|
|
788
798
|
`/bot-upgrade --version X 升级到指定版本(需 upgradeMode=hot-reload)`,
|
|
799
|
+
`/bot-upgrade --pkg scope/name 指定 npm 包(如 ryantest/openclaw-qqbot)`,
|
|
789
800
|
`/bot-upgrade --force 强制重新安装当前版本(需 upgradeMode=hot-reload)`,
|
|
801
|
+
`/bot-upgrade --local 使用本地升级脚本(跳过远端下载)`,
|
|
790
802
|
].join("\n"),
|
|
791
803
|
handler: async (ctx) => {
|
|
792
804
|
const url = ctx.accountConfig?.upgradeUrl || DEFAULT_UPGRADE_URL;
|
|
@@ -834,7 +846,9 @@ registerCommand({
|
|
|
834
846
|
}
|
|
835
847
|
let isForce = false;
|
|
836
848
|
let isLatest = false;
|
|
849
|
+
let isLocal = false;
|
|
837
850
|
let versionArg;
|
|
851
|
+
let pkgArg;
|
|
838
852
|
const tokens = args ? args.split(/\s+/).filter(Boolean) : [];
|
|
839
853
|
for (let i = 0; i < tokens.length; i += 1) {
|
|
840
854
|
const t = tokens[i];
|
|
@@ -846,6 +860,27 @@ registerCommand({
|
|
|
846
860
|
isLatest = true;
|
|
847
861
|
continue;
|
|
848
862
|
}
|
|
863
|
+
if (t === "--local") {
|
|
864
|
+
isLocal = true;
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
if (t === "--pkg") {
|
|
868
|
+
const next = tokens[i + 1];
|
|
869
|
+
if (!next || next.startsWith("--")) {
|
|
870
|
+
return `❌ 参数错误:--pkg 需要包名\n\n示例:/bot-upgrade --pkg ryantest/openclaw-qqbot`;
|
|
871
|
+
}
|
|
872
|
+
pkgArg = next;
|
|
873
|
+
i += 1;
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
if (t.startsWith("--pkg=")) {
|
|
877
|
+
const v = t.slice("--pkg=".length).trim();
|
|
878
|
+
if (!v) {
|
|
879
|
+
return `❌ 参数错误:--pkg 需要包名\n\n示例:/bot-upgrade --pkg ryantest/openclaw-qqbot`;
|
|
880
|
+
}
|
|
881
|
+
pkgArg = v;
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
849
884
|
if (t === "--version") {
|
|
850
885
|
const next = tokens[i + 1];
|
|
851
886
|
if (!next || next.startsWith("--")) {
|
|
@@ -904,9 +939,17 @@ registerCommand({
|
|
|
904
939
|
`🌟官方 GitHub 仓库:[点击前往](${GITHUB_URL})`,
|
|
905
940
|
].join("\n");
|
|
906
941
|
}
|
|
942
|
+
// 解析 npm 包名:--pkg 参数 > 配置项 upgradePkg > 默认
|
|
943
|
+
// 支持 "scope/name"(自动补 @)和 "@scope/name" 两种格式
|
|
944
|
+
let upgradePkg = pkgArg || ctx.accountConfig?.upgradePkg;
|
|
945
|
+
if (upgradePkg) {
|
|
946
|
+
upgradePkg = upgradePkg.trim();
|
|
947
|
+
if (!upgradePkg.startsWith("@"))
|
|
948
|
+
upgradePkg = `@${upgradePkg}`;
|
|
949
|
+
}
|
|
907
950
|
// ── --version 指定版本:先校验版本号是否存在 ──
|
|
908
951
|
if (versionArg) {
|
|
909
|
-
const exists = await checkVersionExists(versionArg);
|
|
952
|
+
const exists = await checkVersionExists(versionArg, upgradePkg);
|
|
910
953
|
if (!exists) {
|
|
911
954
|
return `❌ 版本 ${versionArg} 不存在,请检查版本号`;
|
|
912
955
|
}
|
|
@@ -951,7 +994,7 @@ registerCommand({
|
|
|
951
994
|
// 热更新前保存凭证快照,防止更新过程被打断导致 appId/secret 丢失
|
|
952
995
|
preUpgradeCredentialBackup(ctx.accountId, ctx.appId);
|
|
953
996
|
// 异步执行升级
|
|
954
|
-
const startResult = fireHotUpgrade(targetVersion);
|
|
997
|
+
const startResult = fireHotUpgrade(targetVersion, upgradePkg, isLocal);
|
|
955
998
|
if (!startResult.ok) {
|
|
956
999
|
_upgrading = false;
|
|
957
1000
|
if (startResult.reason === "no-script") {
|
package/dist/src/types.d.ts
CHANGED
|
@@ -96,6 +96,13 @@ export interface QQBotAccountConfig {
|
|
|
96
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
|
|
@@ -63,6 +63,7 @@ print_usage() {
|
|
|
63
63
|
echo " upgrade-via-npm.sh --self-version # 升级到当前仓库版本"
|
|
64
64
|
fi
|
|
65
65
|
echo ""
|
|
66
|
+
echo " --pkg <scope/name> 指定 npm 包名(如 ryantest/openclaw-qqbot)"
|
|
66
67
|
echo " --appid <appid> QQ机器人 appid(首次安装时必填)"
|
|
67
68
|
echo " --secret <secret> QQ机器人 secret(首次安装时必填)"
|
|
68
69
|
echo ""
|
|
@@ -76,22 +77,17 @@ while [[ $# -gt 0 ]]; do
|
|
|
76
77
|
case "$1" in
|
|
77
78
|
--tag)
|
|
78
79
|
[ -z "$2" ] && echo "❌ --tag 需要参数" && exit 1
|
|
79
|
-
|
|
80
|
-
TARGET_VERSION="$_ver"
|
|
81
|
-
INSTALL_SRC="${PKG_NAME}@$_ver"
|
|
80
|
+
TARGET_VERSION="${2#v}" # 去掉 v 前缀(npm 版本号不带 v)
|
|
82
81
|
shift 2
|
|
83
82
|
;;
|
|
84
83
|
--version)
|
|
85
84
|
[ -z "$2" ] && echo "❌ --version 需要参数" && exit 1
|
|
86
|
-
|
|
87
|
-
TARGET_VERSION="$_ver"
|
|
88
|
-
INSTALL_SRC="${PKG_NAME}@$_ver"
|
|
85
|
+
TARGET_VERSION="${2#v}" # 去掉 v 前缀(npm 版本号不带 v)
|
|
89
86
|
shift 2
|
|
90
87
|
;;
|
|
91
88
|
--self-version)
|
|
92
89
|
[ -z "$LOCAL_VERSION" ] && echo "❌ 无法从 package.json 读取版本" && exit 1
|
|
93
90
|
TARGET_VERSION="$LOCAL_VERSION"
|
|
94
|
-
INSTALL_SRC="${PKG_NAME}@${LOCAL_VERSION}"
|
|
95
91
|
shift 1
|
|
96
92
|
;;
|
|
97
93
|
--appid)
|
|
@@ -104,6 +100,14 @@ while [[ $# -gt 0 ]]; do
|
|
|
104
100
|
SECRET="$2"
|
|
105
101
|
shift 2
|
|
106
102
|
;;
|
|
103
|
+
--pkg)
|
|
104
|
+
[ -z "$2" ] && echo "❌ --pkg 需要参数" && exit 1
|
|
105
|
+
_pkg="$2"
|
|
106
|
+
# 支持 "scope/name" 自动补 @
|
|
107
|
+
if [[ "$_pkg" != @* ]]; then _pkg="@$_pkg"; fi
|
|
108
|
+
PKG_NAME="$_pkg"
|
|
109
|
+
shift 2
|
|
110
|
+
;;
|
|
107
111
|
--no-restart)
|
|
108
112
|
NO_RESTART=true
|
|
109
113
|
shift 1
|
|
@@ -115,7 +119,12 @@ while [[ $# -gt 0 ]]; do
|
|
|
115
119
|
*) echo "未知选项: $1"; print_usage; exit 1 ;;
|
|
116
120
|
esac
|
|
117
121
|
done
|
|
118
|
-
INSTALL_SRC
|
|
122
|
+
# 参数解析完毕后统一拼接 INSTALL_SRC(确保 --pkg 无论在 --version 前后都能生效)
|
|
123
|
+
if [ -n "$TARGET_VERSION" ]; then
|
|
124
|
+
INSTALL_SRC="${PKG_NAME}@${TARGET_VERSION}"
|
|
125
|
+
else
|
|
126
|
+
INSTALL_SRC="${PKG_NAME}@latest"
|
|
127
|
+
fi
|
|
119
128
|
|
|
120
129
|
# 环境变量 fallback
|
|
121
130
|
APPID="${APPID:-$QQBOT_APPID}"
|
|
@@ -344,13 +344,13 @@ if [ ! -f "$_INSTALL_DIR/dist/index.js" ] || [ ! -f "$_INSTALL_DIR/preload.cjs"
|
|
|
344
344
|
echo "请先解决安装问题后再运行此脚本。"
|
|
345
345
|
# 恢复 channels.qqbot 后再退出
|
|
346
346
|
if [ -n "$_QQBOT_CHANNEL_STASH" ] && [ -n "$_STASH_CFG" ] && [ -f "$_STASH_CFG" ]; then
|
|
347
|
-
node -e
|
|
348
|
-
const fs = require(
|
|
349
|
-
const cfg = JSON.parse(fs.readFileSync(
|
|
347
|
+
_STASH="$_QQBOT_CHANNEL_STASH" _CFG="$_STASH_CFG" node -e '
|
|
348
|
+
const fs = require("fs");
|
|
349
|
+
const cfg = JSON.parse(fs.readFileSync(process.env._CFG, "utf8"));
|
|
350
350
|
if (!cfg.channels) cfg.channels = {};
|
|
351
|
-
cfg.channels.qqbot =
|
|
352
|
-
fs.writeFileSync(
|
|
353
|
-
|
|
351
|
+
cfg.channels.qqbot = JSON.parse(process.env._STASH);
|
|
352
|
+
fs.writeFileSync(process.env._CFG, JSON.stringify(cfg, null, 4) + "\n");
|
|
353
|
+
' 2>/dev/null || true
|
|
354
354
|
fi
|
|
355
355
|
exit 1
|
|
356
356
|
;;
|
|
@@ -592,11 +592,11 @@ echo "[4/6] 准备机器人通道配置..."
|
|
|
592
592
|
# 注意:channels.qqbot 已被暂存移除,所以从 _QQBOT_CHANNEL_STASH 读取
|
|
593
593
|
CURRENT_QQBOT_TOKEN=""
|
|
594
594
|
if [ -n "$_QQBOT_CHANNEL_STASH" ]; then
|
|
595
|
-
CURRENT_QQBOT_TOKEN=$(node -e
|
|
596
|
-
const ch =
|
|
595
|
+
CURRENT_QQBOT_TOKEN=$(_STASH="$_QQBOT_CHANNEL_STASH" node -e '
|
|
596
|
+
const ch = JSON.parse(process.env._STASH);
|
|
597
597
|
if (ch.token) { process.stdout.write(ch.token); }
|
|
598
|
-
else if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId +
|
|
599
|
-
|
|
598
|
+
else if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId + ":" + ch.clientSecret); }
|
|
599
|
+
' 2>/dev/null || true)
|
|
600
600
|
fi
|
|
601
601
|
|
|
602
602
|
DESIRED_QQBOT_TOKEN=""
|
|
@@ -787,34 +787,39 @@ case "$start_choice" in
|
|
|
787
787
|
|
|
788
788
|
if [ -n "$_target_cfg" ]; then
|
|
789
789
|
# 构建完整的 channels.qqbot 对象(合并暂存配置 + 新 token + markdown)
|
|
790
|
-
node -e "
|
|
791
|
-
|
|
792
|
-
|
|
790
|
+
# 通过环境变量传递,避免 JSON 双引号在 node -e "..." 中被 shell 错误解析
|
|
791
|
+
_STASH="$_QQBOT_CHANNEL_STASH" \
|
|
792
|
+
_DESIRED="$DESIRED_QQBOT_TOKEN" \
|
|
793
|
+
_MD="$MARKDOWN_VALUE" \
|
|
794
|
+
_CFG="$_target_cfg" \
|
|
795
|
+
node -e '
|
|
796
|
+
const fs = require("fs");
|
|
797
|
+
const cfg = JSON.parse(fs.readFileSync(process.env._CFG, "utf8"));
|
|
793
798
|
if (!cfg.channels) cfg.channels = {};
|
|
794
799
|
|
|
795
800
|
// 从暂存恢复基础配置
|
|
796
|
-
const stash =
|
|
801
|
+
const stash = process.env._STASH;
|
|
797
802
|
if (stash) {
|
|
798
803
|
try { cfg.channels.qqbot = JSON.parse(stash); } catch {}
|
|
799
804
|
}
|
|
800
805
|
if (!cfg.channels.qqbot) cfg.channels.qqbot = {};
|
|
801
806
|
|
|
802
807
|
// 覆盖 token(如果有新值)
|
|
803
|
-
const desired =
|
|
804
|
-
if (desired && desired.includes(
|
|
805
|
-
const [appId, ...rest] = desired.split(
|
|
808
|
+
const desired = process.env._DESIRED;
|
|
809
|
+
if (desired && desired.includes(":")) {
|
|
810
|
+
const [appId, ...rest] = desired.split(":");
|
|
806
811
|
cfg.channels.qqbot.appId = appId;
|
|
807
|
-
cfg.channels.qqbot.clientSecret = rest.join(
|
|
812
|
+
cfg.channels.qqbot.clientSecret = rest.join(":");
|
|
808
813
|
delete cfg.channels.qqbot.token;
|
|
809
814
|
}
|
|
810
815
|
|
|
811
816
|
// 覆盖 markdown(如果有指定)
|
|
812
|
-
const md =
|
|
813
|
-
if (md ===
|
|
814
|
-
else if (md ===
|
|
817
|
+
const md = process.env._MD;
|
|
818
|
+
if (md === "true") cfg.channels.qqbot.markdownSupport = true;
|
|
819
|
+
else if (md === "false") cfg.channels.qqbot.markdownSupport = false;
|
|
815
820
|
|
|
816
|
-
fs.writeFileSync(
|
|
817
|
-
|
|
821
|
+
fs.writeFileSync(process.env._CFG, JSON.stringify(cfg, null, 4) + "\n");
|
|
822
|
+
' 2>&1 || echo " ⚠️ 配置写入失败"
|
|
818
823
|
echo " ✅ 已恢复 channels.qqbot 配置(含 token/markdown)"
|
|
819
824
|
_need_reload=1
|
|
820
825
|
fi
|
|
@@ -884,13 +889,13 @@ case "$start_choice" in
|
|
|
884
889
|
# 注意:下次 gateway 启动可能因 "unknown channel id" 失败,
|
|
885
890
|
# 需要用户手动 stop → 移除 channels.qqbot → start → 恢复
|
|
886
891
|
if [ -n "$_QQBOT_CHANNEL_STASH" ] && [ -n "$_STASH_CFG" ] && [ -f "$_STASH_CFG" ]; then
|
|
887
|
-
node -e
|
|
888
|
-
const fs = require(
|
|
889
|
-
const cfg = JSON.parse(fs.readFileSync(
|
|
892
|
+
_STASH="$_QQBOT_CHANNEL_STASH" _CFG="$_STASH_CFG" node -e '
|
|
893
|
+
const fs = require("fs");
|
|
894
|
+
const cfg = JSON.parse(fs.readFileSync(process.env._CFG, "utf8"));
|
|
890
895
|
if (!cfg.channels) cfg.channels = {};
|
|
891
|
-
cfg.channels.qqbot =
|
|
892
|
-
fs.writeFileSync(
|
|
893
|
-
|
|
896
|
+
cfg.channels.qqbot = JSON.parse(process.env._STASH);
|
|
897
|
+
fs.writeFileSync(process.env._CFG, JSON.stringify(cfg, null, 4) + "\n");
|
|
898
|
+
' 2>/dev/null || true
|
|
894
899
|
echo " 已恢复 channels.qqbot 配置"
|
|
895
900
|
fi
|
|
896
901
|
echo ""
|
package/src/api.ts
CHANGED
|
@@ -137,7 +137,7 @@ async function doFetchToken(appId: string, clientSecret: string): Promise<string
|
|
|
137
137
|
const requestHeaders = { "Content-Type": "application/json", "User-Agent": PLUGIN_USER_AGENT };
|
|
138
138
|
|
|
139
139
|
// 打印请求信息(隐藏敏感信息)
|
|
140
|
-
console.log(`[qqbot-api:${appId}] >>> POST ${TOKEN_URL}`);
|
|
140
|
+
console.log(`[qqbot-api:${appId}] >>> POST ${TOKEN_URL} [secret: ${clientSecret.slice(0, 6)}...len=${clientSecret.length}]`);
|
|
141
141
|
|
|
142
142
|
let response: Response;
|
|
143
143
|
try {
|
package/src/slash-commands.ts
CHANGED
|
@@ -162,7 +162,7 @@ function checkUpgradeCompatibility(): UpgradeCompatResult {
|
|
|
162
162
|
// 3. 检查 Node.js 版本
|
|
163
163
|
const nodeVer = process.version.replace(/^v/, "");
|
|
164
164
|
if (compareSemver(nodeVer, req.minNodeVersion) < 0) {
|
|
165
|
-
errors.push(`❌
|
|
165
|
+
errors.push(`❌ NoVBNde.js 版本过低:当前 **v${nodeVer}**,热更新要求最低 **v${req.minNodeVersion}**`);
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
// 4. 检查系统架构(arm 等特殊架构提示)
|
|
@@ -746,14 +746,22 @@ function cleanupTempScript(): void {
|
|
|
746
746
|
*
|
|
747
747
|
* 安全机制:脚本会被复制到临时目录再执行,避免升级过程中插件目录被操作导致脚本自身丢失。
|
|
748
748
|
*/
|
|
749
|
-
function fireHotUpgrade(targetVersion?: string): HotUpgradeStartResult {
|
|
750
|
-
//
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
749
|
+
function fireHotUpgrade(targetVersion?: string, pkg?: string, useLocal?: boolean): HotUpgradeStartResult {
|
|
750
|
+
// --local: 直接使用本地脚本,跳过远端下载
|
|
751
|
+
// 默认: 优先从远端下载升级脚本,避免使用本地可能过时的版本
|
|
752
|
+
const scriptPath = useLocal
|
|
753
|
+
? (() => {
|
|
754
|
+
const local = getUpgradeScriptPath();
|
|
755
|
+
if (!local) return null;
|
|
756
|
+
console.log(`[qqbot] fireHotUpgrade: --local specified, using local script: ${local}`);
|
|
757
|
+
return copyScriptToTemp(local) || local;
|
|
758
|
+
})()
|
|
759
|
+
: downloadRemoteUpgradeScript() || (() => {
|
|
760
|
+
const local = getUpgradeScriptPath();
|
|
761
|
+
if (!local) return null;
|
|
762
|
+
console.log(`[qqbot] fireHotUpgrade: remote download failed, falling back to local script: ${local}`);
|
|
763
|
+
return copyScriptToTemp(local) || local;
|
|
764
|
+
})();
|
|
757
765
|
if (!scriptPath) return { ok: false, reason: "no-script" };
|
|
758
766
|
|
|
759
767
|
const cli = findCli();
|
|
@@ -772,16 +780,17 @@ function fireHotUpgrade(targetVersion?: string): HotUpgradeStartResult {
|
|
|
772
780
|
"-File", scriptPath,
|
|
773
781
|
"-NoRestart",
|
|
774
782
|
...(targetVersion ? ["-Version", targetVersion] : []),
|
|
783
|
+
...(pkg ? ["-Pkg", pkg] : []),
|
|
775
784
|
];
|
|
776
785
|
} else {
|
|
777
786
|
// Mac / Linux: bash 执行 .sh
|
|
778
787
|
const bash = findBash();
|
|
779
788
|
if (!bash) return { ok: false, reason: "no-bash" };
|
|
780
789
|
shell = bash;
|
|
781
|
-
shellArgs = [scriptPath, "--no-restart", ...(targetVersion ? ["--version", targetVersion] : [])];
|
|
790
|
+
shellArgs = [scriptPath, "--no-restart", ...(targetVersion ? ["--version", targetVersion] : []), ...(pkg ? ["--pkg", pkg] : [])];
|
|
782
791
|
}
|
|
783
792
|
|
|
784
|
-
console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}`);
|
|
793
|
+
console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}, pkg=${pkg || "default"}`);
|
|
785
794
|
|
|
786
795
|
// 异步执行升级脚本
|
|
787
796
|
execFile(shell, shellArgs, {
|
|
@@ -894,7 +903,9 @@ registerCommand({
|
|
|
894
903
|
`/bot-upgrade 检查是否有新版本`,
|
|
895
904
|
`/bot-upgrade --latest 确认升级到最新版本(需 upgradeMode=hot-reload)`,
|
|
896
905
|
`/bot-upgrade --version X 升级到指定版本(需 upgradeMode=hot-reload)`,
|
|
906
|
+
`/bot-upgrade --pkg scope/name 指定 npm 包(如 ryantest/openclaw-qqbot)`,
|
|
897
907
|
`/bot-upgrade --force 强制重新安装当前版本(需 upgradeMode=hot-reload)`,
|
|
908
|
+
`/bot-upgrade --local 使用本地升级脚本(跳过远端下载)`,
|
|
898
909
|
].join("\n"),
|
|
899
910
|
handler: async (ctx) => {
|
|
900
911
|
const url = ctx.accountConfig?.upgradeUrl || DEFAULT_UPGRADE_URL;
|
|
@@ -949,7 +960,9 @@ registerCommand({
|
|
|
949
960
|
|
|
950
961
|
let isForce = false;
|
|
951
962
|
let isLatest = false;
|
|
963
|
+
let isLocal = false;
|
|
952
964
|
let versionArg: string | undefined;
|
|
965
|
+
let pkgArg: string | undefined;
|
|
953
966
|
const tokens = args ? args.split(/\s+/).filter(Boolean) : [];
|
|
954
967
|
for (let i = 0; i < tokens.length; i += 1) {
|
|
955
968
|
const t = tokens[i]!;
|
|
@@ -961,6 +974,27 @@ registerCommand({
|
|
|
961
974
|
isLatest = true;
|
|
962
975
|
continue;
|
|
963
976
|
}
|
|
977
|
+
if (t === "--local") {
|
|
978
|
+
isLocal = true;
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
if (t === "--pkg") {
|
|
982
|
+
const next = tokens[i + 1];
|
|
983
|
+
if (!next || next.startsWith("--")) {
|
|
984
|
+
return `❌ 参数错误:--pkg 需要包名\n\n示例:/bot-upgrade --pkg ryantest/openclaw-qqbot`;
|
|
985
|
+
}
|
|
986
|
+
pkgArg = next;
|
|
987
|
+
i += 1;
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
if (t.startsWith("--pkg=")) {
|
|
991
|
+
const v = t.slice("--pkg=".length).trim();
|
|
992
|
+
if (!v) {
|
|
993
|
+
return `❌ 参数错误:--pkg 需要包名\n\n示例:/bot-upgrade --pkg ryantest/openclaw-qqbot`;
|
|
994
|
+
}
|
|
995
|
+
pkgArg = v;
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
964
998
|
if (t === "--version") {
|
|
965
999
|
const next = tokens[i + 1];
|
|
966
1000
|
if (!next || next.startsWith("--")) {
|
|
@@ -1022,9 +1056,17 @@ registerCommand({
|
|
|
1022
1056
|
].join("\n");
|
|
1023
1057
|
}
|
|
1024
1058
|
|
|
1059
|
+
// 解析 npm 包名:--pkg 参数 > 配置项 upgradePkg > 默认
|
|
1060
|
+
// 支持 "scope/name"(自动补 @)和 "@scope/name" 两种格式
|
|
1061
|
+
let upgradePkg = pkgArg || ctx.accountConfig?.upgradePkg;
|
|
1062
|
+
if (upgradePkg) {
|
|
1063
|
+
upgradePkg = upgradePkg.trim();
|
|
1064
|
+
if (!upgradePkg.startsWith("@")) upgradePkg = `@${upgradePkg}`;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1025
1067
|
// ── --version 指定版本:先校验版本号是否存在 ──
|
|
1026
1068
|
if (versionArg) {
|
|
1027
|
-
const exists = await checkVersionExists(versionArg);
|
|
1069
|
+
const exists = await checkVersionExists(versionArg, upgradePkg);
|
|
1028
1070
|
if (!exists) {
|
|
1029
1071
|
return `❌ 版本 ${versionArg} 不存在,请检查版本号`;
|
|
1030
1072
|
}
|
|
@@ -1077,7 +1119,7 @@ registerCommand({
|
|
|
1077
1119
|
preUpgradeCredentialBackup(ctx.accountId, ctx.appId);
|
|
1078
1120
|
|
|
1079
1121
|
// 异步执行升级
|
|
1080
|
-
const startResult = fireHotUpgrade(targetVersion);
|
|
1122
|
+
const startResult = fireHotUpgrade(targetVersion, upgradePkg, isLocal);
|
|
1081
1123
|
if (!startResult.ok) {
|
|
1082
1124
|
_upgrading = false;
|
|
1083
1125
|
if (startResult.reason === "no-script") {
|
package/src/types.ts
CHANGED
|
@@ -101,6 +101,13 @@ export interface QQBotAccountConfig {
|
|
|
101
101
|
* - "hot-reload":检测到新版本时直接执行 npm 升级脚本进行热更新(默认)
|
|
102
102
|
*/
|
|
103
103
|
upgradeMode?: "doc" | "hot-reload";
|
|
104
|
+
/**
|
|
105
|
+
* /bot-upgrade 热更新时使用的 npm 包名
|
|
106
|
+
* 支持 "scope/name"(自动补 @)或 "@scope/name" 格式
|
|
107
|
+
* 默认: "@tencent-connect/openclaw-qqbot"
|
|
108
|
+
* 示例: "ryantest/openclaw-qqbot"
|
|
109
|
+
*/
|
|
110
|
+
upgradePkg?: string;
|
|
104
111
|
/**
|
|
105
112
|
* 出站消息合并回复(debounce)配置
|
|
106
113
|
* 当短时间内收到多次 deliver 时,将文本合并为一条消息发送,避免消息轰炸
|
package/src/update-checker.ts
CHANGED
|
@@ -122,9 +122,12 @@ export async function getUpdateInfo(): Promise<UpdateInfo> {
|
|
|
122
122
|
/**
|
|
123
123
|
* 检查指定版本是否存在于 npm registry
|
|
124
124
|
* 用于 /bot-upgrade --version 的前置校验
|
|
125
|
+
* @param version 要检查的版本号
|
|
126
|
+
* @param pkgName 可选的包名(如 "@ryantest/openclaw-qqbot"),默认使用内置包名
|
|
125
127
|
*/
|
|
126
|
-
export async function checkVersionExists(version: string): Promise<boolean> {
|
|
127
|
-
|
|
128
|
+
export async function checkVersionExists(version: string, pkgName?: string): Promise<boolean> {
|
|
129
|
+
const registries = pkgName ? buildRegistries(pkgName) : REGISTRIES;
|
|
130
|
+
for (const baseUrl of registries) {
|
|
128
131
|
try {
|
|
129
132
|
const url = `${baseUrl}/${version}`;
|
|
130
133
|
const json = await fetchJson(url, 10_000);
|
|
@@ -136,6 +139,15 @@ export async function checkVersionExists(version: string): Promise<boolean> {
|
|
|
136
139
|
return false;
|
|
137
140
|
}
|
|
138
141
|
|
|
142
|
+
/** 根据自定义包名构建 registry URL 列表 */
|
|
143
|
+
function buildRegistries(pkgName: string): string[] {
|
|
144
|
+
const encoded = encodeURIComponent(pkgName);
|
|
145
|
+
return [
|
|
146
|
+
`https://registry.npmjs.org/${encoded}`,
|
|
147
|
+
`https://registry.npmmirror.com/${encoded}`,
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
|
|
139
151
|
function compareVersions(a: string, b: string): number {
|
|
140
152
|
const parse = (v: string) => {
|
|
141
153
|
const clean = v.replace(/^v/, "");
|