@zhin.js/adapter-icqq 8.0.9 → 8.0.10

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +36 -28
  3. package/adapters/icqq.js +4 -9
  4. package/adapters/icqq.ts +5 -14
  5. package/agent/PERMITS.md +2 -0
  6. package/lib/agent-prompt.js +17 -22
  7. package/lib/cq-message.d.ts +2 -2
  8. package/lib/cq-message.js +2 -2
  9. package/lib/endpoint.d.ts +4 -74
  10. package/lib/endpoint.js +348 -507
  11. package/lib/forward-msg.d.ts +7 -8
  12. package/lib/forward-msg.js +11 -24
  13. package/lib/icqq-agent-deps.d.ts +3 -15
  14. package/lib/icqq-agent-deps.js +0 -5
  15. package/lib/icqq-endpoint-commands.js +4 -13
  16. package/lib/icqq-guild.d.ts +15 -11
  17. package/lib/icqq-guild.js +9 -17
  18. package/lib/icqq-inbound.d.ts +20 -20
  19. package/lib/icqq-inbound.js +9 -9
  20. package/lib/icqq-inbox.d.ts +2 -10
  21. package/lib/index.d.ts +2 -3
  22. package/lib/index.js +2 -3
  23. package/lib/outbound-media.d.ts +3 -8
  24. package/lib/outbound-media.js +3 -8
  25. package/lib/protocol.d.ts +38 -57
  26. package/lib/protocol.js +24 -21
  27. package/lib/types.d.ts +4 -40
  28. package/lib/types.js +0 -5
  29. package/package.json +25 -13
  30. package/plugin.js +6 -1
  31. package/schema.json +119 -61
  32. package/src/agent-prompt.ts +17 -22
  33. package/src/cq-message.ts +2 -2
  34. package/src/endpoint.ts +358 -600
  35. package/src/forward-msg.ts +12 -26
  36. package/src/icqq-agent-deps.ts +3 -17
  37. package/src/icqq-endpoint-commands.ts +4 -13
  38. package/src/icqq-guild.ts +16 -25
  39. package/src/icqq-inbound.ts +26 -26
  40. package/src/icqq-inbox.ts +2 -10
  41. package/src/icqqjs-icqq.d.ts +88 -0
  42. package/src/index.ts +2 -9
  43. package/src/outbound-media.ts +4 -9
  44. package/src/protocol.ts +60 -66
  45. package/src/types.ts +4 -41
  46. package/tools/announce.js +19 -0
  47. package/tools/announce.ts +18 -0
  48. package/tools/essence.js +24 -0
  49. package/{agent/tools → tools}/essence.ts +8 -8
  50. package/tools/friend_list.js +20 -0
  51. package/{agent/tools → tools}/friend_list.ts +7 -7
  52. package/tools/group_files.js +25 -0
  53. package/tools/group_files.ts +23 -0
  54. package/tools/group_invite.js +25 -0
  55. package/tools/group_invite.ts +22 -0
  56. package/tools/group_kick.js +28 -0
  57. package/tools/group_kick.ts +24 -0
  58. package/tools/group_list.js +21 -0
  59. package/{agent/tools → tools}/group_list.ts +7 -7
  60. package/tools/group_sign.js +21 -0
  61. package/tools/group_sign.ts +19 -0
  62. package/tools/list_muted.js +20 -0
  63. package/tools/list_muted.ts +19 -0
  64. package/tools/poke.js +19 -0
  65. package/tools/poke.ts +18 -0
  66. package/tools/send_user_like.js +28 -0
  67. package/tools/send_user_like.ts +25 -0
  68. package/tools/set_anonymous.js +23 -0
  69. package/tools/set_anonymous.ts +21 -0
  70. package/tools/set_mute.js +26 -0
  71. package/tools/set_mute.ts +23 -0
  72. package/tools/set_title.js +27 -0
  73. package/tools/set_title.ts +24 -0
  74. package/tools/user_info.js +17 -0
  75. package/tools/user_info.ts +16 -0
  76. package/agent/tools/announce.ts +0 -21
  77. package/agent/tools/group_files.ts +0 -27
  78. package/agent/tools/group_invite.ts +0 -20
  79. package/agent/tools/group_sign.ts +0 -19
  80. package/agent/tools/list_muted.ts +0 -22
  81. package/agent/tools/poke.ts +0 -20
  82. package/agent/tools/send_user_like.ts +0 -22
  83. package/agent/tools/set_anonymous.ts +0 -22
  84. package/agent/tools/set_title.ts +0 -25
  85. package/lib/ipc-client.d.ts +0 -73
  86. package/lib/ipc-client.js +0 -358
  87. package/src/ipc-client.ts +0 -417
@@ -1,10 +1,5 @@
1
- /**
2
- * 合并转发(聊天记录)解析:从 json/xml 提取 resid,经 IPC get_forward_msg 拉取正文。
3
- */
4
1
  import { segment, type MessageSegment, type QuotedMessagePayload } from 'zhin.js';
5
2
 
6
- import { Actions } from "./protocol.js";
7
- import type { IpcClient } from "./ipc-client.js";
8
3
  import {
9
4
  icqqElementsToSegments,
10
5
  type IcqqMessageElement,
@@ -215,7 +210,7 @@ export function extractForwardResidFromPayload(
215
210
  return undefined;
216
211
  }
217
212
 
218
- /** 在 get_msg / IPC 原始对象里深度搜索 resid */
213
+ /** 在 get_msg 原始对象里深度搜索 resid */
219
214
  export function extractForwardResidDeep(
220
215
  root: unknown,
221
216
  maxDepth = 14,
@@ -367,22 +362,14 @@ export function formatForwardMsgResponse(data: unknown): string {
367
362
  }
368
363
 
369
364
  export async function fetchForwardMsgText(
370
- ipc: IpcClient,
365
+ client: { getForwardMsg(resId: string, fileName?: string): Promise<unknown[]> },
371
366
  id: string,
372
367
  ): Promise<string> {
373
- const attempts: Record<string, unknown>[] = [
374
- { message_id: id },
375
- { id },
376
- { resid: id },
377
- { res_id: id },
378
- { msg_id: id },
379
- ];
380
- for (const params of attempts) {
381
- const resp = await ipc.request(Actions.GET_FORWARD_MSG, params);
382
- if (!resp.ok) continue;
383
- const text = formatForwardMsgResponse(resp.data);
368
+ try {
369
+ const messages = await client.getForwardMsg(id);
370
+ const text = formatForwardMsgResponse(messages);
384
371
  if (text.trim()) return text.trim();
385
- }
372
+ } catch { /* ignore */ }
386
373
  return "";
387
374
  }
388
375
 
@@ -404,18 +391,17 @@ function appendForwardBlock(
404
391
  };
405
392
  }
406
393
 
407
- /** 若 payload 含合并转发,拉取并追加到 content */
408
394
  export async function enrichQuotedPayloadWithForward(
409
- ipc: IpcClient | null | undefined,
395
+ client: { getForwardMsg(resId: string, fileName?: string): Promise<unknown[]> } | null | undefined,
410
396
  payload: QuotedMessagePayload,
411
- ipcRaw?: unknown,
397
+ rawData?: unknown,
412
398
  ): Promise<QuotedMessagePayload> {
413
- if (!ipc || hasMergedForwardBlock(payload)) return payload;
399
+ if (!client || hasMergedForwardBlock(payload)) return payload;
414
400
 
415
401
  const resid =
416
402
  extractForwardResidFromPayload(payload) ??
417
- (ipcRaw ? extractForwardResidFromGetMsg(ipcRaw) : undefined) ??
418
- (ipcRaw ? extractForwardResidDeep(ipcRaw) : undefined);
403
+ (rawData ? extractForwardResidFromGetMsg(rawData) : undefined) ??
404
+ (rawData ? extractForwardResidDeep(rawData) : undefined);
419
405
 
420
406
  const fetchIds = [
421
407
  resid,
@@ -423,7 +409,7 @@ export async function enrichQuotedPayloadWithForward(
423
409
  ].filter((v, i, a): v is string => !!v && a.indexOf(v) === i);
424
410
 
425
411
  for (const fetchId of fetchIds) {
426
- const forwardText = await fetchForwardMsgText(ipc, fetchId);
412
+ const forwardText = await fetchForwardMsgText(client, fetchId);
427
413
  if (!forwardText.trim()) continue;
428
414
  const block = `[Merged chat history — id ${fetchId}]\n${forwardText.trim()}`;
429
415
  return appendForwardBlock(payload, block);
@@ -1,20 +1,7 @@
1
- /**
2
- * Agent tool deps for icqq.
3
- * Endpoints register themselves on start; tools look up by config name / QQ uin.
4
- */
1
+ import type { Client } from '@icqqjs/icqq';
5
2
 
6
- import type { IpcClient } from './ipc-client.js';
7
- import type { IpcFriendInfo, IpcGroupInfo } from './types.js';
8
- import type { IpcResponse } from './protocol.js';
9
-
10
- export interface IcqqAgentEndpoint {
11
- readonly name: string;
12
- readonly ipc: {
13
- request(action: string, params?: Record<string, unknown>): Promise<IpcResponse>;
14
- };
15
- readonly friends: Map<number, IpcFriendInfo>;
16
- readonly groups: Map<number, IpcGroupInfo>;
17
- request(action: string, params?: Record<string, unknown>): Promise<IpcResponse>;
3
+ export interface IcqqAgentEndpoint extends Client {
4
+ readonly endpointName: string;
18
5
  }
19
6
 
20
7
  export interface IcqqAgentDeps {
@@ -37,7 +24,6 @@ export function registerIcqqAgentEndpoint(
37
24
  };
38
25
  }
39
26
 
40
- /** Optional override used by tests / transitional callers. Pass `null` to clear. */
41
27
  export function setIcqqAgentDeps(deps: IcqqAgentDeps | null): void {
42
28
  override = deps;
43
29
  }
@@ -1,12 +1,3 @@
1
- /**
2
- * `icqq.endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
3
- *
4
- * ICQQ 的凭据由外部守护进程(@icqqjs/cli)按账号持有(~/.icqq/<uin>/),
5
- * 其 IPC/RPC 协议是「按 uin 连接 + auth + 账号级 action」,没有 add-account /
6
- * 触发 login 的 action——扫码登录只能经 `icqq login <uin>` CLI 完成。
7
- * 因此 add 不走 kv 凭据录入,而是 bindFlow:校验 uin、把 { id: '<uin>' }
8
- * 追加到 plugins.icqq.endpoints,并引导用户先完成守护进程登录。
9
- */
10
1
  import { addEndpointToConfig, createEndpointCommands } from '@zhin.js/adapter';
11
2
  import { defineCommand } from '@zhin.js/command';
12
3
  import { icqqRuntimeStateToken } from './icqq-runtime-state.js';
@@ -15,11 +6,11 @@ export const icqqEndpointCommands = createEndpointCommands({
15
6
  adapterKey: 'icqq',
16
7
  adapterDisplayName: 'ICQQ',
17
8
  running: (use) => use(icqqRuntimeStateToken).endpoints.values(),
18
- describeEntry: (entry) => (entry.rpc ? 'rpc(远程守护进程)' : 'ipc(本地守护进程)'),
19
- addDescription: '登记 ICQQ endpoint(uin 需先经 icqq login 完成扫码登录;重启 zhin 生效)',
9
+ describeEntry: () => 'direct(直连 @icqqjs/icqq)',
10
+ addDescription: '登记 ICQQ endpoint(重启 zhin 生效)',
20
11
  bindFlow: ({ id }) => {
21
12
  if (!id) {
22
- return '用法:icqq.endpoint add <uin>(uin 为纯数字 QQ 号,且需先执行 icqq login <uin> 完成扫码登录)';
13
+ return '用法:icqq.endpoint add <uin>(uin 为纯数字 QQ 号)';
23
14
  }
24
15
  if (!/^\d+$/.test(id)) {
25
16
  return 'icqq endpoint 名必须是纯数字 QQ 号(uin)';
@@ -28,7 +19,7 @@ export const icqqEndpointCommands = createEndpointCommands({
28
19
  const filePath = addEndpointToConfig('icqq', { id });
29
20
  return (
30
21
  `✅ endpoint「${id}」已追加到 ${filePath} 的 plugins.icqq.endpoints。\n` +
31
- `⚠️ 请确保已执行 icqq login ${id} 启动守护进程并完成扫码登录;重启 zhin 后生效。`
22
+ `重启 zhin 后生效。`
32
23
  );
33
24
  } catch (error) {
34
25
  return `添加失败:${error instanceof Error ? error.message : String(error)}`;
package/src/icqq-guild.ts CHANGED
@@ -2,15 +2,12 @@
2
2
  * ICQQ guild (QQ 频道) catalog sync, inbound normalize, Console channel list.
3
3
  */
4
4
  import { toCanonicalSegments, type MessageSegment } from "zhin.js";
5
- import { Actions, type IpcGuildMessageEventData } from "./protocol.js";
5
+ import type { GuildMessageEventData } from "./protocol.js";
6
6
  import { parseCqMessage } from "./cq-message.js";
7
7
 
8
- /** Narrow request port shared by the real IPC client and endpoint test transports. */
9
- export interface IcqqGuildRpc {
10
- request(
11
- action: string,
12
- params?: Record<string, unknown>,
13
- ): Promise<{ ok: boolean; data?: unknown }>;
8
+ export interface IcqqGuildClient {
9
+ getGuildList(): Array<{ guild_id: string; guild_name: string }>;
10
+ getChannelList(guildId: string): Array<{ guild_id: string; channel_id: string; channel_name: string }>;
14
11
  }
15
12
 
16
13
  export interface IcqqGuildChannelEntry {
@@ -31,7 +28,7 @@ export interface NormalizedIcqqGuildInbound {
31
28
  content: MessageSegment[];
32
29
  rawMessage: string;
33
30
  timestampMs: number;
34
- raw: IpcGuildMessageEventData;
31
+ raw: GuildMessageEventData;
35
32
  }
36
33
 
37
34
  export class IcqqGuildCatalog {
@@ -43,25 +40,19 @@ export class IcqqGuildCatalog {
43
40
  this.guilds.clear();
44
41
  }
45
42
 
46
- async syncAll(ipc: IcqqGuildRpc): Promise<void> {
47
- const listResp = await ipc.request(Actions.GUILD_LIST);
48
- if (!listResp.ok) return;
49
-
50
- const guildRows = Array.isArray(listResp.data) ? listResp.data : [];
43
+ async syncAll(client: IcqqGuildClient): Promise<void> {
44
+ const guildRows = client.getGuildList();
51
45
  for (const row of guildRows) {
52
- const rec = row as Record<string, unknown>;
53
- const guild_id = String(rec.guild_id ?? rec.id ?? "");
46
+ const guild_id = String(row.guild_id ?? "");
54
47
  if (!guild_id) continue;
55
- const guild_name = String(rec.guild_name ?? rec.name ?? guild_id);
48
+ const guild_name = String(row.guild_name ?? guild_id);
56
49
  this.guilds.set(guild_id, { guild_id, guild_name });
57
50
 
58
- const chResp = await ipc.request(Actions.GUILD_CHANNELS, { guild_id });
59
- if (!chResp.ok || !Array.isArray(chResp.data)) continue;
60
- for (const ch of chResp.data) {
61
- const chRec = ch as Record<string, unknown>;
62
- const channel_id = String(chRec.channel_id ?? chRec.id ?? "");
51
+ const channels = client.getChannelList(guild_id);
52
+ for (const ch of channels) {
53
+ const channel_id = String(ch.channel_id ?? "");
63
54
  if (!channel_id) continue;
64
- const channel_name = String(chRec.channel_name ?? chRec.name ?? channel_id);
55
+ const channel_name = String(ch.channel_name ?? channel_id);
65
56
  this.channels.set(channel_id, {
66
57
  channel_id,
67
58
  channel_name,
@@ -72,7 +63,7 @@ export class IcqqGuildCatalog {
72
63
  }
73
64
  }
74
65
 
75
- upsertFromInbound(data: IpcGuildMessageEventData): void {
66
+ upsertFromInbound(data: GuildMessageEventData): void {
76
67
  const guild_id = String(data.guild_id ?? "");
77
68
  const channel_id = String(data.channel_id ?? "");
78
69
  if (!guild_id || !channel_id) return;
@@ -119,12 +110,12 @@ export class IcqqGuildCatalog {
119
110
  }
120
111
  }
121
112
 
122
- export function isIcqqGuildIpcEvent(eventName: string | undefined): boolean {
113
+ export function isIcqqGuildEvent(eventName: string | undefined): boolean {
123
114
  return typeof eventName === "string" && eventName.startsWith("message.guild");
124
115
  }
125
116
 
126
117
  export function normalizeIcqqGuildInboundMessage(
127
- data: IpcGuildMessageEventData & Record<string, unknown>,
118
+ data: GuildMessageEventData & Record<string, unknown>,
128
119
  ): NormalizedIcqqGuildInbound | null {
129
120
  if (data.type !== "guild") return null;
130
121
  const guildId = String(data.guild_id ?? "");
@@ -1,5 +1,5 @@
1
1
  /**
2
- * icqq 守护进程 IPC 入站事件归一化(post_type / message_type / message 段)
2
+ * icqq 入站事件归一化(post_type / message_type / message 段)
3
3
  */
4
4
  import { Message, toCanonicalSegments, type MessageSegment, type QuotedMessagePayload } from "zhin.js";
5
5
  import { parseCqMessage, icqqMediaRefFromString } from "./cq-message.js";
@@ -7,13 +7,13 @@ import { extractForwardResidFromJsonElement } from "./forward-msg.js";
7
7
  import type { GroupRole } from "./types.js";
8
8
 
9
9
  /** 守护进程推送的通用事件壳 */
10
- export interface IcqqIpcEventBase {
10
+ export interface IcqqEventBase {
11
11
  post_type?: string;
12
12
  self_id?: number;
13
13
  }
14
14
 
15
15
  /** post_type=message 时的消息体(字段随 icqqjs/cli 演进,未知字段保留) */
16
- export interface IcqqIpcMessageEvent extends IcqqIpcEventBase {
16
+ export interface IcqqMessageEvent extends IcqqEventBase {
17
17
  post_type: "message";
18
18
  message_id?: string;
19
19
  msg_id?: string;
@@ -32,7 +32,7 @@ export interface IcqqIpcMessageEvent extends IcqqIpcEventBase {
32
32
  card?: string;
33
33
  /** 群临时会话来源群号 */
34
34
  group_id?: number;
35
- /** NT/IPC 群成员角色:owner | admin | member */
35
+ /** 群成员角色:owner | admin | member */
36
36
  role?: GroupRole | string;
37
37
  };
38
38
  from_id?: number;
@@ -40,7 +40,7 @@ export interface IcqqIpcMessageEvent extends IcqqIpcEventBase {
40
40
  group_id?: number;
41
41
  to_id?: number;
42
42
  nickname?: string;
43
- /** 旧版 IPC 仅有 type 无 message_type */
43
+ /** 旧版仅有 type 无 message_type */
44
44
  type?: "group" | "private";
45
45
  /** oicq Message#source:被引用回复的源消息 */
46
46
  source?: IcqqMessageSource;
@@ -87,19 +87,19 @@ export interface NormalizedIcqqInbound {
87
87
  channelParentGroupId?: string;
88
88
  userId: string;
89
89
  nickname: string;
90
- /** 群聊时来自 IPC sender.role(私聊无) */
90
+ /** 群聊时来自 sender.role(私聊无) */
91
91
  senderRole?: GroupRole;
92
92
  content: MessageSegment[];
93
93
  rawMessage: string;
94
94
  timestampMs: number;
95
- raw: IcqqIpcMessageEvent;
95
+ raw: IcqqMessageEvent;
96
96
  }
97
97
 
98
98
  const DEDUPE_TTL_MS = 120_000;
99
99
 
100
100
  const ICQQ_GROUP_ROLES = new Set<GroupRole>(["owner", "admin", "member"]);
101
101
 
102
- /** 从 IPC sender.role 解析群成员角色(供 Message.$sender → resolveSenderRoles) */
102
+ /** 从 sender.role 解析群成员角色(供 Message.$sender → resolveSenderRoles) */
103
103
  export function parseIcqqSenderGroupRole(
104
104
  sender: { role?: unknown } | undefined,
105
105
  ): GroupRole | undefined {
@@ -148,15 +148,15 @@ function isMessagePostType(postType: unknown): boolean {
148
148
  return postType.startsWith("message.");
149
149
  }
150
150
 
151
- /** 从 IPC 事件壳中取出 OneBot/icqq 载荷(兼容 data 嵌套或字段在根级) */
152
- export function unwrapIcqqIpcEventPayload(event: {
151
+ /** 从事件壳中取出 OneBot/icqq 载荷(兼容 data 嵌套或字段在根级) */
152
+ export function unwrapIcqqEventPayload(event: {
153
153
  data?: unknown;
154
154
  [key: string]: unknown;
155
155
  }): unknown {
156
156
  const nested = event.data;
157
157
  if (nested && typeof nested === "object" && !Array.isArray(nested)) {
158
158
  const d = nested as Record<string, unknown>;
159
- if (isRecognizedIcqqIpcPayload(d)) {
159
+ if (isRecognizedIcqqPayload(d)) {
160
160
  return nested;
161
161
  }
162
162
  for (const key of ["data", "detail", "payload", "event"]) {
@@ -168,13 +168,13 @@ export function unwrapIcqqIpcEventPayload(event: {
168
168
  }
169
169
 
170
170
  const { id: _id, event: _ev, data: _data, ok: _ok, ...rest } = event;
171
- if (isRecognizedIcqqIpcPayload(rest)) {
171
+ if (isRecognizedIcqqPayload(rest)) {
172
172
  return rest;
173
173
  }
174
174
  return nested;
175
175
  }
176
176
 
177
- function isRecognizedIcqqIpcPayload(d: Record<string, unknown>): boolean {
177
+ function isRecognizedIcqqPayload(d: Record<string, unknown>): boolean {
178
178
  const pt = d.post_type;
179
179
  if (typeof pt === "string") {
180
180
  if (
@@ -216,7 +216,7 @@ export function resolveIcqqInboundUserId(
216
216
 
217
217
  export function isIcqqMessagePostType(
218
218
  data: unknown,
219
- ): data is IcqqIpcMessageEvent {
219
+ ): data is IcqqMessageEvent {
220
220
  if (!data || typeof data !== "object") return false;
221
221
  const d = data as Record<string, unknown>;
222
222
  if (isMessagePostType(d.post_type)) return true;
@@ -232,14 +232,14 @@ export function isIcqqMessagePostType(
232
232
  }
233
233
 
234
234
  export function shouldSkipSelfInboundMessage(
235
- data: IcqqIpcMessageEvent,
235
+ data: IcqqMessageEvent,
236
236
  ): boolean {
237
237
  if (data.self_id == null || data.user_id == null) return false;
238
238
  return Number(data.self_id) === Number(data.user_id);
239
239
  }
240
240
 
241
241
  /** 群临时会话私聊:icqq message_type=private & sub_type=group */
242
- export function isIcqqGroupTempPrivateMessage(data: IcqqIpcMessageEvent): boolean {
242
+ export function isIcqqGroupTempPrivateMessage(data: IcqqMessageEvent): boolean {
243
243
  const messageType =
244
244
  data.message_type ??
245
245
  (data.type === "group" || data.type === "private" ? data.type : undefined);
@@ -250,7 +250,7 @@ export function isIcqqGroupTempPrivateMessage(data: IcqqIpcMessageEvent): boolea
250
250
  }
251
251
 
252
252
  export function resolveChannelFromIcqqMessage(
253
- data: IcqqIpcMessageEvent,
253
+ data: IcqqMessageEvent,
254
254
  ): {
255
255
  channelType: "group" | "private";
256
256
  channelId: string;
@@ -287,7 +287,7 @@ export function resolveChannelFromIcqqMessage(
287
287
  }
288
288
 
289
289
  export function resolveIcqqInboundMessageId(
290
- data: IcqqIpcMessageEvent,
290
+ data: IcqqMessageEvent,
291
291
  channelId: string,
292
292
  ): { id: string; source: "message_id" | "msg_id" | "seq" | "synthetic" } {
293
293
  if (data.message_id != null && String(data.message_id) !== "") {
@@ -485,7 +485,7 @@ function looksLikeIcqqMessageSource(value: unknown): value is IcqqMessageSource
485
485
  return !!resolveQuoteIdFromIcqqSource(value);
486
486
  }
487
487
 
488
- /** IPC 载荷可能把 source 嵌在 data / event 等字段,深度查找 */
488
+ /** 载荷可能把 source 嵌在 data / event 等字段,深度查找 */
489
489
  export function findIcqqNestedMessageSource(
490
490
  root: unknown,
491
491
  maxDepth = MAX_SOURCE_WALK_DEPTH,
@@ -590,16 +590,16 @@ export function quotedPayloadFromIcqqSource(
590
590
  };
591
591
  }
592
592
 
593
- /** 从 IPC 事件顶层字段解析引用 message_id(与 message 段互补) */
593
+ /** 从事件顶层字段解析引用 message_id(与 message 段互补) */
594
594
  export function resolveIcqqQuoteIdFromEvent(
595
- data: IcqqIpcMessageEvent,
595
+ data: IcqqMessageEvent,
596
596
  ): string | undefined {
597
597
  const fromSource =
598
598
  resolveQuoteIdFromIcqqSource(data.source) ??
599
599
  resolveQuoteIdFromIcqqSource(findIcqqNestedMessageSource(data));
600
600
  if (fromSource) return fromSource;
601
601
 
602
- const ext = data as IcqqIpcMessageEvent & Record<string, unknown>;
602
+ const ext = data as IcqqMessageEvent & Record<string, unknown>;
603
603
  const candidates = [
604
604
  ext.reply,
605
605
  ext.quoted_message_id,
@@ -634,7 +634,7 @@ function mergeReplyFromRawMessage(
634
634
 
635
635
  function mergeReplyFromSource(
636
636
  content: MessageSegment[],
637
- data: IcqqIpcMessageEvent,
637
+ data: IcqqMessageEvent,
638
638
  ): MessageSegment[] {
639
639
  if (Message.quoteIdFromContent(content)) return content;
640
640
  const quoteId =
@@ -648,7 +648,7 @@ function mergeReplyFromSource(
648
648
  }
649
649
 
650
650
  export function resolveInboundContent(
651
- data: IcqqIpcMessageEvent,
651
+ data: IcqqMessageEvent,
652
652
  ): MessageSegment[] {
653
653
  const fromElements = icqqElementsToSegments(data.message);
654
654
  const raw = data.raw_message ?? "";
@@ -664,10 +664,10 @@ export function resolveInboundContent(
664
664
  }
665
665
 
666
666
  export function normalizeIcqqInboundMessage(
667
- data: IcqqIpcMessageEvent,
667
+ data: IcqqMessageEvent,
668
668
  ): NormalizedIcqqInbound | null {
669
669
  if (!isIcqqMessagePostType(data)) return null;
670
- const ext = data as IcqqIpcMessageEvent & Record<string, unknown>;
670
+ const ext = data as IcqqMessageEvent & Record<string, unknown>;
671
671
  const userIdNum = resolveIcqqInboundUserId(ext);
672
672
  if (userIdNum == null) return null;
673
673
  const { channelType, channelId, channelParentGroupId } = resolveChannelFromIcqqMessage(data);
package/src/icqq-inbox.ts CHANGED
@@ -1,12 +1,4 @@
1
- /**
2
- * icqq IPC request/notice 事件 → 统一收件箱行(unified_inbox_request / unified_inbox_notice)。
3
- *
4
- * daemon 经 IpcEvent 推送 icqq client.em 分发的全部事件(见 protocol.ts),
5
- * 好友/群请求(request.friend.add、request.group.add、request.group.invite)
6
- * 与通知(notice.friend.*、notice.group.*)随事件流实时到达,无需轮询;
7
- * endpoint 启动时补一次 GET_SYSTEM_MSG 拉取离线期间积存的待处理请求。
8
- */
9
- import type { IpcSystemMessage } from './types.js';
1
+ import type { SystemMessage } from './types.js';
10
2
 
11
3
  /** 行级公共前缀:adapter 槽 localName + endpoint live 名(uin)。 */
12
4
  export interface IcqqInboxBase {
@@ -107,7 +99,7 @@ export function buildIcqqInboxNoticeRow(
107
99
  * friend 列表项 type 语义即好友请求;group 列表项 type 为 add/invite(记 sub_type)。
108
100
  */
109
101
  export function buildIcqqSystemRequestRow(
110
- message: IpcSystemMessage,
102
+ message: SystemMessage,
111
103
  kind: 'friend' | 'group',
112
104
  base: IcqqInboxBase,
113
105
  ): Record<string, unknown> | null {
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Fallback types when `@icqqjs/icqq` peer is not installed.
3
+ * Prefer package types when the real dependency is present.
4
+ */
5
+ declare module '@icqqjs/icqq' {
6
+ export interface Config extends Record<string, unknown> {
7
+ sign_api_addr?: string;
8
+ ver?: string;
9
+ platform?: number;
10
+ auto_server?: boolean;
11
+ autoReconnect?: boolean;
12
+ }
13
+
14
+ export type Sendable = string | Record<string, unknown> | unknown[];
15
+
16
+ export interface ParsedGroupMessageId {
17
+ group_id: number;
18
+ user_id: number;
19
+ seq: number;
20
+ rand: number;
21
+ time: number;
22
+ pktnum: number;
23
+ }
24
+
25
+ export function parseGroupMessageId(msgid: string): ParsedGroupMessageId;
26
+
27
+ export interface GroupHandle {
28
+ setReaction(seq: number, emoji: string): Promise<unknown>;
29
+ delReaction(seq: number, reactionId: string): Promise<unknown>;
30
+ }
31
+
32
+ export interface FriendInfo {
33
+ user_id: number;
34
+ nickname: string;
35
+ remark?: string;
36
+ }
37
+
38
+ export interface GroupInfo {
39
+ group_id: number;
40
+ group_name: string;
41
+ }
42
+
43
+ export interface GroupMemberInfo {
44
+ user_id: number;
45
+ nickname?: string;
46
+ card?: string;
47
+ role?: string;
48
+ }
49
+
50
+ export class Client {
51
+ uin: number;
52
+ fl: Map<number, FriendInfo>;
53
+ gl: Map<number, GroupInfo>;
54
+ config: Config;
55
+ dir?: string;
56
+ status?: number;
57
+
58
+ constructor(uin?: number | Config, config?: Config);
59
+
60
+ on(event: string, listener: (...args: unknown[]) => void): this;
61
+ off(event: string, listener?: (...args: unknown[]) => void): this;
62
+ emit(event: string, ...args: unknown[]): boolean;
63
+
64
+ login(password?: string | number): Promise<void>;
65
+ logout?(keepalive?: boolean): Promise<void>;
66
+ terminate(): void;
67
+
68
+ sendPrivateMsg(userId: number, message: Sendable): Promise<{ message_id?: unknown }>;
69
+ sendGroupMsg(groupId: number, message: Sendable): Promise<{ message_id?: unknown }>;
70
+ sendTempMsg(groupId: number, userId: number, message: Sendable): Promise<{ message_id?: unknown }>;
71
+ sendGuildMsg(guildId: string, channelId: string, message: Sendable): Promise<{ message_id?: unknown }>;
72
+
73
+ deleteMsg(messageId: string): Promise<boolean>;
74
+ getSystemMsg(): Promise<unknown[]>;
75
+ setFriendAddRequest(flag: string, approve: boolean, remark?: string): Promise<boolean>;
76
+ setGroupAddRequest(flag: string, approve: boolean, reason?: string): Promise<boolean>;
77
+ deleteFriend(userId: number): Promise<boolean>;
78
+ setGroupKick(groupId: number, userId: number): Promise<boolean>;
79
+ setGroupBan(groupId: number, userId: number, duration: number): Promise<boolean>;
80
+ setGroupAdmin(groupId: number, userId: number, enabled: boolean): Promise<boolean>;
81
+ getGroupMemberList(groupId: number): Promise<Map<number, GroupMemberInfo>>;
82
+
83
+ getGuildList(): Array<{ guild_id: string; guild_name: string }>;
84
+ getChannelList(guildId: string): Array<{ guild_id: string; channel_id: string; channel_name: string }>;
85
+
86
+ pickGroup(groupId: number): GroupHandle;
87
+ }
88
+ }
package/src/index.ts CHANGED
@@ -4,24 +4,18 @@ export {
4
4
  formatOutboundBody,
5
5
  icqqInboundConversation,
6
6
  icqqOutboundTarget,
7
- resolveIcqqConfig,
7
+ type ActionResult,
8
8
  type IcqqAdapterConfig,
9
9
  type IcqqInboundMessage,
10
10
  type IcqqWireSegment,
11
- type IpcEvent,
12
- type IpcMessage,
13
- type IpcRequest,
14
- type IpcResponse,
15
11
  type ParsedIcqqSendTarget,
16
12
  type ResolvedIcqqConfig,
17
13
  } from './protocol.js';
18
14
 
19
15
  export {
20
- IcqqIpcEndpoint,
21
- type CreateIcqqIpc,
16
+ IcqqEndpoint,
22
17
  type IcqqEndpointOptions,
23
18
  type IcqqInboxHooks,
24
- type IcqqIpcTransport,
25
19
  } from './endpoint.js';
26
20
 
27
21
  export * from './types.js';
@@ -34,7 +28,6 @@ export {
34
28
  type IcqqAgentEndpoint,
35
29
  } from './icqq-agent-deps.js';
36
30
 
37
- export { IpcClient } from './ipc-client.js';
38
31
  export {
39
32
  resolveIcqqInboundMessageId,
40
33
  } from './icqq-inbound.js';
@@ -34,24 +34,19 @@ function spoolBase64ToFile(
34
34
  return filePath;
35
35
  }
36
36
 
37
- /**
38
- * 出站媒体模式:file=本机落盘后 CQ [image:path](与 icqq 同机时可用);
39
- * base64=保留 MediaRef base64,CQ 编码为 [image:base64://...] 经 IPC 交给守护进程解码(异机 RPC 默认)。
40
- */
41
37
  export function resolveIcqqOutboundMediaMode(
42
- config: Pick<IcqqEndpointConfig, "rpc" | "outboundMedia">,
38
+ config: Pick<IcqqEndpointConfig, "outboundMedia">,
43
39
  ): IcqqOutboundMediaMode {
44
40
  if (config.outboundMedia === "base64" || config.outboundMedia === "file") {
45
41
  return config.outboundMedia;
46
42
  }
47
- return config.rpc ? "base64" : "file";
43
+ return "file";
48
44
  }
49
45
 
50
46
  /**
51
47
  * file 模式:将 canonical MediaRef(`data.media`,kind=base64)物化为本机路径,
52
- * 并把 media 改写为 path 引用(与 icqq 同机时守护进程直接读盘)。
53
- * base64 模式:不改动(由 toCqString/formatOutboundBody 生成 base64://,供异机守护进程解码)。
54
- * 无 canonical MediaRef 的段原样保留(不可投递,由下游 CQ 序列化 warn + 丢弃)。
48
+ * 并把 media 改写为 path 引用(icqq 客户端直接读盘)。
49
+ * base64 模式:不改动(由 toCqString/formatOutboundBody 生成 base64://)。
55
50
  */
56
51
  export function materializeOutboundBase64(
57
52
  content: SendContent,