@ryantest/openclaw-qqbot 1.6.6-alpha.1 → 1.6.6-alpha.3
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/channel.d.ts +1 -1
- package/dist/src/channel.js +12 -10
- package/dist/src/slash-commands.js +42 -5
- package/dist/src/utils/chunked-upload.d.ts +1 -1
- package/dist/src/utils/chunked-upload.js +1 -1
- package/dist/src/utils/file-utils.d.ts +1 -1
- package/dist/src/utils/file-utils.js +5 -5
- package/index.ts +1 -1
- package/node_modules/ws/index.js +6 -15
- package/node_modules/ws/lib/permessage-deflate.js +6 -6
- package/node_modules/ws/lib/websocket-server.js +5 -5
- package/node_modules/ws/lib/websocket.js +6 -6
- package/node_modules/ws/package.json +3 -4
- package/node_modules/ws/wrapper.mjs +1 -14
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -2
- package/preload.cjs +33 -0
- package/scripts/link-sdk-core.cjs +185 -0
- package/scripts/upgrade-via-npm.sh +79 -59
- package/scripts/upgrade-via-source.sh +201 -223
- package/skills/qqbot-media/SKILL.md +2 -2
- package/src/channel.ts +19 -18
- package/src/slash-commands.ts +46 -6
- package/src/utils/chunked-upload.ts +2 -2
- package/src/utils/file-utils.ts +5 -5
package/dist/src/channel.d.ts
CHANGED
package/dist/src/channel.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyAccountNameToChannelSection, deleteAccountFromConfigSection, setAccountEnabledInConfigSection, } from "openclaw/plugin-sdk";
|
|
1
|
+
import { applyAccountNameToChannelSection, deleteAccountFromConfigSection, setAccountEnabledInConfigSection, } from "openclaw/plugin-sdk/core";
|
|
2
2
|
import { DEFAULT_ACCOUNT_ID, listQQBotAccountIds, resolveQQBotAccount, applyQQBotAccountConfig, resolveDefaultQQBotAccountId } from "./config.js";
|
|
3
3
|
import { sendText, sendMedia } from "./outbound.js";
|
|
4
4
|
import { startGateway } from "./gateway.js";
|
|
@@ -40,6 +40,7 @@ export const qqbotPlugin = {
|
|
|
40
40
|
},
|
|
41
41
|
reload: { configPrefixes: ["channels.qqbot"] },
|
|
42
42
|
// CLI onboarding wizard
|
|
43
|
+
// @ts-expect-error onboarding removed from ChannelPlugin type in 2026.3.23 but still supported at runtime
|
|
43
44
|
onboarding: qqbotOnboardingAdapter,
|
|
44
45
|
config: {
|
|
45
46
|
listAccountIds: (cfg) => listQQBotAccountIds(cfg),
|
|
@@ -76,7 +77,7 @@ export const qqbotPlugin = {
|
|
|
76
77
|
}),
|
|
77
78
|
// 关键:解析 allowFrom 配置,用于命令授权
|
|
78
79
|
resolveAllowFrom: ({ cfg, accountId }) => {
|
|
79
|
-
const account = resolveQQBotAccount(cfg, accountId);
|
|
80
|
+
const account = resolveQQBotAccount(cfg, accountId ?? undefined);
|
|
80
81
|
const allowFrom = account.config?.allowFrom ?? [];
|
|
81
82
|
console.log(`[qqbot] resolveAllowFrom: accountId=${accountId}, allowFrom=${JSON.stringify(allowFrom)}`);
|
|
82
83
|
return allowFrom.map((entry) => String(entry));
|
|
@@ -201,20 +202,21 @@ export const qqbotPlugin = {
|
|
|
201
202
|
sendText: async ({ to, text, accountId, replyToId, cfg }) => {
|
|
202
203
|
console.log(`[qqbot:channel] sendText called — accountId=${accountId}, to=${to}, replyToId=${replyToId}, text.length=${text?.length ?? 0}`);
|
|
203
204
|
console.log(`[qqbot:channel] sendText text preview: ${text?.slice(0, 100)}${(text?.length ?? 0) > 100 ? "..." : ""}`);
|
|
204
|
-
const account = resolveQQBotAccount(cfg, accountId);
|
|
205
|
+
const account = resolveQQBotAccount(cfg, accountId ?? undefined);
|
|
205
206
|
initApiConfig({ markdownSupport: account.markdownSupport });
|
|
206
207
|
console.log(`[qqbot:channel] sendText resolved account: id=${account.accountId}, appId=${account.appId}, enabled=${account.enabled}`);
|
|
207
208
|
const result = await sendText({ to, text, accountId, replyToId, account });
|
|
208
209
|
console.log(`[qqbot:channel] sendText result: messageId=${result.messageId}, error=${result.error ?? "none"}`);
|
|
210
|
+
if (result.error)
|
|
211
|
+
throw new Error(result.error);
|
|
209
212
|
return {
|
|
210
213
|
channel: "qqbot",
|
|
211
|
-
messageId: result.messageId,
|
|
212
|
-
error: result.error ? new Error(result.error) : undefined,
|
|
214
|
+
messageId: result.messageId ?? "",
|
|
213
215
|
};
|
|
214
216
|
},
|
|
215
217
|
sendMedia: async ({ to, text, mediaUrl, accountId, replyToId, cfg }) => {
|
|
216
218
|
console.log(`[qqbot:channel] sendMedia called — accountId=${accountId}, to=${to}, replyToId=${replyToId}, mediaUrl=${mediaUrl?.slice(0, 80)}, text.length=${text?.length ?? 0}`);
|
|
217
|
-
const account = resolveQQBotAccount(cfg, accountId);
|
|
219
|
+
const account = resolveQQBotAccount(cfg, accountId ?? undefined);
|
|
218
220
|
initApiConfig({ markdownSupport: account.markdownSupport });
|
|
219
221
|
console.log(`[qqbot:channel] sendMedia resolved account: id=${account.accountId}, appId=${account.appId}, enabled=${account.enabled}`);
|
|
220
222
|
const result = await sendMedia({ to, text: text ?? "", mediaUrl: mediaUrl ?? "", accountId, replyToId, account });
|
|
@@ -231,11 +233,11 @@ export const qqbotPlugin = {
|
|
|
231
233
|
catch (fallbackErr) {
|
|
232
234
|
console.error(`[qqbot:channel] sendMedia fallback text failed: ${fallbackErr}`);
|
|
233
235
|
}
|
|
236
|
+
throw new Error(result.error);
|
|
234
237
|
}
|
|
235
238
|
return {
|
|
236
239
|
channel: "qqbot",
|
|
237
|
-
messageId: result.messageId,
|
|
238
|
-
error: result.error ? new Error(result.error) : undefined,
|
|
240
|
+
messageId: result.messageId ?? "",
|
|
239
241
|
};
|
|
240
242
|
},
|
|
241
243
|
},
|
|
@@ -356,8 +358,8 @@ export const qqbotPlugin = {
|
|
|
356
358
|
enabled: account?.enabled ?? false,
|
|
357
359
|
configured: Boolean(account?.appId && account?.clientSecret),
|
|
358
360
|
tokenSource: account?.secretSource,
|
|
359
|
-
running: runtime?.running ?? false,
|
|
360
|
-
connected: runtime?.connected ?? false,
|
|
361
|
+
running: Boolean(runtime?.running ?? false),
|
|
362
|
+
connected: Boolean(runtime?.connected ?? false),
|
|
361
363
|
lastConnectedAt: runtime?.lastConnectedAt ?? null,
|
|
362
364
|
lastError: runtime?.lastError ?? null,
|
|
363
365
|
lastInboundAt: runtime?.lastInboundAt ?? null,
|
|
@@ -586,6 +586,38 @@ function copyScriptToTemp(scriptPath) {
|
|
|
586
586
|
return null;
|
|
587
587
|
}
|
|
588
588
|
}
|
|
589
|
+
const REMOTE_UPGRADE_SCRIPT_URL = "https://raw.githubusercontent.com/tencent-connect/openclaw-qqbot/main/scripts/upgrade-via-npm.sh";
|
|
590
|
+
const REMOTE_UPGRADE_SCRIPT_URL_WIN = "https://raw.githubusercontent.com/tencent-connect/openclaw-qqbot/main/scripts/upgrade-via-npm.ps1";
|
|
591
|
+
/**
|
|
592
|
+
* 从远端下载升级脚本到临时目录,返回临时脚本路径,失败返回 null。
|
|
593
|
+
*/
|
|
594
|
+
function downloadRemoteUpgradeScript() {
|
|
595
|
+
try {
|
|
596
|
+
const url = isWindows() ? REMOTE_UPGRADE_SCRIPT_URL_WIN : REMOTE_UPGRADE_SCRIPT_URL;
|
|
597
|
+
const ext = isWindows() ? ".ps1" : ".sh";
|
|
598
|
+
const tmpDir = path.join(getHomeDir(), ".openclaw", ".qqbot-upgrade-tmp");
|
|
599
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
600
|
+
const tmpScript = path.join(tmpDir, `upgrade-via-npm${ext}`);
|
|
601
|
+
// 使用 curl 同步下载(macOS/Linux/Windows 均内置 curl)
|
|
602
|
+
execFileSync("curl", ["-fsSL", "--max-time", "15", "-o", tmpScript, url], {
|
|
603
|
+
timeout: 20_000,
|
|
604
|
+
stdio: "pipe",
|
|
605
|
+
});
|
|
606
|
+
if (!fs.existsSync(tmpScript) || fs.statSync(tmpScript).size < 100) {
|
|
607
|
+
console.error(`[qqbot] downloadRemoteUpgradeScript: downloaded file too small or missing`);
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
if (!isWindows()) {
|
|
611
|
+
fs.chmodSync(tmpScript, 0o755);
|
|
612
|
+
}
|
|
613
|
+
console.log(`[qqbot] downloadRemoteUpgradeScript: fetched from ${url} → ${tmpScript}`);
|
|
614
|
+
return tmpScript;
|
|
615
|
+
}
|
|
616
|
+
catch (e) {
|
|
617
|
+
console.error(`[qqbot] downloadRemoteUpgradeScript: failed: ${e.message}`);
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
589
621
|
/**
|
|
590
622
|
* 清理临时升级脚本目录
|
|
591
623
|
*/
|
|
@@ -611,11 +643,16 @@ function cleanupTempScript() {
|
|
|
611
643
|
* 安全机制:脚本会被复制到临时目录再执行,避免升级过程中插件目录被操作导致脚本自身丢失。
|
|
612
644
|
*/
|
|
613
645
|
function fireHotUpgrade(targetVersion) {
|
|
614
|
-
|
|
615
|
-
|
|
646
|
+
// 优先从远端下载升级脚本,避免使用本地可能过时的版本
|
|
647
|
+
const scriptPath = downloadRemoteUpgradeScript() || (() => {
|
|
648
|
+
const local = getUpgradeScriptPath();
|
|
649
|
+
if (!local)
|
|
650
|
+
return null;
|
|
651
|
+
console.log(`[qqbot] fireHotUpgrade: remote download failed, falling back to local script: ${local}`);
|
|
652
|
+
return copyScriptToTemp(local) || local;
|
|
653
|
+
})();
|
|
654
|
+
if (!scriptPath)
|
|
616
655
|
return { ok: false, reason: "no-script" };
|
|
617
|
-
// 将脚本复制到临时位置,避免升级过程中脚本被删除
|
|
618
|
-
const scriptPath = copyScriptToTemp(originalScriptPath) || originalScriptPath;
|
|
619
656
|
const cli = findCli();
|
|
620
657
|
if (!cli)
|
|
621
658
|
return { ok: false, reason: "no-cli" };
|
|
@@ -642,7 +679,7 @@ function fireHotUpgrade(targetVersion) {
|
|
|
642
679
|
shell = bash;
|
|
643
680
|
shellArgs = [scriptPath, "--no-restart", ...(targetVersion ? ["--version", targetVersion] : [])];
|
|
644
681
|
}
|
|
645
|
-
console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}
|
|
682
|
+
console.log(`[qqbot] fireHotUpgrade: shell=${shell}, script=${scriptPath}, cli=${cli}, target=${targetVersion || "latest"}`);
|
|
646
683
|
// 异步执行升级脚本
|
|
647
684
|
execFile(shell, shellArgs, {
|
|
648
685
|
timeout: 120_000,
|
|
@@ -28,7 +28,7 @@ export interface ChunkedUploadProgress {
|
|
|
28
28
|
export interface ChunkedUploadOptions {
|
|
29
29
|
/** 进度回调 */
|
|
30
30
|
onProgress?: (progress: ChunkedUploadProgress) => void;
|
|
31
|
-
/** 最大并发数(默认
|
|
31
|
+
/** 最大并发数(默认 2) */
|
|
32
32
|
maxConcurrent?: number;
|
|
33
33
|
/** 日志前缀 */
|
|
34
34
|
logPrefix?: string;
|
|
@@ -17,7 +17,7 @@ import * as fs from "node:fs";
|
|
|
17
17
|
import { c2cUploadPrepare, c2cUploadPartFinish, c2cCompleteUpload, groupUploadPrepare, groupUploadPartFinish, groupCompleteUpload, getAccessToken, } from "../api.js";
|
|
18
18
|
import { formatFileSize } from "./file-utils.js";
|
|
19
19
|
/** 分片上传并发控制:最多同时上传 N 个分片 */
|
|
20
|
-
const MAX_CONCURRENT_PARTS =
|
|
20
|
+
const MAX_CONCURRENT_PARTS = 1;
|
|
21
21
|
/** 单个分片上传超时(毫秒)— 5 分钟,兼容低带宽场景 */
|
|
22
22
|
const PART_UPLOAD_TIMEOUT = 300_000;
|
|
23
23
|
/** 单个分片上传最大重试次数 */
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
export declare const UPLOAD_SIZE_LIMITS: Record<number, number>;
|
|
6
6
|
/** 获取文件类型的中文名称;未知类型返回 "文件" */
|
|
7
7
|
export declare function getFileTypeName(fileType: number): string;
|
|
8
|
-
/** 获取指定文件类型的上传大小限制;未知类型默认
|
|
8
|
+
/** 获取指定文件类型的上传大小限制;未知类型默认 100MB */
|
|
9
9
|
export declare function getMaxUploadSize(fileType: number): number;
|
|
10
10
|
/** @deprecated 使用 getMaxUploadSize(fileType) 代替 */
|
|
11
11
|
export declare const MAX_UPLOAD_SIZE: number;
|
|
@@ -7,9 +7,9 @@ import crypto from "node:crypto";
|
|
|
7
7
|
/** QQ Bot API 各类型文件上传大小限制(QQ 机器人上行) */
|
|
8
8
|
export const UPLOAD_SIZE_LIMITS = {
|
|
9
9
|
1: 30 * 1024 * 1024, // IMAGE: 30MB
|
|
10
|
-
2:
|
|
10
|
+
2: 100 * 1024 * 1024, // VIDEO: 100MB
|
|
11
11
|
3: 20 * 1024 * 1024, // VOICE: 20MB
|
|
12
|
-
4:
|
|
12
|
+
4: 100 * 1024 * 1024, // FILE: 100MB
|
|
13
13
|
};
|
|
14
14
|
/** 文件类型中文名映射 */
|
|
15
15
|
const FILE_TYPE_NAMES = {
|
|
@@ -22,12 +22,12 @@ const FILE_TYPE_NAMES = {
|
|
|
22
22
|
export function getFileTypeName(fileType) {
|
|
23
23
|
return FILE_TYPE_NAMES[fileType] ?? "文件";
|
|
24
24
|
}
|
|
25
|
-
/** 获取指定文件类型的上传大小限制;未知类型默认
|
|
25
|
+
/** 获取指定文件类型的上传大小限制;未知类型默认 100MB */
|
|
26
26
|
export function getMaxUploadSize(fileType) {
|
|
27
|
-
return UPLOAD_SIZE_LIMITS[fileType] ??
|
|
27
|
+
return UPLOAD_SIZE_LIMITS[fileType] ?? 100 * 1024 * 1024;
|
|
28
28
|
}
|
|
29
29
|
/** @deprecated 使用 getMaxUploadSize(fileType) 代替 */
|
|
30
|
-
export const MAX_UPLOAD_SIZE =
|
|
30
|
+
export const MAX_UPLOAD_SIZE = 100 * 1024 * 1024;
|
|
31
31
|
/** 大文件阈值(超过此值发送进度提示):5MB */
|
|
32
32
|
export const LARGE_FILE_THRESHOLD = 5 * 1024 * 1024;
|
|
33
33
|
/**
|
package/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ const plugin = {
|
|
|
13
13
|
configSchema: emptyPluginConfigSchema(),
|
|
14
14
|
register(api: OpenClawPluginApi) {
|
|
15
15
|
setQQBotRuntime(api.runtime);
|
|
16
|
-
api.registerChannel({ plugin: qqbotPlugin });
|
|
16
|
+
api.registerChannel({ plugin: qqbotPlugin as any });
|
|
17
17
|
registerChannelTool(api);
|
|
18
18
|
registerRemindTool(api);
|
|
19
19
|
},
|
package/node_modules/ws/index.js
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const createWebSocketStream = require('./lib/stream');
|
|
4
|
-
const extension = require('./lib/extension');
|
|
5
|
-
const PerMessageDeflate = require('./lib/permessage-deflate');
|
|
6
|
-
const Receiver = require('./lib/receiver');
|
|
7
|
-
const Sender = require('./lib/sender');
|
|
8
|
-
const subprotocol = require('./lib/subprotocol');
|
|
9
3
|
const WebSocket = require('./lib/websocket');
|
|
10
|
-
const WebSocketServer = require('./lib/websocket-server');
|
|
11
4
|
|
|
12
|
-
WebSocket.createWebSocketStream =
|
|
13
|
-
WebSocket.
|
|
14
|
-
WebSocket.
|
|
15
|
-
WebSocket.
|
|
16
|
-
|
|
17
|
-
WebSocket.Server = WebSocketServer;
|
|
18
|
-
WebSocket.subprotocol = subprotocol;
|
|
5
|
+
WebSocket.createWebSocketStream = require('./lib/stream');
|
|
6
|
+
WebSocket.Server = require('./lib/websocket-server');
|
|
7
|
+
WebSocket.Receiver = require('./lib/receiver');
|
|
8
|
+
WebSocket.Sender = require('./lib/sender');
|
|
9
|
+
|
|
19
10
|
WebSocket.WebSocket = WebSocket;
|
|
20
|
-
WebSocket.WebSocketServer =
|
|
11
|
+
WebSocket.WebSocketServer = WebSocket.Server;
|
|
21
12
|
|
|
22
13
|
module.exports = WebSocket;
|
|
@@ -37,9 +37,6 @@ class PerMessageDeflate {
|
|
|
37
37
|
* acknowledge disabling of client context takeover
|
|
38
38
|
* @param {Number} [options.concurrencyLimit=10] The number of concurrent
|
|
39
39
|
* calls to zlib
|
|
40
|
-
* @param {Boolean} [options.isServer=false] Create the instance in either
|
|
41
|
-
* server or client mode
|
|
42
|
-
* @param {Number} [options.maxPayload=0] The maximum allowed message length
|
|
43
40
|
* @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the
|
|
44
41
|
* use of a custom server window size
|
|
45
42
|
* @param {Boolean} [options.serverNoContextTakeover=false] Request/accept
|
|
@@ -50,13 +47,16 @@ class PerMessageDeflate {
|
|
|
50
47
|
* deflate
|
|
51
48
|
* @param {Object} [options.zlibInflateOptions] Options to pass to zlib on
|
|
52
49
|
* inflate
|
|
50
|
+
* @param {Boolean} [isServer=false] Create the instance in either server or
|
|
51
|
+
* client mode
|
|
52
|
+
* @param {Number} [maxPayload=0] The maximum allowed message length
|
|
53
53
|
*/
|
|
54
|
-
constructor(options) {
|
|
54
|
+
constructor(options, isServer, maxPayload) {
|
|
55
|
+
this._maxPayload = maxPayload | 0;
|
|
55
56
|
this._options = options || {};
|
|
56
57
|
this._threshold =
|
|
57
58
|
this._options.threshold !== undefined ? this._options.threshold : 1024;
|
|
58
|
-
this.
|
|
59
|
-
this._isServer = !!this._options.isServer;
|
|
59
|
+
this._isServer = !!isServer;
|
|
60
60
|
this._deflate = null;
|
|
61
61
|
this._inflate = null;
|
|
62
62
|
|
|
@@ -293,11 +293,11 @@ class WebSocketServer extends EventEmitter {
|
|
|
293
293
|
this.options.perMessageDeflate &&
|
|
294
294
|
secWebSocketExtensions !== undefined
|
|
295
295
|
) {
|
|
296
|
-
const perMessageDeflate = new PerMessageDeflate(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
296
|
+
const perMessageDeflate = new PerMessageDeflate(
|
|
297
|
+
this.options.perMessageDeflate,
|
|
298
|
+
true,
|
|
299
|
+
this.options.maxPayload
|
|
300
|
+
);
|
|
301
301
|
|
|
302
302
|
try {
|
|
303
303
|
const offers = extension.parse(secWebSocketExtensions);
|
|
@@ -693,7 +693,7 @@ function initAsClient(websocket, address, protocols, options) {
|
|
|
693
693
|
} else {
|
|
694
694
|
try {
|
|
695
695
|
parsedUrl = new URL(address);
|
|
696
|
-
} catch {
|
|
696
|
+
} catch (e) {
|
|
697
697
|
throw new SyntaxError(`Invalid URL: ${address}`);
|
|
698
698
|
}
|
|
699
699
|
}
|
|
@@ -755,11 +755,11 @@ function initAsClient(websocket, address, protocols, options) {
|
|
|
755
755
|
opts.timeout = opts.handshakeTimeout;
|
|
756
756
|
|
|
757
757
|
if (opts.perMessageDeflate) {
|
|
758
|
-
perMessageDeflate = new PerMessageDeflate(
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
758
|
+
perMessageDeflate = new PerMessageDeflate(
|
|
759
|
+
opts.perMessageDeflate !== true ? opts.perMessageDeflate : {},
|
|
760
|
+
false,
|
|
761
|
+
opts.maxPayload
|
|
762
|
+
);
|
|
763
763
|
opts.headers['Sec-WebSocket-Extensions'] = format({
|
|
764
764
|
[PerMessageDeflate.extensionName]: perMessageDeflate.offer()
|
|
765
765
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ws",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.19.0",
|
|
4
4
|
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"HyBi",
|
|
@@ -55,13 +55,12 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@eslint/js": "^10.0.1",
|
|
59
58
|
"benchmark": "^2.1.4",
|
|
60
59
|
"bufferutil": "^4.0.1",
|
|
61
|
-
"eslint": "^
|
|
60
|
+
"eslint": "^9.0.0",
|
|
62
61
|
"eslint-config-prettier": "^10.0.1",
|
|
63
62
|
"eslint-plugin-prettier": "^5.0.0",
|
|
64
|
-
"globals": "^
|
|
63
|
+
"globals": "^16.0.0",
|
|
65
64
|
"mocha": "^8.4.0",
|
|
66
65
|
"nyc": "^15.0.0",
|
|
67
66
|
"prettier": "^3.0.0",
|
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
import createWebSocketStream from './lib/stream.js';
|
|
2
|
-
import extension from './lib/extension.js';
|
|
3
|
-
import PerMessageDeflate from './lib/permessage-deflate.js';
|
|
4
2
|
import Receiver from './lib/receiver.js';
|
|
5
3
|
import Sender from './lib/sender.js';
|
|
6
|
-
import subprotocol from './lib/subprotocol.js';
|
|
7
4
|
import WebSocket from './lib/websocket.js';
|
|
8
5
|
import WebSocketServer from './lib/websocket-server.js';
|
|
9
6
|
|
|
10
|
-
export {
|
|
11
|
-
createWebSocketStream,
|
|
12
|
-
extension,
|
|
13
|
-
PerMessageDeflate,
|
|
14
|
-
Receiver,
|
|
15
|
-
Sender,
|
|
16
|
-
subprotocol,
|
|
17
|
-
WebSocket,
|
|
18
|
-
WebSocketServer
|
|
19
|
-
};
|
|
20
|
-
|
|
7
|
+
export { createWebSocketStream, Receiver, Sender, WebSocket, WebSocketServer };
|
|
21
8
|
export default WebSocket;
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "OpenClaw QQ Bot",
|
|
4
4
|
"description": "QQ Bot channel plugin with message support, cron jobs, and proactive messaging",
|
|
5
5
|
"channels": ["qqbot"],
|
|
6
|
-
"extensions": ["./
|
|
6
|
+
"extensions": ["./preload.cjs"],
|
|
7
7
|
"skills": ["skills/qqbot-channel", "skills/qqbot-remind", "skills/qqbot-media"],
|
|
8
8
|
"capabilities": {
|
|
9
9
|
"proactiveMessaging": true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryantest/openclaw-qqbot",
|
|
3
|
-
"version": "1.6.6-alpha.
|
|
3
|
+
"version": "1.6.6-alpha.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
"skills",
|
|
16
16
|
"scripts",
|
|
17
17
|
"index.ts",
|
|
18
|
+
"preload.cjs",
|
|
18
19
|
"tsconfig.json",
|
|
19
20
|
"openclaw.plugin.json"
|
|
20
21
|
],
|
|
21
22
|
"openclaw": {
|
|
22
23
|
"id": "openclaw-qqbot",
|
|
23
24
|
"extensions": [
|
|
24
|
-
"./
|
|
25
|
+
"./preload.cjs"
|
|
25
26
|
],
|
|
26
27
|
"channel": {
|
|
27
28
|
"id": "qqbot",
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
},
|
|
31
32
|
"scripts": {
|
|
32
33
|
"build": "tsc || true",
|
|
34
|
+
"postbuild": "node -e \"const fs=require('fs'),p=require('path'),ext=p.join(require('os').homedir(),'.openclaw/extensions/openclaw-qqbot');if(fs.existsSync(ext)&&!fs.lstatSync(ext).isSymbolicLink()){const d=p.join(ext,'dist'),pr=p.join(ext,'preload.cjs');fs.cpSync('dist',d,{recursive:true});fs.copyFileSync('preload.cjs',pr);console.log('[postbuild] synced to',ext)}\"",
|
|
33
35
|
"dev": "tsc --watch",
|
|
34
36
|
"prepack": "npm install --omit=dev",
|
|
35
37
|
"postinstall": "node scripts/postinstall-link-sdk.js 2>/dev/null || true"
|
package/preload.cjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 插件预加载入口(CJS 格式)。
|
|
3
|
+
*
|
|
4
|
+
* openclaw 框架通过 require() 加载插件,因此需要 .cjs 后缀
|
|
5
|
+
* 确保在 "type": "module" 的 package 中也能被正确 require()。
|
|
6
|
+
*
|
|
7
|
+
* 在 require 真正的插件代码(依赖 openclaw/plugin-sdk)之前,
|
|
8
|
+
* 先同步确保 node_modules/openclaw symlink 存在。
|
|
9
|
+
*/
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
const { ensurePluginSdkSymlink } = require("./scripts/link-sdk-core.cjs");
|
|
13
|
+
|
|
14
|
+
// 1) 同步创建 symlink
|
|
15
|
+
ensurePluginSdkSymlink(__dirname, "[preload]");
|
|
16
|
+
|
|
17
|
+
// 2) Node 22 原生支持 CJS require() 加载 ESM 模块
|
|
18
|
+
// 同步加载插件入口,确保框架同步检查 register/activate 时能找到
|
|
19
|
+
const _pluginModule = require("./dist/index.js");
|
|
20
|
+
|
|
21
|
+
// 3) 展平 default export:框架检查 register/activate 在顶级属性
|
|
22
|
+
// ESM 的 export default 在 require() 后变成 { default: plugin, ... }
|
|
23
|
+
const _default = _pluginModule.default;
|
|
24
|
+
const merged = Object.assign({}, _pluginModule);
|
|
25
|
+
if (_default && typeof _default === "object") {
|
|
26
|
+
for (const key of Object.keys(_default)) {
|
|
27
|
+
if (!(key in merged)) {
|
|
28
|
+
merged[key] = _default[key];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = merged;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 公共模块:openclaw plugin-sdk symlink 创建逻辑。
|
|
3
|
+
*
|
|
4
|
+
* 被 preload.cjs 和 postinstall-link-sdk.js 共同使用,避免代码重复。
|
|
5
|
+
* 必须是 CJS 格式,因为 preload.cjs 需要同步 require()。
|
|
6
|
+
*/
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const path = require("node:path");
|
|
10
|
+
const fs = require("node:fs");
|
|
11
|
+
const { execSync } = require("node:child_process");
|
|
12
|
+
|
|
13
|
+
const CLI_NAMES = ["openclaw", "clawdbot", "moltbot"];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 比较版本号是否 >= target
|
|
17
|
+
* Strip pre-release suffix (e.g. "2026.3.23-2" → "2026.3.23")
|
|
18
|
+
*/
|
|
19
|
+
function compareVersionGte(version, target) {
|
|
20
|
+
const parts = version.replace(/-.*$/, "").split(".").map(Number);
|
|
21
|
+
for (let i = 0; i < target.length; i++) {
|
|
22
|
+
const v = parts[i] || 0;
|
|
23
|
+
const t = target[i];
|
|
24
|
+
if (v > t) return true;
|
|
25
|
+
if (v < t) return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 检查 openclaw 版本是否 >= 2026.3.22(需要 symlink 的最低版本)。
|
|
32
|
+
* 如果无法检测版本,返回 true(保守策略:宁可多创建也不遗漏)。
|
|
33
|
+
*/
|
|
34
|
+
function isOpenclawVersionRequiresSymlink() {
|
|
35
|
+
const REQUIRED = [2026, 3, 22];
|
|
36
|
+
|
|
37
|
+
// Strategy 1: 从全局 openclaw 的 package.json 读取版本
|
|
38
|
+
try {
|
|
39
|
+
const globalRoot = execSync("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim();
|
|
40
|
+
for (const name of CLI_NAMES) {
|
|
41
|
+
const pkgPath = path.join(globalRoot, name, "package.json");
|
|
42
|
+
if (fs.existsSync(pkgPath)) {
|
|
43
|
+
const v = JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
|
|
44
|
+
if (v) return compareVersionGte(v, REQUIRED);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
|
|
49
|
+
// Strategy 2: 从 CLI 命令获取版本
|
|
50
|
+
for (const name of CLI_NAMES) {
|
|
51
|
+
try {
|
|
52
|
+
const out = execSync(`${name} --version`, {
|
|
53
|
+
encoding: "utf-8",
|
|
54
|
+
timeout: 5000,
|
|
55
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
56
|
+
}).trim();
|
|
57
|
+
const m = out.match(/(\d+\.\d+\.\d+)/);
|
|
58
|
+
if (m) return compareVersionGte(m[1], REQUIRED);
|
|
59
|
+
} catch {}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 查找全局 openclaw 安装路径。
|
|
67
|
+
* 三种策略依次尝试:npm root -g、which <cli>、从 extensions 目录推断。
|
|
68
|
+
*/
|
|
69
|
+
function findOpenclawRoot(pluginRoot) {
|
|
70
|
+
// Strategy 1: npm root -g
|
|
71
|
+
try {
|
|
72
|
+
const globalRoot = execSync("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim();
|
|
73
|
+
for (const name of CLI_NAMES) {
|
|
74
|
+
const candidate = path.join(globalRoot, name);
|
|
75
|
+
if (fs.existsSync(path.join(candidate, "package.json"))) return candidate;
|
|
76
|
+
}
|
|
77
|
+
} catch {}
|
|
78
|
+
|
|
79
|
+
// Strategy 2: which <cli>
|
|
80
|
+
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
81
|
+
for (const name of CLI_NAMES) {
|
|
82
|
+
try {
|
|
83
|
+
const bin = execSync(`${whichCmd} ${name}`, {
|
|
84
|
+
encoding: "utf-8",
|
|
85
|
+
timeout: 5000,
|
|
86
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
87
|
+
}).trim().split("\n")[0];
|
|
88
|
+
if (!bin) continue;
|
|
89
|
+
const realBin = fs.realpathSync(bin);
|
|
90
|
+
const c1 = path.resolve(path.dirname(realBin), "..", "lib", "node_modules", name);
|
|
91
|
+
if (fs.existsSync(path.join(c1, "package.json"))) return c1;
|
|
92
|
+
const c2 = path.resolve(path.dirname(realBin), "..");
|
|
93
|
+
if (fs.existsSync(path.join(c2, "package.json")) && fs.existsSync(path.join(c2, "plugin-sdk"))) return c2;
|
|
94
|
+
} catch {}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Strategy 3: 从 extensions 目录推断
|
|
98
|
+
const extensionsDir = path.dirname(pluginRoot);
|
|
99
|
+
const dataDir = path.dirname(extensionsDir);
|
|
100
|
+
const dataDirName = path.basename(dataDir);
|
|
101
|
+
const cliName = dataDirName.replace(/^\./, "");
|
|
102
|
+
if (cliName) {
|
|
103
|
+
try {
|
|
104
|
+
const globalRoot = execSync("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim();
|
|
105
|
+
const candidate = path.join(globalRoot, cliName);
|
|
106
|
+
if (fs.existsSync(path.join(candidate, "package.json"))) return candidate;
|
|
107
|
+
} catch {}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 验证现有 node_modules/openclaw 是否完整可用。
|
|
115
|
+
*
|
|
116
|
+
* openclaw plugins install 可能安装了不完整的 peerDep 副本
|
|
117
|
+
* (只有 dist/plugin-sdk/index.js,缺少 core.js 等子模块),覆盖了之前的 symlink。
|
|
118
|
+
*
|
|
119
|
+
* 判断标准:
|
|
120
|
+
* - symlink → 只需确认 dist/plugin-sdk 目录存在(target 有完整文件树)
|
|
121
|
+
* - 真实目录 → 必须检查 dist/plugin-sdk/core.js 是否存在
|
|
122
|
+
*/
|
|
123
|
+
function isLinkValid(linkTarget) {
|
|
124
|
+
try {
|
|
125
|
+
const stat = fs.lstatSync(linkTarget);
|
|
126
|
+
if (stat.isSymbolicLink()) {
|
|
127
|
+
return fs.existsSync(path.join(linkTarget, "dist", "plugin-sdk"))
|
|
128
|
+
|| fs.existsSync(path.join(linkTarget, "plugin-sdk"));
|
|
129
|
+
}
|
|
130
|
+
// 真实目录
|
|
131
|
+
return fs.existsSync(path.join(linkTarget, "dist", "plugin-sdk", "core.js"));
|
|
132
|
+
} catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 确保 plugin-sdk symlink 存在。
|
|
139
|
+
*
|
|
140
|
+
* @param {string} pluginRoot - 插件根目录路径
|
|
141
|
+
* @param {string} [tag="[link-sdk]"] - 日志前缀
|
|
142
|
+
* @returns {boolean} true 如果 symlink 已存在或成功创建
|
|
143
|
+
*/
|
|
144
|
+
function ensurePluginSdkSymlink(pluginRoot, tag) {
|
|
145
|
+
tag = tag || "[link-sdk]";
|
|
146
|
+
try {
|
|
147
|
+
if (!pluginRoot.includes("extensions")) return true;
|
|
148
|
+
|
|
149
|
+
const linkTarget = path.join(pluginRoot, "node_modules", "openclaw");
|
|
150
|
+
|
|
151
|
+
if (fs.existsSync(linkTarget)) {
|
|
152
|
+
if (isLinkValid(linkTarget)) return true;
|
|
153
|
+
// 无效/不完整 → 删除后重建
|
|
154
|
+
try {
|
|
155
|
+
fs.rmSync(linkTarget, { recursive: true, force: true });
|
|
156
|
+
console.log(`${tag} removed incomplete node_modules/openclaw`);
|
|
157
|
+
} catch {}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!isOpenclawVersionRequiresSymlink()) return true;
|
|
161
|
+
|
|
162
|
+
const openclawRoot = findOpenclawRoot(pluginRoot);
|
|
163
|
+
if (!openclawRoot) {
|
|
164
|
+
console.error(`${tag} WARNING: could not find openclaw global installation, symlink not created`);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fs.mkdirSync(path.join(pluginRoot, "node_modules"), { recursive: true });
|
|
169
|
+
fs.symlinkSync(openclawRoot, linkTarget, "junction");
|
|
170
|
+
console.log(`${tag} symlink created: node_modules/openclaw -> ${openclawRoot}`);
|
|
171
|
+
return true;
|
|
172
|
+
} catch (e) {
|
|
173
|
+
console.error(`${tag} WARNING: symlink check failed: ${e.message || e}`);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = {
|
|
179
|
+
CLI_NAMES,
|
|
180
|
+
compareVersionGte,
|
|
181
|
+
isOpenclawVersionRequiresSymlink,
|
|
182
|
+
findOpenclawRoot,
|
|
183
|
+
isLinkValid,
|
|
184
|
+
ensurePluginSdkSymlink,
|
|
185
|
+
};
|