@zhin.js/adapter-icqq 8.0.9 → 8.0.11
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 +27 -0
- package/README.md +36 -28
- package/adapters/icqq.js +6 -11
- package/adapters/icqq.ts +7 -16
- package/agent/PERMITS.md +2 -0
- package/lib/agent-prompt.js +17 -22
- package/lib/cq-message.d.ts +2 -2
- package/lib/cq-message.js +2 -2
- package/lib/endpoint.d.ts +6 -76
- package/lib/endpoint.js +348 -507
- package/lib/forward-msg.d.ts +7 -8
- package/lib/forward-msg.js +11 -24
- package/lib/icqq-agent-deps.d.ts +3 -15
- package/lib/icqq-agent-deps.js +0 -5
- package/lib/icqq-endpoint-commands.js +6 -15
- package/lib/icqq-guild.d.ts +15 -11
- package/lib/icqq-guild.js +9 -17
- package/lib/icqq-inbound.d.ts +20 -20
- package/lib/icqq-inbound.js +9 -9
- package/lib/icqq-inbox.d.ts +2 -10
- package/lib/icqq-runtime-state.js +1 -1
- package/lib/index.d.ts +2 -3
- package/lib/index.js +2 -3
- package/lib/outbound-media.d.ts +3 -8
- package/lib/outbound-media.js +3 -8
- package/lib/protocol.d.ts +38 -57
- package/lib/protocol.js +25 -22
- package/lib/types.d.ts +4 -40
- package/lib/types.js +0 -5
- package/package.json +22 -13
- package/plugin.js +8 -3
- package/schema.json +119 -61
- package/src/agent-prompt.ts +17 -22
- package/src/cq-message.ts +2 -2
- package/src/endpoint.ts +360 -602
- package/src/forward-msg.ts +12 -26
- package/src/icqq-agent-deps.ts +3 -17
- package/src/icqq-endpoint-commands.ts +6 -15
- package/src/icqq-guild.ts +16 -25
- package/src/icqq-inbound.ts +26 -26
- package/src/icqq-inbox.ts +2 -10
- package/src/icqq-runtime-state.ts +1 -1
- package/src/index.ts +2 -9
- package/src/outbound-media.ts +4 -9
- package/src/protocol.ts +61 -67
- package/src/types.ts +4 -41
- package/tools/announce.js +19 -0
- package/tools/announce.ts +18 -0
- package/tools/essence.js +24 -0
- package/{agent/tools → tools}/essence.ts +8 -8
- package/tools/friend_list.js +20 -0
- package/{agent/tools → tools}/friend_list.ts +7 -7
- package/tools/group_files.js +25 -0
- package/tools/group_files.ts +23 -0
- package/tools/group_invite.js +25 -0
- package/tools/group_invite.ts +22 -0
- package/tools/group_kick.js +28 -0
- package/tools/group_kick.ts +24 -0
- package/tools/group_list.js +21 -0
- package/{agent/tools → tools}/group_list.ts +7 -7
- package/tools/group_sign.js +21 -0
- package/tools/group_sign.ts +19 -0
- package/tools/list_muted.js +20 -0
- package/tools/list_muted.ts +19 -0
- package/tools/poke.js +19 -0
- package/tools/poke.ts +18 -0
- package/tools/send_user_like.js +28 -0
- package/tools/send_user_like.ts +25 -0
- package/tools/set_anonymous.js +23 -0
- package/tools/set_anonymous.ts +21 -0
- package/tools/set_mute.js +26 -0
- package/tools/set_mute.ts +23 -0
- package/tools/set_title.js +27 -0
- package/tools/set_title.ts +24 -0
- package/tools/user_info.js +17 -0
- package/tools/user_info.ts +16 -0
- package/agent/tools/announce.ts +0 -21
- package/agent/tools/group_files.ts +0 -27
- package/agent/tools/group_invite.ts +0 -20
- package/agent/tools/group_sign.ts +0 -19
- package/agent/tools/list_muted.ts +0 -22
- package/agent/tools/poke.ts +0 -20
- package/agent/tools/send_user_like.ts +0 -22
- package/agent/tools/set_anonymous.ts +0 -22
- package/agent/tools/set_title.ts +0 -25
- package/lib/ipc-client.d.ts +0 -73
- package/lib/ipc-client.js +0 -358
- package/src/ipc-client.ts +0 -417
package/lib/forward-msg.d.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 合并转发(聊天记录)解析:从 json/xml 提取 resid,经 IPC get_forward_msg 拉取正文。
|
|
3
|
-
*/
|
|
4
1
|
import { type MessageSegment, type QuotedMessagePayload } from 'zhin.js';
|
|
5
|
-
import type { IpcClient } from "./ipc-client.js";
|
|
6
2
|
import { type IcqqMessageElement } from "./icqq-inbound.js";
|
|
7
3
|
/** 从 QQ 合并转发 json 元素提取 resid */
|
|
8
4
|
export declare function extractForwardResidFromJsonElement(el: IcqqMessageElement): string | undefined;
|
|
@@ -16,11 +12,14 @@ export declare function extractForwardResidFromGetMsgElements(elements: IcqqMess
|
|
|
16
12
|
export declare function extractForwardResidFromGetMsg(data: unknown): string | undefined;
|
|
17
13
|
export declare function extractForwardResidFromSegments(content: MessageSegment[]): string | undefined;
|
|
18
14
|
export declare function extractForwardResidFromPayload(payload: QuotedMessagePayload): string | undefined;
|
|
19
|
-
/** 在 get_msg
|
|
15
|
+
/** 在 get_msg 原始对象里深度搜索 resid */
|
|
20
16
|
export declare function extractForwardResidDeep(root: unknown, maxDepth?: number): string | undefined;
|
|
21
17
|
export declare function isForwardPlaceholderPayload(payload: QuotedMessagePayload): boolean;
|
|
22
18
|
/** 将 get_forward_msg 响应格式化为可读聊天记录 */
|
|
23
19
|
export declare function formatForwardMsgResponse(data: unknown): string;
|
|
24
|
-
export declare function fetchForwardMsgText(
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
export declare function fetchForwardMsgText(client: {
|
|
21
|
+
getForwardMsg(resId: string, fileName?: string): Promise<unknown[]>;
|
|
22
|
+
}, id: string): Promise<string>;
|
|
23
|
+
export declare function enrichQuotedPayloadWithForward(client: {
|
|
24
|
+
getForwardMsg(resId: string, fileName?: string): Promise<unknown[]>;
|
|
25
|
+
} | null | undefined, payload: QuotedMessagePayload, rawData?: unknown): Promise<QuotedMessagePayload>;
|
package/lib/forward-msg.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 合并转发(聊天记录)解析:从 json/xml 提取 resid,经 IPC get_forward_msg 拉取正文。
|
|
3
|
-
*/
|
|
4
1
|
import { segment } from 'zhin.js';
|
|
5
|
-
import { Actions } from "./protocol.js";
|
|
6
2
|
import { icqqElementsToSegments, } from "./icqq-inbound.js";
|
|
7
3
|
import { parseCqMessage } from "./cq-message.js";
|
|
8
4
|
const RESID_IN_XML_RE = /(?:m_resid|resid|fileid|res_id)=["']?([A-Za-z0-9+/=_.-]{8,})/i;
|
|
@@ -191,7 +187,7 @@ export function extractForwardResidFromPayload(payload) {
|
|
|
191
187
|
}
|
|
192
188
|
return undefined;
|
|
193
189
|
}
|
|
194
|
-
/** 在 get_msg
|
|
190
|
+
/** 在 get_msg 原始对象里深度搜索 resid */
|
|
195
191
|
export function extractForwardResidDeep(root, maxDepth = 14) {
|
|
196
192
|
const seen = new Set();
|
|
197
193
|
function walk(node, depth) {
|
|
@@ -336,22 +332,14 @@ export function formatForwardMsgResponse(data) {
|
|
|
336
332
|
}
|
|
337
333
|
return lines.join("\n\n");
|
|
338
334
|
}
|
|
339
|
-
export async function fetchForwardMsgText(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
{ resid: id },
|
|
344
|
-
{ res_id: id },
|
|
345
|
-
{ msg_id: id },
|
|
346
|
-
];
|
|
347
|
-
for (const params of attempts) {
|
|
348
|
-
const resp = await ipc.request(Actions.GET_FORWARD_MSG, params);
|
|
349
|
-
if (!resp.ok)
|
|
350
|
-
continue;
|
|
351
|
-
const text = formatForwardMsgResponse(resp.data);
|
|
335
|
+
export async function fetchForwardMsgText(client, id) {
|
|
336
|
+
try {
|
|
337
|
+
const messages = await client.getForwardMsg(id);
|
|
338
|
+
const text = formatForwardMsgResponse(messages);
|
|
352
339
|
if (text.trim())
|
|
353
340
|
return text.trim();
|
|
354
341
|
}
|
|
342
|
+
catch { /* ignore */ }
|
|
355
343
|
return "";
|
|
356
344
|
}
|
|
357
345
|
function appendForwardBlock(payload, block) {
|
|
@@ -367,19 +355,18 @@ function appendForwardBlock(payload, block) {
|
|
|
367
355
|
content: prev ? `${prev}\n\n${block}` : block,
|
|
368
356
|
};
|
|
369
357
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (!ipc || hasMergedForwardBlock(payload))
|
|
358
|
+
export async function enrichQuotedPayloadWithForward(client, payload, rawData) {
|
|
359
|
+
if (!client || hasMergedForwardBlock(payload))
|
|
373
360
|
return payload;
|
|
374
361
|
const resid = extractForwardResidFromPayload(payload) ??
|
|
375
|
-
(
|
|
376
|
-
(
|
|
362
|
+
(rawData ? extractForwardResidFromGetMsg(rawData) : undefined) ??
|
|
363
|
+
(rawData ? extractForwardResidDeep(rawData) : undefined);
|
|
377
364
|
const fetchIds = [
|
|
378
365
|
resid,
|
|
379
366
|
isForwardPlaceholderPayload(payload) ? payload.messageId : undefined,
|
|
380
367
|
].filter((v, i, a) => !!v && a.indexOf(v) === i);
|
|
381
368
|
for (const fetchId of fetchIds) {
|
|
382
|
-
const forwardText = await fetchForwardMsgText(
|
|
369
|
+
const forwardText = await fetchForwardMsgText(client, fetchId);
|
|
383
370
|
if (!forwardText.trim())
|
|
384
371
|
continue;
|
|
385
372
|
const block = `[Merged chat history — id ${fetchId}]\n${forwardText.trim()}`;
|
package/lib/icqq-agent-deps.d.ts
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*/
|
|
5
|
-
import type { IpcFriendInfo, IpcGroupInfo } from './types.js';
|
|
6
|
-
import type { IpcResponse } from './protocol.js';
|
|
7
|
-
export interface IcqqAgentEndpoint {
|
|
8
|
-
readonly name: string;
|
|
9
|
-
readonly ipc: {
|
|
10
|
-
request(action: string, params?: Record<string, unknown>): Promise<IpcResponse>;
|
|
11
|
-
};
|
|
12
|
-
readonly friends: Map<number, IpcFriendInfo>;
|
|
13
|
-
readonly groups: Map<number, IpcGroupInfo>;
|
|
14
|
-
request(action: string, params?: Record<string, unknown>): Promise<IpcResponse>;
|
|
1
|
+
import type { Client } from '@icqqjs/icqq';
|
|
2
|
+
export interface IcqqAgentEndpoint extends Client {
|
|
3
|
+
readonly endpointName: string;
|
|
15
4
|
}
|
|
16
5
|
export interface IcqqAgentDeps {
|
|
17
6
|
getEndpoint: (endpointKey: string) => IcqqAgentEndpoint;
|
|
@@ -20,6 +9,5 @@ export interface IcqqAgentDeps {
|
|
|
20
9
|
};
|
|
21
10
|
}
|
|
22
11
|
export declare function registerIcqqAgentEndpoint(endpointKey: string, endpoint: IcqqAgentEndpoint): () => void;
|
|
23
|
-
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
24
12
|
export declare function setIcqqAgentDeps(deps: IcqqAgentDeps | null): void;
|
|
25
13
|
export declare function getIcqqAgentDeps(): IcqqAgentDeps;
|
package/lib/icqq-agent-deps.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent tool deps for icqq.
|
|
3
|
-
* Endpoints register themselves on start; tools look up by config name / QQ uin.
|
|
4
|
-
*/
|
|
5
1
|
const endpoints = new Map();
|
|
6
2
|
let override = null;
|
|
7
3
|
export function registerIcqqAgentEndpoint(endpointKey, endpoint) {
|
|
@@ -12,7 +8,6 @@ export function registerIcqqAgentEndpoint(endpointKey, endpoint) {
|
|
|
12
8
|
}
|
|
13
9
|
};
|
|
14
10
|
}
|
|
15
|
-
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
16
11
|
export function setIcqqAgentDeps(deps) {
|
|
17
12
|
override = deps;
|
|
18
13
|
}
|
|
@@ -1,24 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
import { addEndpointToConfig, createEndpointCommands } from '@zhin.js/adapter';
|
|
11
|
-
import { defineCommand } from '@zhin.js/command';
|
|
1
|
+
import { addEndpointToConfig, createEndpointCommands } from 'zhin.js/adapter';
|
|
2
|
+
import { defineCommand } from 'zhin.js/command';
|
|
12
3
|
import { icqqRuntimeStateToken } from './icqq-runtime-state.js';
|
|
13
4
|
export const icqqEndpointCommands = createEndpointCommands({
|
|
14
5
|
adapterKey: 'icqq',
|
|
15
6
|
adapterDisplayName: 'ICQQ',
|
|
16
7
|
running: (use) => use(icqqRuntimeStateToken).endpoints.values(),
|
|
17
|
-
describeEntry: (
|
|
18
|
-
addDescription: '登记 ICQQ endpoint
|
|
8
|
+
describeEntry: () => 'direct(直连 @icqqjs/icqq)',
|
|
9
|
+
addDescription: '登记 ICQQ endpoint(重启 zhin 生效)',
|
|
19
10
|
bindFlow: ({ id }) => {
|
|
20
11
|
if (!id) {
|
|
21
|
-
return '用法:icqq.endpoint add <uin>(uin 为纯数字 QQ
|
|
12
|
+
return '用法:icqq.endpoint add <uin>(uin 为纯数字 QQ 号)';
|
|
22
13
|
}
|
|
23
14
|
if (!/^\d+$/.test(id)) {
|
|
24
15
|
return 'icqq endpoint 名必须是纯数字 QQ 号(uin)';
|
|
@@ -26,7 +17,7 @@ export const icqqEndpointCommands = createEndpointCommands({
|
|
|
26
17
|
try {
|
|
27
18
|
const filePath = addEndpointToConfig('icqq', { id });
|
|
28
19
|
return (`✅ endpoint「${id}」已追加到 ${filePath} 的 plugins.icqq.endpoints。\n` +
|
|
29
|
-
|
|
20
|
+
`重启 zhin 后生效。`);
|
|
30
21
|
}
|
|
31
22
|
catch (error) {
|
|
32
23
|
return `添加失败:${error instanceof Error ? error.message : String(error)}`;
|
package/lib/icqq-guild.d.ts
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
* ICQQ guild (QQ 频道) catalog sync, inbound normalize, Console channel list.
|
|
3
3
|
*/
|
|
4
4
|
import { type MessageSegment } from "zhin.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import type { GuildMessageEventData } from "./protocol.js";
|
|
6
|
+
export interface IcqqGuildClient {
|
|
7
|
+
getGuildList(): Array<{
|
|
8
|
+
guild_id: string;
|
|
9
|
+
guild_name: string;
|
|
10
|
+
}>;
|
|
11
|
+
getChannelList(guildId: string): Array<{
|
|
12
|
+
guild_id: string;
|
|
13
|
+
channel_id: string;
|
|
14
|
+
channel_name: string;
|
|
11
15
|
}>;
|
|
12
16
|
}
|
|
13
17
|
export interface IcqqGuildChannelEntry {
|
|
@@ -27,14 +31,14 @@ export interface NormalizedIcqqGuildInbound {
|
|
|
27
31
|
content: MessageSegment[];
|
|
28
32
|
rawMessage: string;
|
|
29
33
|
timestampMs: number;
|
|
30
|
-
raw:
|
|
34
|
+
raw: GuildMessageEventData;
|
|
31
35
|
}
|
|
32
36
|
export declare class IcqqGuildCatalog {
|
|
33
37
|
private readonly channels;
|
|
34
38
|
private readonly guilds;
|
|
35
39
|
clear(): void;
|
|
36
|
-
syncAll(
|
|
37
|
-
upsertFromInbound(data:
|
|
40
|
+
syncAll(client: IcqqGuildClient): Promise<void>;
|
|
41
|
+
upsertFromInbound(data: GuildMessageEventData): void;
|
|
38
42
|
getGuildChannelList(): Array<{
|
|
39
43
|
id: string;
|
|
40
44
|
name: string;
|
|
@@ -49,5 +53,5 @@ export declare class IcqqGuildCatalog {
|
|
|
49
53
|
parentName?: string;
|
|
50
54
|
};
|
|
51
55
|
}
|
|
52
|
-
export declare function
|
|
53
|
-
export declare function normalizeIcqqGuildInboundMessage(data:
|
|
56
|
+
export declare function isIcqqGuildEvent(eventName: string | undefined): boolean;
|
|
57
|
+
export declare function normalizeIcqqGuildInboundMessage(data: GuildMessageEventData & Record<string, unknown>): NormalizedIcqqGuildInbound | null;
|
package/lib/icqq-guild.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* ICQQ guild (QQ 频道) catalog sync, inbound normalize, Console channel list.
|
|
3
3
|
*/
|
|
4
4
|
import { toCanonicalSegments } from "zhin.js";
|
|
5
|
-
import { Actions } from "./protocol.js";
|
|
6
5
|
import { parseCqMessage } from "./cq-message.js";
|
|
7
6
|
export class IcqqGuildCatalog {
|
|
8
7
|
channels = new Map();
|
|
@@ -11,27 +10,20 @@ export class IcqqGuildCatalog {
|
|
|
11
10
|
this.channels.clear();
|
|
12
11
|
this.guilds.clear();
|
|
13
12
|
}
|
|
14
|
-
async syncAll(
|
|
15
|
-
const
|
|
16
|
-
if (!listResp.ok)
|
|
17
|
-
return;
|
|
18
|
-
const guildRows = Array.isArray(listResp.data) ? listResp.data : [];
|
|
13
|
+
async syncAll(client) {
|
|
14
|
+
const guildRows = client.getGuildList();
|
|
19
15
|
for (const row of guildRows) {
|
|
20
|
-
const
|
|
21
|
-
const guild_id = String(rec.guild_id ?? rec.id ?? "");
|
|
16
|
+
const guild_id = String(row.guild_id ?? "");
|
|
22
17
|
if (!guild_id)
|
|
23
18
|
continue;
|
|
24
|
-
const guild_name = String(
|
|
19
|
+
const guild_name = String(row.guild_name ?? guild_id);
|
|
25
20
|
this.guilds.set(guild_id, { guild_id, guild_name });
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
for (const ch of chResp.data) {
|
|
30
|
-
const chRec = ch;
|
|
31
|
-
const channel_id = String(chRec.channel_id ?? chRec.id ?? "");
|
|
21
|
+
const channels = client.getChannelList(guild_id);
|
|
22
|
+
for (const ch of channels) {
|
|
23
|
+
const channel_id = String(ch.channel_id ?? "");
|
|
32
24
|
if (!channel_id)
|
|
33
25
|
continue;
|
|
34
|
-
const channel_name = String(
|
|
26
|
+
const channel_name = String(ch.channel_name ?? channel_id);
|
|
35
27
|
this.channels.set(channel_id, {
|
|
36
28
|
channel_id,
|
|
37
29
|
channel_name,
|
|
@@ -79,7 +71,7 @@ export class IcqqGuildCatalog {
|
|
|
79
71
|
return {};
|
|
80
72
|
}
|
|
81
73
|
}
|
|
82
|
-
export function
|
|
74
|
+
export function isIcqqGuildEvent(eventName) {
|
|
83
75
|
return typeof eventName === "string" && eventName.startsWith("message.guild");
|
|
84
76
|
}
|
|
85
77
|
export function normalizeIcqqGuildInboundMessage(data) {
|
package/lib/icqq-inbound.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* icqq
|
|
2
|
+
* icqq 入站事件归一化(post_type / message_type / message 段)
|
|
3
3
|
*/
|
|
4
4
|
import { type MessageSegment, type QuotedMessagePayload } from "zhin.js";
|
|
5
5
|
import type { GroupRole } from "./types.js";
|
|
6
6
|
/** 守护进程推送的通用事件壳 */
|
|
7
|
-
export interface
|
|
7
|
+
export interface IcqqEventBase {
|
|
8
8
|
post_type?: string;
|
|
9
9
|
self_id?: number;
|
|
10
10
|
}
|
|
11
11
|
/** post_type=message 时的消息体(字段随 icqqjs/cli 演进,未知字段保留) */
|
|
12
|
-
export interface
|
|
12
|
+
export interface IcqqMessageEvent extends IcqqEventBase {
|
|
13
13
|
post_type: "message";
|
|
14
14
|
message_id?: string;
|
|
15
15
|
msg_id?: string;
|
|
@@ -28,7 +28,7 @@ export interface IcqqIpcMessageEvent extends IcqqIpcEventBase {
|
|
|
28
28
|
card?: string;
|
|
29
29
|
/** 群临时会话来源群号 */
|
|
30
30
|
group_id?: number;
|
|
31
|
-
/**
|
|
31
|
+
/** 群成员角色:owner | admin | member */
|
|
32
32
|
role?: GroupRole | string;
|
|
33
33
|
};
|
|
34
34
|
from_id?: number;
|
|
@@ -36,7 +36,7 @@ export interface IcqqIpcMessageEvent extends IcqqIpcEventBase {
|
|
|
36
36
|
group_id?: number;
|
|
37
37
|
to_id?: number;
|
|
38
38
|
nickname?: string;
|
|
39
|
-
/**
|
|
39
|
+
/** 旧版仅有 type 无 message_type */
|
|
40
40
|
type?: "group" | "private";
|
|
41
41
|
/** oicq Message#source:被引用回复的源消息 */
|
|
42
42
|
source?: IcqqMessageSource;
|
|
@@ -80,14 +80,14 @@ export interface NormalizedIcqqInbound {
|
|
|
80
80
|
channelParentGroupId?: string;
|
|
81
81
|
userId: string;
|
|
82
82
|
nickname: string;
|
|
83
|
-
/** 群聊时来自
|
|
83
|
+
/** 群聊时来自 sender.role(私聊无) */
|
|
84
84
|
senderRole?: GroupRole;
|
|
85
85
|
content: MessageSegment[];
|
|
86
86
|
rawMessage: string;
|
|
87
87
|
timestampMs: number;
|
|
88
|
-
raw:
|
|
88
|
+
raw: IcqqMessageEvent;
|
|
89
89
|
}
|
|
90
|
-
/** 从
|
|
90
|
+
/** 从 sender.role 解析群成员角色(供 Message.$sender → resolveSenderRoles) */
|
|
91
91
|
export declare function parseIcqqSenderGroupRole(sender: {
|
|
92
92
|
role?: unknown;
|
|
93
93
|
} | undefined): GroupRole | undefined;
|
|
@@ -101,36 +101,36 @@ export declare function isIcqqBotMentioned(input: {
|
|
|
101
101
|
readonly content?: readonly MessageSegment[] | null;
|
|
102
102
|
readonly rawMessage?: string;
|
|
103
103
|
}): boolean;
|
|
104
|
-
/**
|
|
105
|
-
export declare function
|
|
104
|
+
/** 从事件壳中取出 OneBot/icqq 载荷(兼容 data 嵌套或字段在根级) */
|
|
105
|
+
export declare function unwrapIcqqEventPayload(event: {
|
|
106
106
|
data?: unknown;
|
|
107
107
|
[key: string]: unknown;
|
|
108
108
|
}): unknown;
|
|
109
109
|
export declare function resolveIcqqInboundUserId(data: Record<string, unknown>): number | undefined;
|
|
110
|
-
export declare function isIcqqMessagePostType(data: unknown): data is
|
|
111
|
-
export declare function shouldSkipSelfInboundMessage(data:
|
|
110
|
+
export declare function isIcqqMessagePostType(data: unknown): data is IcqqMessageEvent;
|
|
111
|
+
export declare function shouldSkipSelfInboundMessage(data: IcqqMessageEvent): boolean;
|
|
112
112
|
/** 群临时会话私聊:icqq message_type=private & sub_type=group */
|
|
113
|
-
export declare function isIcqqGroupTempPrivateMessage(data:
|
|
114
|
-
export declare function resolveChannelFromIcqqMessage(data:
|
|
113
|
+
export declare function isIcqqGroupTempPrivateMessage(data: IcqqMessageEvent): boolean;
|
|
114
|
+
export declare function resolveChannelFromIcqqMessage(data: IcqqMessageEvent): {
|
|
115
115
|
channelType: "group" | "private";
|
|
116
116
|
channelId: string;
|
|
117
117
|
channelParentGroupId?: string;
|
|
118
118
|
};
|
|
119
|
-
export declare function resolveIcqqInboundMessageId(data:
|
|
119
|
+
export declare function resolveIcqqInboundMessageId(data: IcqqMessageEvent, channelId: string): {
|
|
120
120
|
id: string;
|
|
121
121
|
source: "message_id" | "msg_id" | "seq" | "synthetic";
|
|
122
122
|
};
|
|
123
123
|
export declare function icqqElementsToSegments(elements: IcqqMessageElement[] | undefined): MessageSegment[] | null;
|
|
124
|
-
/**
|
|
124
|
+
/** 载荷可能把 source 嵌在 data / event 等字段,深度查找 */
|
|
125
125
|
export declare function findIcqqNestedMessageSource(root: unknown, maxDepth?: number): IcqqMessageSource | undefined;
|
|
126
126
|
/** 从 oicq MessageEvent.source 解析被引用消息的 message_id */
|
|
127
127
|
export declare function resolveQuoteIdFromIcqqSource(source: unknown): string | undefined;
|
|
128
128
|
/** 将 source 转为 QuotedMessagePayload(供 $getMsg 缓存,避免重复拉取) */
|
|
129
129
|
export declare function quotedPayloadFromIcqqSource(source: unknown): QuotedMessagePayload | null;
|
|
130
|
-
/**
|
|
131
|
-
export declare function resolveIcqqQuoteIdFromEvent(data:
|
|
132
|
-
export declare function resolveInboundContent(data:
|
|
133
|
-
export declare function normalizeIcqqInboundMessage(data:
|
|
130
|
+
/** 从事件顶层字段解析引用 message_id(与 message 段互补) */
|
|
131
|
+
export declare function resolveIcqqQuoteIdFromEvent(data: IcqqMessageEvent): string | undefined;
|
|
132
|
+
export declare function resolveInboundContent(data: IcqqMessageEvent): MessageSegment[];
|
|
133
|
+
export declare function normalizeIcqqInboundMessage(data: IcqqMessageEvent): NormalizedIcqqInbound | null;
|
|
134
134
|
/** 按 message_id 去重(多路 subscribe 会收到同一消息多次) */
|
|
135
135
|
export declare class InboundMessageDeduper {
|
|
136
136
|
private readonly seen;
|
package/lib/icqq-inbound.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* icqq
|
|
2
|
+
* icqq 入站事件归一化(post_type / message_type / message 段)
|
|
3
3
|
*/
|
|
4
4
|
import { Message, toCanonicalSegments } from "zhin.js";
|
|
5
5
|
import { parseCqMessage, icqqMediaRefFromString } from "./cq-message.js";
|
|
6
6
|
import { extractForwardResidFromJsonElement } from "./forward-msg.js";
|
|
7
7
|
const DEDUPE_TTL_MS = 120_000;
|
|
8
8
|
const ICQQ_GROUP_ROLES = new Set(["owner", "admin", "member"]);
|
|
9
|
-
/** 从
|
|
9
|
+
/** 从 sender.role 解析群成员角色(供 Message.$sender → resolveSenderRoles) */
|
|
10
10
|
export function parseIcqqSenderGroupRole(sender) {
|
|
11
11
|
const raw = sender?.role;
|
|
12
12
|
if (typeof raw !== "string" || !raw.trim())
|
|
@@ -49,12 +49,12 @@ function isMessagePostType(postType) {
|
|
|
49
49
|
// 如 message.private / message.group
|
|
50
50
|
return postType.startsWith("message.");
|
|
51
51
|
}
|
|
52
|
-
/**
|
|
53
|
-
export function
|
|
52
|
+
/** 从事件壳中取出 OneBot/icqq 载荷(兼容 data 嵌套或字段在根级) */
|
|
53
|
+
export function unwrapIcqqEventPayload(event) {
|
|
54
54
|
const nested = event.data;
|
|
55
55
|
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
56
56
|
const d = nested;
|
|
57
|
-
if (
|
|
57
|
+
if (isRecognizedIcqqPayload(d)) {
|
|
58
58
|
return nested;
|
|
59
59
|
}
|
|
60
60
|
for (const key of ["data", "detail", "payload", "event"]) {
|
|
@@ -65,12 +65,12 @@ export function unwrapIcqqIpcEventPayload(event) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
const { id: _id, event: _ev, data: _data, ok: _ok, ...rest } = event;
|
|
68
|
-
if (
|
|
68
|
+
if (isRecognizedIcqqPayload(rest)) {
|
|
69
69
|
return rest;
|
|
70
70
|
}
|
|
71
71
|
return nested;
|
|
72
72
|
}
|
|
73
|
-
function
|
|
73
|
+
function isRecognizedIcqqPayload(d) {
|
|
74
74
|
const pt = d.post_type;
|
|
75
75
|
if (typeof pt === "string") {
|
|
76
76
|
if (isMessagePostType(pt) ||
|
|
@@ -359,7 +359,7 @@ function looksLikeIcqqMessageSource(value) {
|
|
|
359
359
|
return false;
|
|
360
360
|
return !!resolveQuoteIdFromIcqqSource(value);
|
|
361
361
|
}
|
|
362
|
-
/**
|
|
362
|
+
/** 载荷可能把 source 嵌在 data / event 等字段,深度查找 */
|
|
363
363
|
export function findIcqqNestedMessageSource(root, maxDepth = MAX_SOURCE_WALK_DEPTH) {
|
|
364
364
|
const seen = new Set();
|
|
365
365
|
function walk(node, depth) {
|
|
@@ -453,7 +453,7 @@ export function quotedPayloadFromIcqqSource(source) {
|
|
|
453
453
|
time: typeof s.time === "number" ? s.time : undefined,
|
|
454
454
|
};
|
|
455
455
|
}
|
|
456
|
-
/**
|
|
456
|
+
/** 从事件顶层字段解析引用 message_id(与 message 段互补) */
|
|
457
457
|
export function resolveIcqqQuoteIdFromEvent(data) {
|
|
458
458
|
const fromSource = resolveQuoteIdFromIcqqSource(data.source) ??
|
|
459
459
|
resolveQuoteIdFromIcqqSource(findIcqqNestedMessageSource(data));
|
package/lib/icqq-inbox.d.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
|
/** 行级公共前缀:adapter 槽 localName + endpoint live 名(uin)。 */
|
|
11
3
|
export interface IcqqInboxBase {
|
|
12
4
|
readonly adapter: string;
|
|
@@ -30,4 +22,4 @@ export declare function buildIcqqInboxNoticeRow(payload: Record<string, unknown>
|
|
|
30
22
|
* GET_SYSTEM_MSG 返回项 → unified_inbox_request 行(启动时首次拉取)。
|
|
31
23
|
* friend 列表项 type 语义即好友请求;group 列表项 type 为 add/invite(记 sub_type)。
|
|
32
24
|
*/
|
|
33
|
-
export declare function buildIcqqSystemRequestRow(message:
|
|
25
|
+
export declare function buildIcqqSystemRequestRow(message: SystemMessage, kind: 'friend' | 'group', base: IcqqInboxBase): Record<string, unknown> | null;
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* ICQQ 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
3
|
* 由 plugin.ts setup() provide,adapter create 与 `icqq.endpoint` 命令共享(同一 owner generation)。
|
|
4
4
|
*/
|
|
5
|
-
import { defineEndpointRuntimeStateToken } from '
|
|
5
|
+
import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
|
|
6
6
|
export const icqqRuntimeStateToken = defineEndpointRuntimeStateToken('icqq');
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
export { Actions, formatInboundContent, formatOutboundBody, icqqInboundConversation, icqqOutboundTarget,
|
|
2
|
-
export {
|
|
1
|
+
export { Actions, formatInboundContent, formatOutboundBody, icqqInboundConversation, icqqOutboundTarget, type ActionResult, type IcqqAdapterConfig, type IcqqInboundMessage, type IcqqWireSegment, type ParsedIcqqSendTarget, type ResolvedIcqqConfig, } from './protocol.js';
|
|
2
|
+
export { IcqqEndpoint, type IcqqEndpointOptions, type IcqqInboxHooks, } from './endpoint.js';
|
|
3
3
|
export * from './types.js';
|
|
4
4
|
export { getIcqqAgentDeps, registerIcqqAgentEndpoint, setIcqqAgentDeps, type IcqqAgentDeps, type IcqqAgentEndpoint, } from './icqq-agent-deps.js';
|
|
5
|
-
export { IpcClient } from './ipc-client.js';
|
|
6
5
|
export { resolveIcqqInboundMessageId, } from './icqq-inbound.js';
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
export { Actions, formatInboundContent, formatOutboundBody, icqqInboundConversation, icqqOutboundTarget,
|
|
2
|
-
export {
|
|
1
|
+
export { Actions, formatInboundContent, formatOutboundBody, icqqInboundConversation, icqqOutboundTarget, } from './protocol.js';
|
|
2
|
+
export { IcqqEndpoint, } from './endpoint.js';
|
|
3
3
|
export * from './types.js';
|
|
4
4
|
export { getIcqqAgentDeps, registerIcqqAgentEndpoint, setIcqqAgentDeps, } from './icqq-agent-deps.js';
|
|
5
|
-
export { IpcClient } from './ipc-client.js';
|
|
6
5
|
export { resolveIcqqInboundMessageId, } from './icqq-inbound.js';
|
package/lib/outbound-media.d.ts
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import type { SendContent } from "zhin.js";
|
|
2
2
|
import type { IcqqEndpointConfig } from "./types.js";
|
|
3
3
|
export type IcqqOutboundMediaMode = "file" | "base64";
|
|
4
|
-
|
|
5
|
-
* 出站媒体模式:file=本机落盘后 CQ [image:path](与 icqq 同机时可用);
|
|
6
|
-
* base64=保留 MediaRef base64,CQ 编码为 [image:base64://...] 经 IPC 交给守护进程解码(异机 RPC 默认)。
|
|
7
|
-
*/
|
|
8
|
-
export declare function resolveIcqqOutboundMediaMode(config: Pick<IcqqEndpointConfig, "rpc" | "outboundMedia">): IcqqOutboundMediaMode;
|
|
4
|
+
export declare function resolveIcqqOutboundMediaMode(config: Pick<IcqqEndpointConfig, "outboundMedia">): IcqqOutboundMediaMode;
|
|
9
5
|
/**
|
|
10
6
|
* file 模式:将 canonical MediaRef(`data.media`,kind=base64)物化为本机路径,
|
|
11
|
-
* 并把 media 改写为 path
|
|
12
|
-
* base64 模式:不改动(由 toCqString/formatOutboundBody 生成 base64
|
|
13
|
-
* 无 canonical MediaRef 的段原样保留(不可投递,由下游 CQ 序列化 warn + 丢弃)。
|
|
7
|
+
* 并把 media 改写为 path 引用(icqq 客户端直接读盘)。
|
|
8
|
+
* base64 模式:不改动(由 toCqString/formatOutboundBody 生成 base64://)。
|
|
14
9
|
*/
|
|
15
10
|
export declare function materializeOutboundBase64(content: SendContent, mode?: IcqqOutboundMediaMode): SendContent;
|
package/lib/outbound-media.js
CHANGED
|
@@ -23,21 +23,16 @@ function spoolBase64ToFile(base64, mime, kind) {
|
|
|
23
23
|
fs.writeFileSync(filePath, Buffer.from(base64, "base64"));
|
|
24
24
|
return filePath;
|
|
25
25
|
}
|
|
26
|
-
/**
|
|
27
|
-
* 出站媒体模式:file=本机落盘后 CQ [image:path](与 icqq 同机时可用);
|
|
28
|
-
* base64=保留 MediaRef base64,CQ 编码为 [image:base64://...] 经 IPC 交给守护进程解码(异机 RPC 默认)。
|
|
29
|
-
*/
|
|
30
26
|
export function resolveIcqqOutboundMediaMode(config) {
|
|
31
27
|
if (config.outboundMedia === "base64" || config.outboundMedia === "file") {
|
|
32
28
|
return config.outboundMedia;
|
|
33
29
|
}
|
|
34
|
-
return
|
|
30
|
+
return "file";
|
|
35
31
|
}
|
|
36
32
|
/**
|
|
37
33
|
* file 模式:将 canonical MediaRef(`data.media`,kind=base64)物化为本机路径,
|
|
38
|
-
* 并把 media 改写为 path
|
|
39
|
-
* base64 模式:不改动(由 toCqString/formatOutboundBody 生成 base64
|
|
40
|
-
* 无 canonical MediaRef 的段原样保留(不可投递,由下游 CQ 序列化 warn + 丢弃)。
|
|
34
|
+
* 并把 media 改写为 path 引用(icqq 客户端直接读盘)。
|
|
35
|
+
* base64 模式:不改动(由 toCqString/formatOutboundBody 生成 base64://)。
|
|
41
36
|
*/
|
|
42
37
|
export function materializeOutboundBase64(content, mode = "file") {
|
|
43
38
|
if (mode === "base64")
|