@jerryliang122/openclaw-qqbot 1.0.0 → 1.0.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/README.md +2 -13
- package/README.zh.md +2 -13
- package/dist/index.cjs +6 -182
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -5
- package/index.ts +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/commands/bot-help.ts +1 -1
- package/src/commands/bot-version.ts +1 -12
- package/src/commands/index.ts +0 -2
- package/src/gateway/lifecycle.ts +2 -6
- package/src/types.ts +0 -5
- package/skills/qqbot-upgrade/SKILL.md +0 -56
- package/src/commands/bot-upgrade.ts +0 -56
- package/src/features/update-checker.ts +0 -166
package/dist/index.d.cts
CHANGED
|
@@ -155,11 +155,6 @@ interface QQBotAccountConfig {
|
|
|
155
155
|
* 禁用时:公网 URL 始终由插件先下载到本地,再以 Base64 上传(适用于 QQ 平台无法访问目标 URL 的场景)
|
|
156
156
|
*/
|
|
157
157
|
urlDirectUpload?: boolean;
|
|
158
|
-
/**
|
|
159
|
-
* /bot-upgrade 指令返回的升级指引网址
|
|
160
|
-
* 默认: 本仓库 CHANGELOG(含各版本升级说明)
|
|
161
|
-
*/
|
|
162
|
-
upgradeUrl?: string;
|
|
163
158
|
/**
|
|
164
159
|
* 群消息是否默认需要 @机器人才响应(默认 true)
|
|
165
160
|
* 优先级低于 groups.{groupId}.requireMention 和 groups."*".requireMention
|
package/index.ts
CHANGED
package/openclaw.plugin.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"description": "QQ Bot channel plugin with message support, cron jobs, and proactive messaging",
|
|
5
5
|
"channels": ["qqbot"],
|
|
6
6
|
"extensions": ["./preload.cjs"],
|
|
7
|
-
"skills": ["skills/qqbot-channel", "skills/qqbot-remind"
|
|
7
|
+
"skills": ["skills/qqbot-channel", "skills/qqbot-remind"],
|
|
8
8
|
"contracts": {
|
|
9
9
|
"tools": ["qqbot_platform_api", "qqbot_remind", "qqbot_secret_input"]
|
|
10
10
|
},
|
package/package.json
CHANGED
package/src/commands/bot-help.ts
CHANGED
|
@@ -6,7 +6,7 @@ const PLUGIN_VERSION = getPackageVersion();
|
|
|
6
6
|
|
|
7
7
|
/** 群聊时隐藏的仅限私聊指令 */
|
|
8
8
|
const GROUP_EXCLUDED = new Set([
|
|
9
|
-
'bot-
|
|
9
|
+
'bot-clear-storage', 'bot-logs',
|
|
10
10
|
'bot-approve', 'bot-group-always', 'bot-group-allways', 'bot-streaming', 'bot-me',
|
|
11
11
|
]);
|
|
12
12
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { SlashCommand } from '@tencent-connect/qqbot-nodejs';
|
|
2
2
|
import type { ResolvedQQBotAccount } from '../types.js';
|
|
3
3
|
import { getPackageVersion } from '../utils/pkg-version.js';
|
|
4
|
-
import { getUpdateInfo } from '../features/update-checker.js';
|
|
5
4
|
import { getOpenClawVersion } from '../bot-instance.js';
|
|
6
5
|
|
|
7
6
|
const PLUGIN_VERSION = getPackageVersion();
|
|
@@ -16,25 +15,15 @@ export function botVersion(_account: ResolvedQQBotAccount): SlashCommand {
|
|
|
16
15
|
'/bot-version',
|
|
17
16
|
'',
|
|
18
17
|
'查看当前 QQBot 插件版本和 OpenClaw 框架版本。',
|
|
19
|
-
'同时检查是否有新版本可用。',
|
|
20
18
|
].join('\n'),
|
|
21
19
|
handler: async () => {
|
|
22
20
|
const frameworkVersion = getOpenClawVersion();
|
|
23
21
|
const lines = [
|
|
24
22
|
`🦞框架版本:${frameworkVersion}`,
|
|
25
23
|
`🤖QQBot 插件版本:v${PLUGIN_VERSION}`,
|
|
24
|
+
`🌟官方 GitHub 仓库:[点击前往](${GITHUB_URL})`,
|
|
26
25
|
];
|
|
27
26
|
|
|
28
|
-
const info = await getUpdateInfo();
|
|
29
|
-
if (info.checkedAt === 0) {
|
|
30
|
-
lines.push('⏳ 版本检查中...');
|
|
31
|
-
} else if (info.error) {
|
|
32
|
-
lines.push('⚠️ 版本检查失败');
|
|
33
|
-
} else if (info.hasUpdate && info.latest) {
|
|
34
|
-
lines.push(`🆕最新可用版本:v${info.latest},点击 <qqbot-cmd-input text="/bot-upgrade" show="/bot-upgrade"/> 查看升级指引`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
lines.push(`🌟官方 GitHub 仓库:[点击前往](${GITHUB_URL})`);
|
|
38
27
|
return lines.join('\n');
|
|
39
28
|
},
|
|
40
29
|
};
|
package/src/commands/index.ts
CHANGED
|
@@ -11,7 +11,6 @@ import { botHelp } from './bot-help.js';
|
|
|
11
11
|
import { botPing } from './bot-ping.js';
|
|
12
12
|
import { botVersion } from './bot-version.js';
|
|
13
13
|
import { botMe } from './bot-me.js';
|
|
14
|
-
import { botUpgrade } from './bot-upgrade.js';
|
|
15
14
|
import { botStreaming } from './bot-streaming.js';
|
|
16
15
|
import { botClearStorage } from './bot-clear-storage.js';
|
|
17
16
|
import { botLogs } from './bot-logs.js';
|
|
@@ -37,7 +36,6 @@ export function buildCommandList(account: ResolvedQQBotAccount, opts: CommandBui
|
|
|
37
36
|
botPing(),
|
|
38
37
|
botVersion(account),
|
|
39
38
|
botMe(),
|
|
40
|
-
botUpgrade(account),
|
|
41
39
|
botLogs(opts.getRuntime()),
|
|
42
40
|
botStreaming(account, opts.getRuntime),
|
|
43
41
|
botClearStorage(account),
|
package/src/gateway/lifecycle.ts
CHANGED
|
@@ -22,7 +22,6 @@ import { createPluginLogger } from '../utils/plugin-logger.js';
|
|
|
22
22
|
import type { PluginLogger } from '../utils/plugin-logger.js';
|
|
23
23
|
import { registerGateway, unregisterGateway, getGateway } from '../outbound/outbound-service.js';
|
|
24
24
|
import { saveCredentialBackup, loadCredentialBackup } from '../features/credential-backup.js';
|
|
25
|
-
import { triggerUpdateCheck } from '../features/update-checker.js';
|
|
26
25
|
import { syncCommandPanels } from '../features/command-panel.js';
|
|
27
26
|
import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from '../features/approval-capability.js';
|
|
28
27
|
import { cleanupTypingByAccount } from '../typing-lifecycle.js';
|
|
@@ -111,16 +110,13 @@ async function initFeatures(
|
|
|
111
110
|
ctx: StartAccountContext,
|
|
112
111
|
log: PluginLogger,
|
|
113
112
|
): Promise<void> {
|
|
114
|
-
// 1.
|
|
115
|
-
triggerUpdateCheck(log);
|
|
116
|
-
|
|
117
|
-
// 2. 注册 "approval.native" 运行时上下文。框架的 approval bootstrap 监听此上下文,
|
|
113
|
+
// 1. 注册 "approval.native" 运行时上下文。框架的 approval bootstrap 监听此上下文,
|
|
118
114
|
// 据此为该账户自动 spawn native 审批 handler(消费 exec/plugin approval 事件,
|
|
119
115
|
// 回调 qqbotPlugin.approvalCapability.nativeRuntime 的 availability/presentation/transport)。
|
|
120
116
|
// 账户停止时 abortSignal 触发,上下文自动注销。
|
|
121
117
|
registerApprovalNativeContext(account.accountId, ctx);
|
|
122
118
|
|
|
123
|
-
//
|
|
119
|
+
// 2. 指令面板同步:把 openclaw essential 原生指令注册到 QQ Bot 指令面板(/v2/panels)。
|
|
124
120
|
// 每进程每账号仅一次;失败只记日志并由 once-guard 释放等待重连重试,不影响消息收发。
|
|
125
121
|
syncCommandPanels(account, ctx.cfg, log).catch((e) => {
|
|
126
122
|
log?.error(`[qqbot:${account.accountId}] command panel sync error: ${e}`);
|
package/src/types.ts
CHANGED
|
@@ -163,11 +163,6 @@ export interface QQBotAccountConfig {
|
|
|
163
163
|
* 禁用时:公网 URL 始终由插件先下载到本地,再以 Base64 上传(适用于 QQ 平台无法访问目标 URL 的场景)
|
|
164
164
|
*/
|
|
165
165
|
urlDirectUpload?: boolean;
|
|
166
|
-
/**
|
|
167
|
-
* /bot-upgrade 指令返回的升级指引网址
|
|
168
|
-
* 默认: 本仓库 CHANGELOG(含各版本升级说明)
|
|
169
|
-
*/
|
|
170
|
-
upgradeUrl?: string;
|
|
171
166
|
/**
|
|
172
167
|
* 群消息是否默认需要 @机器人才响应(默认 true)
|
|
173
168
|
* 优先级低于 groups.{groupId}.requireMention 和 groups."*".requireMention
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: qqbot-upgrade
|
|
3
|
-
description: 将 openclaw-qqbot 插件(@jerryliang122/openclaw-qqbot)升级到最新版本。当用户要求更新 QQ 机器人插件、升级 qqbot 扩展或同步最新版时使用。
|
|
4
|
-
metadata: {"openclaw":{"emoji":"⬆️","requires":{"config":["channels.qqbot"]}}}
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# QQBot 插件升级
|
|
8
|
-
|
|
9
|
-
## 何时使用
|
|
10
|
-
|
|
11
|
-
用户表达以下意图时,应在**本机终端**执行升级命令(不要只口头说明「去官网升级」):
|
|
12
|
-
|
|
13
|
-
- 更新 / 升级 `openclaw-qqbot` / QQBot 插件 / QQ 机器人插件
|
|
14
|
-
- 同步本仓库(jerryliang122/qqbot-openclaw)最新版
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## 标准命令
|
|
19
|
-
|
|
20
|
-
npm 包(推荐):
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
openclaw plugins install @jerryliang122/openclaw-qqbot
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
指定版本:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
openclaw plugins install @jerryliang122/openclaw-qqbot@<version>
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
或从 GitHub(无法访问 npm 时):
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
openclaw plugins install git+https://github.com/jerryliang122/qqbot-openclaw.git
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
升级完成后重启网关使其生效:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
openclaw gateway restart
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## 前置条件
|
|
47
|
-
|
|
48
|
-
- OpenClaw >= 2026.9.2(低版本框架无法加载本插件)
|
|
49
|
-
- 需 **Node.js / npm** 环境可访问 npm registry(或 GitHub)
|
|
50
|
-
- 从 2.x 旧版本升级:先阅读仓库 CHANGELOG 的 Breaking Changes 与配置迁移表
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## 执行后
|
|
55
|
-
|
|
56
|
-
根据命令退出码与终端输出向用户简要汇报:成功则提示新版本号并确认网关已重启;失败则摘录关键错误并提示检查网络、npm 权限与 OpenClaw 版本。
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { SlashCommand } from '@tencent-connect/qqbot-nodejs';
|
|
2
|
-
import type { ResolvedQQBotAccount } from '../types.js';
|
|
3
|
-
import { getPackageVersion } from '../utils/pkg-version.js';
|
|
4
|
-
import { getUpdateInfo } from '../features/update-checker.js';
|
|
5
|
-
import { checkCommandAuth } from './config-util.js';
|
|
6
|
-
|
|
7
|
-
const PLUGIN_VERSION = getPackageVersion();
|
|
8
|
-
const DEFAULT_UPGRADE_URL = 'https://github.com/jerryliang122/qqbot-openclaw/blob/main/CHANGELOG.md';
|
|
9
|
-
const GITHUB_URL = 'https://github.com/jerryliang122/qqbot-openclaw';
|
|
10
|
-
|
|
11
|
-
/** /bot-upgrade — 检查更新并查看升级指引 */
|
|
12
|
-
export function botUpgrade(account: ResolvedQQBotAccount): SlashCommand {
|
|
13
|
-
return {
|
|
14
|
-
name: 'bot-upgrade',
|
|
15
|
-
description: '检查更新并查看升级指引',
|
|
16
|
-
scope: 'c2c',
|
|
17
|
-
authorized: checkCommandAuth,
|
|
18
|
-
usage: [
|
|
19
|
-
'/bot-upgrade 检查是否有新版本',
|
|
20
|
-
].join('\n'),
|
|
21
|
-
handler: async () => {
|
|
22
|
-
const url = account.config.upgradeUrl ?? DEFAULT_UPGRADE_URL;
|
|
23
|
-
const info = await getUpdateInfo();
|
|
24
|
-
|
|
25
|
-
if (info.checkedAt === 0) {
|
|
26
|
-
return '⏳ 版本检查中,请稍后再试';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (info.error) {
|
|
30
|
-
return [
|
|
31
|
-
'❌ 主机网络访问异常,无法检查更新',
|
|
32
|
-
'',
|
|
33
|
-
`查看升级指引:[点击查看](${url})`,
|
|
34
|
-
].join('\n');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!info.hasUpdate) {
|
|
38
|
-
return [
|
|
39
|
-
`✅ 当前已是最新版本 v${PLUGIN_VERSION}`,
|
|
40
|
-
'',
|
|
41
|
-
`项目地址:[GitHub](${GITHUB_URL})`,
|
|
42
|
-
].join('\n');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return [
|
|
46
|
-
'🆕 发现新版本',
|
|
47
|
-
'',
|
|
48
|
-
`当前版本:**v${PLUGIN_VERSION}**`,
|
|
49
|
-
`最新版本:**v${info.latest}**`,
|
|
50
|
-
'',
|
|
51
|
-
`📖 升级指引:[点击查看](${url})`,
|
|
52
|
-
`🌟 GitHub 仓库:[点击前往](${GITHUB_URL})`,
|
|
53
|
-
].join('\n');
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 版本检查器
|
|
3
|
-
*
|
|
4
|
-
* - triggerUpdateCheck(): gateway 启动时调用,后台预热缓存
|
|
5
|
-
* - getUpdateInfo(): 每次实时查询 npm registry,返回最新结果
|
|
6
|
-
*
|
|
7
|
-
* 使用 HTTPS 直接请求 npm registry API(不依赖 npm CLI),
|
|
8
|
-
* 支持多 registry fallback:npmjs.org → npmmirror.com,解决国内网络问题。
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import https from "node:https";
|
|
12
|
-
import { getPackageVersion } from "../utils/pkg-version.js";
|
|
13
|
-
import type { PluginLogger } from '../utils/plugin-logger.js';
|
|
14
|
-
|
|
15
|
-
const PKG_NAME = "@jerryliang122/openclaw-qqbot";
|
|
16
|
-
const ENCODED_PKG = encodeURIComponent(PKG_NAME);
|
|
17
|
-
|
|
18
|
-
const REGISTRIES = [
|
|
19
|
-
`https://registry.npmjs.org/${ENCODED_PKG}`,
|
|
20
|
-
`https://registry.npmmirror.com/${ENCODED_PKG}`,
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
let CURRENT_VERSION = getPackageVersion();
|
|
24
|
-
|
|
25
|
-
export interface UpdateInfo {
|
|
26
|
-
current: string;
|
|
27
|
-
latest: string | null;
|
|
28
|
-
stable: string | null;
|
|
29
|
-
alpha: string | null;
|
|
30
|
-
hasUpdate: boolean;
|
|
31
|
-
checkedAt: number;
|
|
32
|
-
error?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function fetchJson(url: string, timeoutMs: number): Promise<any> {
|
|
36
|
-
return new Promise((resolve, reject) => {
|
|
37
|
-
const req = https.get(url, { timeout: timeoutMs, headers: { Accept: "application/json" } }, (res) => {
|
|
38
|
-
if (res.statusCode !== 200) {
|
|
39
|
-
res.resume();
|
|
40
|
-
reject(new Error(`HTTP ${res.statusCode} from ${url}`));
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
let data = "";
|
|
44
|
-
res.on("data", (chunk: string) => { data += chunk; });
|
|
45
|
-
res.on("end", () => {
|
|
46
|
-
try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
req.on("error", reject);
|
|
50
|
-
req.on("timeout", () => { req.destroy(); reject(new Error(`timeout fetching ${url}`)); });
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async function fetchDistTags(log?: PluginLogger): Promise<Record<string, string>> {
|
|
55
|
-
for (const url of REGISTRIES) {
|
|
56
|
-
try {
|
|
57
|
-
const json = await fetchJson(url, 10_000);
|
|
58
|
-
const tags = json["dist-tags"];
|
|
59
|
-
if (tags && typeof tags === "object") return tags;
|
|
60
|
-
} catch (e: any) {
|
|
61
|
-
log?.debug(`[update-checker] ${url} failed: ${e.message}`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
throw new Error("all registries failed");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function buildUpdateInfo(tags: Record<string, string>): UpdateInfo {
|
|
68
|
-
const currentIsPrerelease = CURRENT_VERSION.includes("-");
|
|
69
|
-
const stableTag = tags.latest || null;
|
|
70
|
-
const alphaTag = tags.alpha || null;
|
|
71
|
-
|
|
72
|
-
const compareTarget = currentIsPrerelease ? alphaTag : stableTag;
|
|
73
|
-
|
|
74
|
-
const hasUpdate = typeof compareTarget === "string"
|
|
75
|
-
&& compareTarget !== CURRENT_VERSION
|
|
76
|
-
&& compareVersions(compareTarget, CURRENT_VERSION) > 0;
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
current: CURRENT_VERSION,
|
|
80
|
-
latest: compareTarget,
|
|
81
|
-
stable: stableTag,
|
|
82
|
-
alpha: alphaTag,
|
|
83
|
-
hasUpdate,
|
|
84
|
-
checkedAt: Date.now(),
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Gateway 启动时调用,后台预热版本缓存。
|
|
90
|
-
* log 通过闭包传递,避免模块级可变状态。
|
|
91
|
-
*/
|
|
92
|
-
export function triggerUpdateCheck(log: PluginLogger): void {
|
|
93
|
-
getUpdateInfo(log).then((info) => {
|
|
94
|
-
if (info.hasUpdate) {
|
|
95
|
-
log.info(`[update-checker] new version available: ${info.latest} (current: ${CURRENT_VERSION})`);
|
|
96
|
-
}
|
|
97
|
-
}).catch(() => {});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** 每次实时查询 npm registry */
|
|
101
|
-
export async function getUpdateInfo(log?: PluginLogger): Promise<UpdateInfo> {
|
|
102
|
-
try {
|
|
103
|
-
const tags = await fetchDistTags(log);
|
|
104
|
-
return buildUpdateInfo(tags);
|
|
105
|
-
} catch (err: any) {
|
|
106
|
-
log?.debug(`[update-checker] check failed: ${err.message}`);
|
|
107
|
-
return { current: CURRENT_VERSION, latest: null, stable: null, alpha: null, hasUpdate: false, checkedAt: Date.now(), error: err.message };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* 检查指定版本是否存在于 npm registry
|
|
113
|
-
*/
|
|
114
|
-
export async function checkVersionExists(version: string, pkgName?: string): Promise<boolean> {
|
|
115
|
-
const registries = pkgName ? buildRegistries(pkgName) : REGISTRIES;
|
|
116
|
-
for (const baseUrl of registries) {
|
|
117
|
-
try {
|
|
118
|
-
const url = `${baseUrl}/${version}`;
|
|
119
|
-
const json = await fetchJson(url, 10_000);
|
|
120
|
-
if (json && json.version === version) return true;
|
|
121
|
-
} catch {
|
|
122
|
-
// try next registry
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function buildRegistries(pkgName: string): string[] {
|
|
129
|
-
const encoded = encodeURIComponent(pkgName);
|
|
130
|
-
return [
|
|
131
|
-
`https://registry.npmjs.org/${encoded}`,
|
|
132
|
-
`https://registry.npmmirror.com/${encoded}`,
|
|
133
|
-
];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function compareVersions(a: string, b: string): number {
|
|
137
|
-
const parse = (v: string) => {
|
|
138
|
-
const clean = v.replace(/^v/, "");
|
|
139
|
-
const [main, pre] = clean.split("-", 2);
|
|
140
|
-
return { parts: main.split(".").map(Number), pre: pre || null };
|
|
141
|
-
};
|
|
142
|
-
const pa = parse(a);
|
|
143
|
-
const pb = parse(b);
|
|
144
|
-
for (let i = 0; i < 3; i++) {
|
|
145
|
-
const diff = (pa.parts[i] || 0) - (pb.parts[i] || 0);
|
|
146
|
-
if (diff !== 0) return diff;
|
|
147
|
-
}
|
|
148
|
-
if (!pa.pre && pb.pre) return 1;
|
|
149
|
-
if (pa.pre && !pb.pre) return -1;
|
|
150
|
-
if (!pa.pre && !pb.pre) return 0;
|
|
151
|
-
const aParts = pa.pre!.split(".");
|
|
152
|
-
const bParts = pb.pre!.split(".");
|
|
153
|
-
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
154
|
-
const aP = aParts[i] ?? "";
|
|
155
|
-
const bP = bParts[i] ?? "";
|
|
156
|
-
const aNum = Number(aP);
|
|
157
|
-
const bNum = Number(bP);
|
|
158
|
-
if (!isNaN(aNum) && !isNaN(bNum)) {
|
|
159
|
-
if (aNum !== bNum) return aNum - bNum;
|
|
160
|
-
} else {
|
|
161
|
-
if (aP < bP) return -1;
|
|
162
|
-
if (aP > bP) return 1;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return 0;
|
|
166
|
-
}
|