@zhin.js/adapter-icqq 6.0.3 → 7.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/CHANGELOG.md +57 -0
- package/README.md +23 -5
- package/adapters/icqq.ts +79 -3
- package/commands/endpoint/add/[name:string=].ts +3 -0
- package/commands/endpoint/list.ts +3 -0
- package/commands/endpoint/remove/[name:string].ts +3 -0
- package/lib/endpoint.d.ts +52 -2
- package/lib/endpoint.js +317 -9
- package/lib/icqq-endpoint-commands.d.ts +1 -0
- package/lib/icqq-endpoint-commands.js +35 -0
- package/lib/icqq-guild.d.ts +8 -2
- package/lib/icqq-inbox.d.ts +33 -0
- package/lib/icqq-inbox.js +129 -0
- package/lib/icqq-runtime-state.d.ts +1 -0
- package/lib/icqq-runtime-state.js +6 -0
- package/lib/index.d.ts +1 -1
- package/lib/ipc-client.js +2 -2
- package/lib/outbound-media.d.ts +3 -2
- package/lib/outbound-media.js +17 -5
- package/lib/protocol.d.ts +6 -0
- package/lib/protocol.js +20 -3
- package/lib/types.d.ts +15 -0
- package/package.json +17 -11
- package/plugin.ts +5 -1
- package/schema.json +86 -17
- package/src/endpoint.ts +391 -10
- package/src/icqq-endpoint-commands.ts +37 -0
- package/src/icqq-guild.ts +9 -2
- package/src/icqq-inbox.ts +155 -0
- package/src/icqq-runtime-state.ts +7 -0
- package/src/index.ts +1 -0
- package/src/ipc-client.ts +2 -2
- package/src/outbound-media.ts +23 -5
- package/src/protocol.ts +25 -3
- package/src/types.ts +16 -0
package/lib/ipc-client.js
CHANGED
|
@@ -216,7 +216,7 @@ export class IpcClient {
|
|
|
216
216
|
token = (await readFile(getTokenPath(uin), "utf-8")).trim();
|
|
217
217
|
}
|
|
218
218
|
catch {
|
|
219
|
-
throw new Error(
|
|
219
|
+
throw new Error(`无法读取认证 token,守护进程可能未运行:请先执行 icqq login ${uin} 完成登录并启动守护进程`);
|
|
220
220
|
}
|
|
221
221
|
let rpcInfo;
|
|
222
222
|
try {
|
|
@@ -224,7 +224,7 @@ export class IpcClient {
|
|
|
224
224
|
rpcInfo = JSON.parse(raw);
|
|
225
225
|
}
|
|
226
226
|
catch {
|
|
227
|
-
throw new Error(
|
|
227
|
+
throw new Error(`RPC 未启用或守护进程未运行:请先执行 icqq login ${uin},并确认守护进程已启用 RPC`);
|
|
228
228
|
}
|
|
229
229
|
return IpcClient.connectRpc({
|
|
230
230
|
host: rpcInfo.host,
|
package/lib/outbound-media.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ export type IcqqOutboundMediaMode = "file" | "base64";
|
|
|
7
7
|
*/
|
|
8
8
|
export declare function resolveIcqqOutboundMediaMode(config: Pick<IcqqEndpointConfig, "rpc" | "outboundMedia">): IcqqOutboundMediaMode;
|
|
9
9
|
/**
|
|
10
|
-
* file 模式:将 segment.data.base64
|
|
11
|
-
* base64
|
|
10
|
+
* file 模式:将 segment.data.base64(legacy)或 canonical MediaRef
|
|
11
|
+
* (`data.media`,kind=base64)物化为本机路径,并把 media 改写为 path 引用。
|
|
12
|
+
* base64 模式:不改动(由 toCqString/formatOutboundBody 生成 base64://,供异机守护进程读取)。
|
|
12
13
|
*/
|
|
13
14
|
export declare function materializeOutboundBase64(content: SendContent, mode?: IcqqOutboundMediaMode): SendContent;
|
package/lib/outbound-media.js
CHANGED
|
@@ -33,8 +33,9 @@ export function resolveIcqqOutboundMediaMode(config) {
|
|
|
33
33
|
return config.rpc ? "base64" : "file";
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
* file 模式:将 segment.data.base64
|
|
37
|
-
* base64
|
|
36
|
+
* file 模式:将 segment.data.base64(legacy)或 canonical MediaRef
|
|
37
|
+
* (`data.media`,kind=base64)物化为本机路径,并把 media 改写为 path 引用。
|
|
38
|
+
* base64 模式:不改动(由 toCqString/formatOutboundBody 生成 base64://,供异机守护进程读取)。
|
|
38
39
|
*/
|
|
39
40
|
export function materializeOutboundBase64(content, mode = "file") {
|
|
40
41
|
if (mode === "base64")
|
|
@@ -45,16 +46,27 @@ export function materializeOutboundBase64(content, mode = "file") {
|
|
|
45
46
|
return seg;
|
|
46
47
|
const { type, data } = seg;
|
|
47
48
|
const d = data;
|
|
48
|
-
const
|
|
49
|
-
|
|
49
|
+
const media = d.media;
|
|
50
|
+
const mediaBase64 = media && media.kind === "base64" && typeof media.value === "string" && media.value
|
|
51
|
+
? media.value.replace(/^base64:\/\//, "")
|
|
52
|
+
: undefined;
|
|
53
|
+
const legacyBase64 = d.base64 ?? d.data;
|
|
54
|
+
const b64 = mediaBase64 ??
|
|
55
|
+
(typeof legacyBase64 === "string" && legacyBase64 ? legacyBase64 : undefined);
|
|
56
|
+
if (!b64)
|
|
50
57
|
return seg;
|
|
51
|
-
const mime = String(d.mime ?? d.mimeType ?? "");
|
|
58
|
+
const mime = String(d.mime ?? d.mimeType ?? (typeof media?.mime_type === "string" ? media.mime_type : "") ?? "");
|
|
52
59
|
if (type === "image" || type === "record" || type === "audio" || type === "video") {
|
|
53
60
|
const filePath = spoolBase64ToFile(b64, mime || (type === "image" ? "image/jpeg" : type === "video" ? "video/mp4" : "audio/mpeg"), type === "record" || type === "audio" ? "audio" : type);
|
|
54
61
|
return {
|
|
55
62
|
type,
|
|
56
63
|
data: {
|
|
57
64
|
...d,
|
|
65
|
+
media: {
|
|
66
|
+
kind: "path",
|
|
67
|
+
value: filePath,
|
|
68
|
+
...(mime ? { mime_type: mime } : {}),
|
|
69
|
+
},
|
|
58
70
|
file: filePath,
|
|
59
71
|
url: filePath,
|
|
60
72
|
},
|
package/lib/protocol.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* 登录相关:**推送**走 IpcEvent(`system.login.*`)。
|
|
13
13
|
*/
|
|
14
|
+
import type { MessageSegment } from "zhin.js";
|
|
14
15
|
/** CLI → Daemon 请求 */
|
|
15
16
|
export type IpcRequest = {
|
|
16
17
|
id: string;
|
|
@@ -237,6 +238,11 @@ export interface IcqqInboundMessage {
|
|
|
237
238
|
readonly id: string;
|
|
238
239
|
readonly target: string;
|
|
239
240
|
readonly content: string;
|
|
241
|
+
/**
|
|
242
|
+
* 结构化段视图(canonical Segment,经 toCanonicalSegments 归一)。
|
|
243
|
+
* 与 content(CQ 原文纯文本视图)同源,随 gateway.receive 上送。
|
|
244
|
+
*/
|
|
245
|
+
readonly segments?: readonly MessageSegment[];
|
|
240
246
|
readonly sender: string;
|
|
241
247
|
readonly channelType: 'private' | 'group' | 'channel';
|
|
242
248
|
readonly metadata?: Record<string, unknown>;
|
package/lib/protocol.js
CHANGED
|
@@ -277,16 +277,16 @@ export function formatOutboundBody(payload) {
|
|
|
277
277
|
case 'mention':
|
|
278
278
|
return `[at:${item.data?.qq ?? item.data?.id ?? item.data?.target ?? ''}]`;
|
|
279
279
|
case 'image':
|
|
280
|
-
return `[image:${item.data?.file || item.data?.url || base64Media(item.data) || ''}]`;
|
|
280
|
+
return `[image:${mediaRefValue(item.data) || item.data?.file || item.data?.url || base64Media(item.data) || ''}]`;
|
|
281
281
|
case 'face':
|
|
282
282
|
return `[face:${item.data?.id ?? ''}]`;
|
|
283
283
|
case 'reply':
|
|
284
284
|
return `[reply:${item.data?.message_id ?? item.data?.id ?? ''}]`;
|
|
285
285
|
case 'record':
|
|
286
286
|
case 'audio':
|
|
287
|
-
return `[record:${item.data?.file || item.data?.url || base64Media(item.data) || ''}]`;
|
|
287
|
+
return `[record:${mediaRefValue(item.data) || item.data?.file || item.data?.url || base64Media(item.data) || ''}]`;
|
|
288
288
|
case 'video':
|
|
289
|
-
return `[video:${item.data?.file || item.data?.url || base64Media(item.data) || ''}]`;
|
|
289
|
+
return `[video:${mediaRefValue(item.data) || item.data?.file || item.data?.url || base64Media(item.data) || ''}]`;
|
|
290
290
|
default:
|
|
291
291
|
return String(item.data?.text ?? '');
|
|
292
292
|
}
|
|
@@ -301,6 +301,23 @@ function base64Media(data) {
|
|
|
301
301
|
const b64 = data?.base64;
|
|
302
302
|
return typeof b64 === 'string' && b64 ? `base64://${b64}` : undefined;
|
|
303
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* canonical MediaRef(`data.media`,见 built/segment-contract)→ CQ 媒体值:
|
|
306
|
+
* base64 → `base64://` 前缀(守护进程解码);url/path 直出。
|
|
307
|
+
* file 模式下 materializeOutboundBase64 已把 base64 落盘并把 media 改写为 path 引用。
|
|
308
|
+
*/
|
|
309
|
+
function mediaRefValue(data) {
|
|
310
|
+
const media = data?.media;
|
|
311
|
+
if (!media || typeof media !== 'object' || Array.isArray(media))
|
|
312
|
+
return undefined;
|
|
313
|
+
const { kind, value } = media;
|
|
314
|
+
if (typeof value !== 'string' || !value)
|
|
315
|
+
return undefined;
|
|
316
|
+
if (kind === 'base64') {
|
|
317
|
+
return value.startsWith('base64://') ? value : `base64://${value}`;
|
|
318
|
+
}
|
|
319
|
+
return value;
|
|
320
|
+
}
|
|
304
321
|
/** 段对象判定:`{ type: string, data? }`(非数组 payload 的防御入口)。 */
|
|
305
322
|
function isWireSegment(value) {
|
|
306
323
|
return typeof value === 'object'
|
package/lib/types.d.ts
CHANGED
|
@@ -70,3 +70,18 @@ export interface IpcMemberInfo {
|
|
|
70
70
|
last_sent_time?: number;
|
|
71
71
|
shutup_time?: number;
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* GET_SYSTEM_MSG 归一后的待处理请求(@icqqjs/cli NormalizedSystemMessage)。
|
|
75
|
+
* friendRequests 与 groupRequests 均为该形状,按 flag 处理。
|
|
76
|
+
*/
|
|
77
|
+
export interface IpcSystemMessage {
|
|
78
|
+
type: string;
|
|
79
|
+
user_id?: number;
|
|
80
|
+
nickname?: string;
|
|
81
|
+
group_id?: number;
|
|
82
|
+
group_name?: string;
|
|
83
|
+
comment?: string;
|
|
84
|
+
flag?: string;
|
|
85
|
+
seq?: number;
|
|
86
|
+
time?: number;
|
|
87
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-icqq",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.1",
|
|
4
4
|
"description": "Zhin.js ICQQ adapter for Plugin Runtime (IPC daemon client)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -34,18 +34,19 @@
|
|
|
34
34
|
"directory": "plugins/adapters/icqq"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@zhin.js/adapter": "1.
|
|
38
|
-
"@zhin.js/
|
|
37
|
+
"@zhin.js/adapter": "1.1.1",
|
|
38
|
+
"@zhin.js/command": "1.0.3",
|
|
39
|
+
"@zhin.js/core": "1.4.1",
|
|
39
40
|
"@zhin.js/logger": "1.0.75",
|
|
40
|
-
"@zhin.js/plugin-runtime": "1.
|
|
41
|
+
"@zhin.js/plugin-runtime": "1.1.1"
|
|
41
42
|
},
|
|
42
43
|
"peerDependencies": {
|
|
43
44
|
"zod": "^4.0.0",
|
|
44
|
-
"@zhin.js/adapter": "1.
|
|
45
|
-
"@zhin.js/agent": "1.0.
|
|
46
|
-
"@zhin.js/core": "1.
|
|
47
|
-
"@zhin.js/plugin-runtime": "1.
|
|
48
|
-
"zhin.js": "
|
|
45
|
+
"@zhin.js/adapter": "1.1.1",
|
|
46
|
+
"@zhin.js/agent": "1.0.6",
|
|
47
|
+
"@zhin.js/core": "1.4.1",
|
|
48
|
+
"@zhin.js/plugin-runtime": "1.1.1",
|
|
49
|
+
"zhin.js": "5.0.1"
|
|
49
50
|
},
|
|
50
51
|
"peerDependenciesMeta": {
|
|
51
52
|
"zhin.js": {
|
|
@@ -63,11 +64,12 @@
|
|
|
63
64
|
"typescript": "^6.0.3",
|
|
64
65
|
"vitest": "^4.1.10",
|
|
65
66
|
"zod": "^4.4.3",
|
|
66
|
-
"@zhin.js/agent": "1.0.
|
|
67
|
-
"zhin.js": "
|
|
67
|
+
"@zhin.js/agent": "1.0.6",
|
|
68
|
+
"zhin.js": "5.0.1"
|
|
68
69
|
},
|
|
69
70
|
"files": [
|
|
70
71
|
"adapters",
|
|
72
|
+
"commands",
|
|
71
73
|
"plugin.ts",
|
|
72
74
|
"schema.json",
|
|
73
75
|
"src",
|
|
@@ -93,6 +95,10 @@
|
|
|
93
95
|
{
|
|
94
96
|
"package": "@zhin.js/adapter",
|
|
95
97
|
"api": "^1.0.0"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"package": "@zhin.js/command",
|
|
101
|
+
"api": "^1.0.0"
|
|
96
102
|
}
|
|
97
103
|
],
|
|
98
104
|
"plugins": []
|
package/plugin.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { createEndpointRuntimeState } from '@zhin.js/adapter';
|
|
1
2
|
import { definePlugin } from '@zhin.js/plugin-runtime';
|
|
3
|
+
import { icqqRuntimeStateToken } from './src/icqq-runtime-state.js';
|
|
2
4
|
|
|
3
5
|
// Module-level guard: multiple icqq instances (icqq, icqq-2, …) share this
|
|
4
6
|
// module, but the prompt contributor is per-platform — register it once.
|
|
@@ -9,7 +11,9 @@ export default definePlugin({
|
|
|
9
11
|
metadata: {
|
|
10
12
|
displayName: 'ICQQ Adapter',
|
|
11
13
|
},
|
|
12
|
-
setup() {
|
|
14
|
+
setup(context) {
|
|
15
|
+
// 运行中 endpoint 注册表(icqq endpoint list 的"运行中"数据源)
|
|
16
|
+
context.resources.provide(icqqRuntimeStateToken, createEndpointRuntimeState());
|
|
13
17
|
// Agent prompt contributor (orchestrator/deferred-worker ICQQ guidance).
|
|
14
18
|
// `zhin.js/agent` is an optional peer — skip silently on IM-only installs.
|
|
15
19
|
if (promptContributorRegistered) return;
|
package/schema.json
CHANGED
|
@@ -3,33 +3,102 @@
|
|
|
3
3
|
"type": "object",
|
|
4
4
|
"additionalProperties": false,
|
|
5
5
|
"properties": {
|
|
6
|
-
"name": {
|
|
7
|
-
"type": "string",
|
|
8
|
-
"description": "QQ uin (must match icqq login account)"
|
|
9
|
-
},
|
|
10
6
|
"master": {
|
|
11
|
-
"type": [
|
|
7
|
+
"type": [
|
|
8
|
+
"string",
|
|
9
|
+
"number"
|
|
10
|
+
],
|
|
12
11
|
"description": "Endpoint Owner QQ uin (for /approve bash and master role)"
|
|
13
12
|
},
|
|
13
|
+
"trusted": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": {
|
|
16
|
+
"type": [
|
|
17
|
+
"string",
|
|
18
|
+
"number"
|
|
19
|
+
],
|
|
20
|
+
"description": "Trusted QQ uin"
|
|
21
|
+
},
|
|
22
|
+
"description": "Trusted QQ uin list"
|
|
23
|
+
},
|
|
14
24
|
"autoReconnect": {
|
|
15
25
|
"type": "boolean",
|
|
16
26
|
"default": true
|
|
17
27
|
},
|
|
18
28
|
"outboundMedia": {
|
|
19
29
|
"type": "string",
|
|
20
|
-
"enum": [
|
|
30
|
+
"enum": [
|
|
31
|
+
"file",
|
|
32
|
+
"base64"
|
|
33
|
+
],
|
|
21
34
|
"description": "file=local path (same host as daemon); base64=CQ base64:// (default when rpc set)"
|
|
22
35
|
},
|
|
23
|
-
"
|
|
24
|
-
"type": "
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
"endpoints": {
|
|
37
|
+
"type": "array",
|
|
38
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(name 必填,其余覆盖顶层)",
|
|
39
|
+
"items": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": true,
|
|
42
|
+
"properties": {
|
|
43
|
+
"name": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "QQ uin (must match icqq login account)"
|
|
46
|
+
},
|
|
47
|
+
"master": {
|
|
48
|
+
"type": [
|
|
49
|
+
"string",
|
|
50
|
+
"number"
|
|
51
|
+
],
|
|
52
|
+
"description": "Endpoint Owner QQ uin (for /approve bash and master role)"
|
|
53
|
+
},
|
|
54
|
+
"trusted": {
|
|
55
|
+
"type": "array",
|
|
56
|
+
"items": {
|
|
57
|
+
"type": [
|
|
58
|
+
"string",
|
|
59
|
+
"number"
|
|
60
|
+
],
|
|
61
|
+
"description": "Trusted QQ uin"
|
|
62
|
+
},
|
|
63
|
+
"description": "Trusted QQ uin list"
|
|
64
|
+
},
|
|
65
|
+
"rpc": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"additionalProperties": false,
|
|
68
|
+
"properties": {
|
|
69
|
+
"host": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"description": "RPC host"
|
|
72
|
+
},
|
|
73
|
+
"port": {
|
|
74
|
+
"type": "number",
|
|
75
|
+
"description": "RPC port"
|
|
76
|
+
},
|
|
77
|
+
"token": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "RPC token"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"required": [
|
|
83
|
+
"host",
|
|
84
|
+
"port",
|
|
85
|
+
"token"
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"required": [
|
|
90
|
+
"name"
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"commandPrefix": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"default": "",
|
|
97
|
+
"description": "命令前缀(默认 '' 无前缀,任意文本按命令匹配;如 '/' 要求 / 开头)。endpoints[i] 可逐项覆盖"
|
|
32
98
|
}
|
|
33
99
|
},
|
|
34
|
-
"required": [
|
|
35
|
-
|
|
100
|
+
"required": [
|
|
101
|
+
"master",
|
|
102
|
+
"endpoints"
|
|
103
|
+
]
|
|
104
|
+
}
|