@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/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("无法读取认证 token,守护进程可能未运行");
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("RPC 未启用或守护进程未运行");
227
+ throw new Error(`RPC 未启用或守护进程未运行:请先执行 icqq login ${uin},并确认守护进程已启用 RPC`);
228
228
  }
229
229
  return IpcClient.connectRpc({
230
230
  host: rpcInfo.host,
@@ -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 模式:不改动(由 toCqString 生成 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;
@@ -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 模式:不改动(由 toCqString 生成 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 b64 = d.base64 ?? d.data;
49
- if (typeof b64 !== "string" || !b64)
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": "6.0.3",
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.0.1",
38
- "@zhin.js/core": "1.3.5",
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.0.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.0.1",
45
- "@zhin.js/agent": "1.0.4",
46
- "@zhin.js/core": "1.3.5",
47
- "@zhin.js/plugin-runtime": "1.0.1",
48
- "zhin.js": "4.1.3"
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.4",
67
- "zhin.js": "4.1.3"
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": ["string", "number"],
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": ["file", "base64"],
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
- "rpc": {
24
- "type": "object",
25
- "additionalProperties": false,
26
- "properties": {
27
- "host": { "type": "string" },
28
- "port": { "type": "number" },
29
- "token": { "type": "string" }
30
- },
31
- "required": ["host", "port", "token"]
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": ["name"]
35
- }
100
+ "required": [
101
+ "master",
102
+ "endpoints"
103
+ ]
104
+ }