@zhin.js/adapter-icqq 6.0.2 → 7.0.0
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 +65 -0
- package/README.md +48 -435
- package/adapters/icqq.ts +92 -0
- package/agent/tools/announce.ts +2 -2
- package/agent/tools/essence.ts +2 -2
- package/agent/tools/friend_list.ts +2 -2
- package/agent/tools/group_files.ts +2 -2
- package/agent/tools/group_invite.ts +2 -2
- package/agent/tools/group_list.ts +2 -2
- package/agent/tools/group_sign.ts +2 -2
- package/agent/tools/list_muted.ts +2 -2
- package/agent/tools/poke.ts +2 -2
- package/agent/tools/send_user_like.ts +2 -2
- package/agent/tools/set_anonymous.ts +2 -2
- package/agent/tools/set_title.ts +2 -2
- package/lib/agent-prompt.d.ts +2 -0
- package/lib/{src/agent-prompt.js → agent-prompt.js} +0 -1
- package/lib/cq-message.d.ts +9 -0
- package/lib/{src/cq-message.js → cq-message.js} +0 -1
- package/lib/endpoint.d.ts +101 -0
- package/lib/endpoint.js +715 -0
- package/lib/forward-msg.d.ts +26 -0
- package/lib/{src/forward-msg.js → forward-msg.js} +0 -1
- package/lib/icqq-agent-deps.d.ts +25 -0
- package/lib/icqq-agent-deps.js +32 -0
- package/lib/icqq-guild.d.ts +53 -0
- package/lib/{src/icqq-guild.js → icqq-guild.js} +4 -2
- package/lib/icqq-inbound.d.ts +139 -0
- package/lib/{src/icqq-inbound.js → icqq-inbound.js} +27 -3
- package/lib/icqq-inbox.d.ts +33 -0
- package/lib/icqq-inbox.js +129 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +6 -0
- package/lib/ipc-client.d.ts +73 -0
- package/lib/{src/ipc-client.js → ipc-client.js} +0 -1
- package/lib/outbound-media.d.ts +13 -0
- package/lib/{src/outbound-media.js → outbound-media.js} +0 -1
- package/lib/protocol.d.ts +269 -0
- package/lib/{src/protocol.js → protocol.js} +124 -2
- package/lib/types.d.ts +87 -0
- package/lib/{src/types.js → types.js} +0 -1
- package/package.json +49 -45
- package/plugin.ts +32 -0
- package/schema.json +104 -0
- package/src/endpoint.ts +704 -795
- package/src/icqq-agent-deps.ts +48 -9
- package/src/icqq-guild.ts +10 -4
- package/src/icqq-inbound.ts +31 -2
- package/src/icqq-inbox.ts +155 -0
- package/src/index.ts +36 -69
- package/src/protocol.ts +191 -1
- package/src/types.ts +16 -0
- package/client/ICQQManagement.tsx +0 -147
- package/client/LoginAssistPanel.tsx +0 -300
- package/client/index.tsx +0 -11
- package/client/tsconfig.json +0 -7
- package/client/utils/api.ts +0 -30
- package/dist/index.js +0 -34
- package/lib/agent/tools/announce.js +0 -21
- package/lib/agent/tools/announce.js.map +0 -1
- package/lib/agent/tools/essence.js +0 -22
- package/lib/agent/tools/essence.js.map +0 -1
- package/lib/agent/tools/friend_list.js +0 -19
- package/lib/agent/tools/friend_list.js.map +0 -1
- package/lib/agent/tools/group_files.js +0 -29
- package/lib/agent/tools/group_files.js.map +0 -1
- package/lib/agent/tools/group_invite.js +0 -20
- package/lib/agent/tools/group_invite.js.map +0 -1
- package/lib/agent/tools/group_list.js +0 -20
- package/lib/agent/tools/group_list.js.map +0 -1
- package/lib/agent/tools/group_sign.js +0 -19
- package/lib/agent/tools/group_sign.js.map +0 -1
- package/lib/agent/tools/list_muted.js +0 -23
- package/lib/agent/tools/list_muted.js.map +0 -1
- package/lib/agent/tools/poke.js +0 -20
- package/lib/agent/tools/poke.js.map +0 -1
- package/lib/agent/tools/send_user_like.js +0 -22
- package/lib/agent/tools/send_user_like.js.map +0 -1
- package/lib/agent/tools/set_anonymous.js +0 -22
- package/lib/agent/tools/set_anonymous.js.map +0 -1
- package/lib/agent/tools/set_title.js +0 -25
- package/lib/agent/tools/set_title.js.map +0 -1
- package/lib/src/adapter.js +0 -128
- package/lib/src/adapter.js.map +0 -1
- package/lib/src/agent-prompt.js.map +0 -1
- package/lib/src/cq-message.js.map +0 -1
- package/lib/src/endpoint.js +0 -755
- package/lib/src/endpoint.js.map +0 -1
- package/lib/src/forward-msg.js.map +0 -1
- package/lib/src/get-msg.js +0 -46
- package/lib/src/get-msg.js.map +0 -1
- package/lib/src/icqq-agent-deps.js +0 -10
- package/lib/src/icqq-agent-deps.js.map +0 -1
- package/lib/src/icqq-guild.js.map +0 -1
- package/lib/src/icqq-inbound.js.map +0 -1
- package/lib/src/icqq-login-assist-bridge.js +0 -148
- package/lib/src/icqq-login-assist-bridge.js.map +0 -1
- package/lib/src/icqq-side-events.js +0 -130
- package/lib/src/icqq-side-events.js.map +0 -1
- package/lib/src/index.js +0 -50
- package/lib/src/index.js.map +0 -1
- package/lib/src/ipc-client.js.map +0 -1
- package/lib/src/login-assist-routes.js +0 -29
- package/lib/src/login-assist-routes.js.map +0 -1
- package/lib/src/login-ipc-contract.js +0 -35
- package/lib/src/login-ipc-contract.js.map +0 -1
- package/lib/src/outbound-media.js.map +0 -1
- package/lib/src/protocol.js.map +0 -1
- package/lib/src/routes.js +0 -67
- package/lib/src/routes.js.map +0 -1
- package/lib/src/segment-mapper.js +0 -2
- package/lib/src/segment-mapper.js.map +0 -1
- package/lib/src/tools/index.js +0 -23
- package/lib/src/tools/index.js.map +0 -1
- package/lib/src/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -151
- package/src/get-msg.ts +0 -56
- package/src/icqq-login-assist-bridge.ts +0 -196
- package/src/icqq-side-events.ts +0 -143
- package/src/login-assist-routes.ts +0 -39
- package/src/login-ipc-contract.ts +0 -52
- package/src/routes.ts +0 -83
- package/src/segment-mapper.ts +0 -1
- package/src/tools/index.ts +0 -31
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 合并转发(聊天记录)解析:从 json/xml 提取 resid,经 IPC get_forward_msg 拉取正文。
|
|
3
|
+
*/
|
|
4
|
+
import { type MessageSegment, type QuotedMessagePayload } from 'zhin.js';
|
|
5
|
+
import type { IpcClient } from "./ipc-client.js";
|
|
6
|
+
import { type IcqqMessageElement } from "./icqq-inbound.js";
|
|
7
|
+
/** 从 QQ 合并转发 json 元素提取 resid */
|
|
8
|
+
export declare function extractForwardResidFromJsonElement(el: IcqqMessageElement): string | undefined;
|
|
9
|
+
/** 从 get_msg 的 raw_message 解析 multimsg resid(XML / JSON / CQ:json) */
|
|
10
|
+
export declare function extractForwardResidFromRawMessage(raw: string): string | undefined;
|
|
11
|
+
/** 从 get_msg 的 message 元素数组解析 multimsg resid */
|
|
12
|
+
export declare function extractForwardResidFromGetMsgElements(elements: IcqqMessageElement[] | undefined): string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* 从 get_msg 响应解析合并转发 resid(优先 message 数组,其次 raw_message)。
|
|
15
|
+
*/
|
|
16
|
+
export declare function extractForwardResidFromGetMsg(data: unknown): string | undefined;
|
|
17
|
+
export declare function extractForwardResidFromSegments(content: MessageSegment[]): string | undefined;
|
|
18
|
+
export declare function extractForwardResidFromPayload(payload: QuotedMessagePayload): string | undefined;
|
|
19
|
+
/** 在 get_msg / IPC 原始对象里深度搜索 resid */
|
|
20
|
+
export declare function extractForwardResidDeep(root: unknown, maxDepth?: number): string | undefined;
|
|
21
|
+
export declare function isForwardPlaceholderPayload(payload: QuotedMessagePayload): boolean;
|
|
22
|
+
/** 将 get_forward_msg 响应格式化为可读聊天记录 */
|
|
23
|
+
export declare function formatForwardMsgResponse(data: unknown): string;
|
|
24
|
+
export declare function fetchForwardMsgText(ipc: IpcClient, id: string): Promise<string>;
|
|
25
|
+
/** 若 payload 含合并转发,拉取并追加到 content */
|
|
26
|
+
export declare function enrichQuotedPayloadWithForward(ipc: IpcClient | null | undefined, payload: QuotedMessagePayload, ipcRaw?: unknown): Promise<QuotedMessagePayload>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool deps for icqq.
|
|
3
|
+
* Endpoints register themselves on start; tools look up by config name / QQ uin.
|
|
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>;
|
|
15
|
+
}
|
|
16
|
+
export interface IcqqAgentDeps {
|
|
17
|
+
getEndpoint: (endpointId: string) => IcqqAgentEndpoint;
|
|
18
|
+
getAdapter?: () => {
|
|
19
|
+
getEndpoint: (id: string) => IcqqAgentEndpoint;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare function registerIcqqAgentEndpoint(endpointId: string, endpoint: IcqqAgentEndpoint): () => void;
|
|
23
|
+
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
24
|
+
export declare function setIcqqAgentDeps(deps: IcqqAgentDeps | null): void;
|
|
25
|
+
export declare function getIcqqAgentDeps(): IcqqAgentDeps;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool deps for icqq.
|
|
3
|
+
* Endpoints register themselves on start; tools look up by config name / QQ uin.
|
|
4
|
+
*/
|
|
5
|
+
const endpoints = new Map();
|
|
6
|
+
let override = null;
|
|
7
|
+
export function registerIcqqAgentEndpoint(endpointId, endpoint) {
|
|
8
|
+
endpoints.set(endpointId, endpoint);
|
|
9
|
+
return () => {
|
|
10
|
+
if (endpoints.get(endpointId) === endpoint) {
|
|
11
|
+
endpoints.delete(endpointId);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
16
|
+
export function setIcqqAgentDeps(deps) {
|
|
17
|
+
override = deps;
|
|
18
|
+
}
|
|
19
|
+
function lookup(endpointId) {
|
|
20
|
+
const registered = endpoints.get(endpointId);
|
|
21
|
+
if (!registered)
|
|
22
|
+
throw new Error(`Endpoint ${endpointId} 不存在`);
|
|
23
|
+
return registered;
|
|
24
|
+
}
|
|
25
|
+
export function getIcqqAgentDeps() {
|
|
26
|
+
if (override)
|
|
27
|
+
return override;
|
|
28
|
+
return {
|
|
29
|
+
getEndpoint: lookup,
|
|
30
|
+
getAdapter: () => ({ getEndpoint: lookup }),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ICQQ guild (QQ 频道) catalog sync, inbound normalize, Console channel list.
|
|
3
|
+
*/
|
|
4
|
+
import { type MessageSegment } from "zhin.js";
|
|
5
|
+
import { type IpcGuildMessageEventData } from "./protocol.js";
|
|
6
|
+
/** Narrow request port shared by the real IPC client and endpoint test transports. */
|
|
7
|
+
export interface IcqqGuildRpc {
|
|
8
|
+
request(action: string, params?: Record<string, unknown>): Promise<{
|
|
9
|
+
ok: boolean;
|
|
10
|
+
data?: unknown;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export interface IcqqGuildChannelEntry {
|
|
14
|
+
channel_id: string;
|
|
15
|
+
channel_name: string;
|
|
16
|
+
guild_id: string;
|
|
17
|
+
guild_name: string;
|
|
18
|
+
}
|
|
19
|
+
export interface NormalizedIcqqGuildInbound {
|
|
20
|
+
messageId: string;
|
|
21
|
+
channelId: string;
|
|
22
|
+
guildId: string;
|
|
23
|
+
guildName: string;
|
|
24
|
+
channelName: string;
|
|
25
|
+
userId: string;
|
|
26
|
+
nickname: string;
|
|
27
|
+
content: MessageSegment[];
|
|
28
|
+
rawMessage: string;
|
|
29
|
+
timestampMs: number;
|
|
30
|
+
raw: IpcGuildMessageEventData;
|
|
31
|
+
}
|
|
32
|
+
export declare class IcqqGuildCatalog {
|
|
33
|
+
private readonly channels;
|
|
34
|
+
private readonly guilds;
|
|
35
|
+
clear(): void;
|
|
36
|
+
syncAll(ipc: IcqqGuildRpc): Promise<void>;
|
|
37
|
+
upsertFromInbound(data: IpcGuildMessageEventData): void;
|
|
38
|
+
getGuildChannelList(): Array<{
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
parent: {
|
|
42
|
+
type: "guild";
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
};
|
|
46
|
+
}>;
|
|
47
|
+
resolveConsoleChannelNames(channelId: string, guildId?: string): {
|
|
48
|
+
channelName?: string;
|
|
49
|
+
parentName?: string;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export declare function isIcqqGuildIpcEvent(eventName: string | undefined): boolean;
|
|
53
|
+
export declare function normalizeIcqqGuildInboundMessage(data: IpcGuildMessageEventData & Record<string, unknown>): NormalizedIcqqGuildInbound | null;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ICQQ guild (QQ 频道) catalog sync, inbound normalize, Console channel list.
|
|
3
|
+
*/
|
|
4
|
+
import { toCanonicalSegments } from "zhin.js";
|
|
1
5
|
import { Actions } from "./protocol.js";
|
|
2
6
|
import { parseCqMessage } from "./cq-message.js";
|
|
3
|
-
import { toCanonicalSegments } from "./segment-mapper.js";
|
|
4
7
|
export class IcqqGuildCatalog {
|
|
5
8
|
channels = new Map();
|
|
6
9
|
guilds = new Map();
|
|
@@ -112,4 +115,3 @@ export function normalizeIcqqGuildInboundMessage(data) {
|
|
|
112
115
|
raw: data,
|
|
113
116
|
};
|
|
114
117
|
}
|
|
115
|
-
//# sourceMappingURL=icqq-guild.js.map
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* icqq 守护进程 IPC 入站事件归一化(post_type / message_type / message 段)
|
|
3
|
+
*/
|
|
4
|
+
import { type MessageSegment, type QuotedMessagePayload } from "zhin.js";
|
|
5
|
+
import type { GroupRole } from "./types.js";
|
|
6
|
+
/** 守护进程推送的通用事件壳 */
|
|
7
|
+
export interface IcqqIpcEventBase {
|
|
8
|
+
post_type?: string;
|
|
9
|
+
self_id?: number;
|
|
10
|
+
}
|
|
11
|
+
/** post_type=message 时的消息体(字段随 icqqjs/cli 演进,未知字段保留) */
|
|
12
|
+
export interface IcqqIpcMessageEvent extends IcqqIpcEventBase {
|
|
13
|
+
post_type: "message";
|
|
14
|
+
message_id?: string;
|
|
15
|
+
msg_id?: string;
|
|
16
|
+
user_id: number;
|
|
17
|
+
user_uid?: string;
|
|
18
|
+
time: number;
|
|
19
|
+
seq?: number;
|
|
20
|
+
message_type: string;
|
|
21
|
+
sub_type?: string;
|
|
22
|
+
raw_message?: string;
|
|
23
|
+
message?: IcqqMessageElement[];
|
|
24
|
+
sender?: {
|
|
25
|
+
user_id?: number;
|
|
26
|
+
user_uid?: string;
|
|
27
|
+
nickname?: string;
|
|
28
|
+
card?: string;
|
|
29
|
+
/** 群临时会话来源群号 */
|
|
30
|
+
group_id?: number;
|
|
31
|
+
/** NT/IPC 群成员角色:owner | admin | member */
|
|
32
|
+
role?: GroupRole | string;
|
|
33
|
+
};
|
|
34
|
+
from_id?: number;
|
|
35
|
+
from_uid?: string;
|
|
36
|
+
group_id?: number;
|
|
37
|
+
to_id?: number;
|
|
38
|
+
nickname?: string;
|
|
39
|
+
/** 旧版 IPC 仅有 type 无 message_type */
|
|
40
|
+
type?: "group" | "private";
|
|
41
|
+
/** oicq Message#source:被引用回复的源消息 */
|
|
42
|
+
source?: IcqqMessageSource;
|
|
43
|
+
}
|
|
44
|
+
/** oicq 引用源消息(与 MessageEvent.source 对齐) */
|
|
45
|
+
export interface IcqqMessageSource {
|
|
46
|
+
message_id?: string;
|
|
47
|
+
msg_id?: string;
|
|
48
|
+
seq?: number;
|
|
49
|
+
rand?: number;
|
|
50
|
+
time?: number;
|
|
51
|
+
user_id?: number;
|
|
52
|
+
group_id?: number;
|
|
53
|
+
raw_message?: string;
|
|
54
|
+
/** 元素数组,或 NT 推送的纯文本摘要 */
|
|
55
|
+
message?: IcqqMessageElement[] | string;
|
|
56
|
+
sender?: {
|
|
57
|
+
user_id?: number;
|
|
58
|
+
nickname?: string;
|
|
59
|
+
card?: string;
|
|
60
|
+
};
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
}
|
|
63
|
+
export type IcqqMessageElement = {
|
|
64
|
+
type: string;
|
|
65
|
+
text?: string;
|
|
66
|
+
qq?: string | number;
|
|
67
|
+
user_id?: string | number;
|
|
68
|
+
id?: string | number;
|
|
69
|
+
url?: string;
|
|
70
|
+
file?: string;
|
|
71
|
+
[key: string]: unknown;
|
|
72
|
+
};
|
|
73
|
+
/** 归一化后供适配器使用的入站消息 */
|
|
74
|
+
export interface NormalizedIcqqInbound {
|
|
75
|
+
messageId: string;
|
|
76
|
+
idSource: "message_id" | "msg_id" | "seq" | "synthetic";
|
|
77
|
+
channelType: "group" | "private";
|
|
78
|
+
channelId: string;
|
|
79
|
+
/** 群临时会话:来源群号 */
|
|
80
|
+
channelParentGroupId?: string;
|
|
81
|
+
userId: string;
|
|
82
|
+
nickname: string;
|
|
83
|
+
/** 群聊时来自 IPC sender.role(私聊无) */
|
|
84
|
+
senderRole?: GroupRole;
|
|
85
|
+
content: MessageSegment[];
|
|
86
|
+
rawMessage: string;
|
|
87
|
+
timestampMs: number;
|
|
88
|
+
raw: IcqqIpcMessageEvent;
|
|
89
|
+
}
|
|
90
|
+
/** 从 IPC sender.role 解析群成员角色(供 Message.$sender → resolveSenderRoles) */
|
|
91
|
+
export declare function parseIcqqSenderGroupRole(sender: {
|
|
92
|
+
role?: unknown;
|
|
93
|
+
} | undefined): GroupRole | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* 判断入站消息是否 @ 了本机账号(uin = endpoint 配置 name,即 QQ 号)。
|
|
96
|
+
* 新 Plugin Runtime 的 Message.content 为纯文本,at 信息只能经 metadata 传递,
|
|
97
|
+
* endpoint 在 admit 时用本函数标注 `metadata.mentioned`(供 Agent Host 的 @ 触发)。
|
|
98
|
+
*/
|
|
99
|
+
export declare function isIcqqBotMentioned(input: {
|
|
100
|
+
readonly uin: string;
|
|
101
|
+
readonly content?: readonly MessageSegment[] | null;
|
|
102
|
+
readonly rawMessage?: string;
|
|
103
|
+
}): boolean;
|
|
104
|
+
/** 从 IPC 事件壳中取出 OneBot/icqq 载荷(兼容 data 嵌套或字段在根级) */
|
|
105
|
+
export declare function unwrapIcqqIpcEventPayload(event: {
|
|
106
|
+
data?: unknown;
|
|
107
|
+
[key: string]: unknown;
|
|
108
|
+
}): unknown;
|
|
109
|
+
export declare function resolveIcqqInboundUserId(data: Record<string, unknown>): number | undefined;
|
|
110
|
+
export declare function isIcqqMessagePostType(data: unknown): data is IcqqIpcMessageEvent;
|
|
111
|
+
export declare function shouldSkipSelfInboundMessage(data: IcqqIpcMessageEvent): boolean;
|
|
112
|
+
/** 群临时会话私聊:icqq message_type=private & sub_type=group */
|
|
113
|
+
export declare function isIcqqGroupTempPrivateMessage(data: IcqqIpcMessageEvent): boolean;
|
|
114
|
+
export declare function resolveChannelFromIcqqMessage(data: IcqqIpcMessageEvent): {
|
|
115
|
+
channelType: "group" | "private";
|
|
116
|
+
channelId: string;
|
|
117
|
+
channelParentGroupId?: string;
|
|
118
|
+
};
|
|
119
|
+
export declare function resolveIcqqInboundMessageId(data: IcqqIpcMessageEvent, channelId: string): {
|
|
120
|
+
id: string;
|
|
121
|
+
source: "message_id" | "msg_id" | "seq" | "synthetic";
|
|
122
|
+
};
|
|
123
|
+
export declare function icqqElementsToSegments(elements: IcqqMessageElement[] | undefined): MessageSegment[] | null;
|
|
124
|
+
/** IPC 载荷可能把 source 嵌在 data / event 等字段,深度查找 */
|
|
125
|
+
export declare function findIcqqNestedMessageSource(root: unknown, maxDepth?: number): IcqqMessageSource | undefined;
|
|
126
|
+
/** 从 oicq MessageEvent.source 解析被引用消息的 message_id */
|
|
127
|
+
export declare function resolveQuoteIdFromIcqqSource(source: unknown): string | undefined;
|
|
128
|
+
/** 将 source 转为 QuotedMessagePayload(供 $getMsg 缓存,避免重复拉取) */
|
|
129
|
+
export declare function quotedPayloadFromIcqqSource(source: unknown): QuotedMessagePayload | null;
|
|
130
|
+
/** 从 IPC 事件顶层字段解析引用 message_id(与 message 段互补) */
|
|
131
|
+
export declare function resolveIcqqQuoteIdFromEvent(data: IcqqIpcMessageEvent): string | undefined;
|
|
132
|
+
export declare function resolveInboundContent(data: IcqqIpcMessageEvent): MessageSegment[];
|
|
133
|
+
export declare function normalizeIcqqInboundMessage(data: IcqqIpcMessageEvent): NormalizedIcqqInbound | null;
|
|
134
|
+
/** 按 message_id 去重(多路 subscribe 会收到同一消息多次) */
|
|
135
|
+
export declare class InboundMessageDeduper {
|
|
136
|
+
private readonly seen;
|
|
137
|
+
shouldProcess(messageId: string): boolean;
|
|
138
|
+
clear(): void;
|
|
139
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* icqq 守护进程 IPC 入站事件归一化(post_type / message_type / message 段)
|
|
3
3
|
*/
|
|
4
|
-
import { Message } from "zhin.js";
|
|
4
|
+
import { Message, toCanonicalSegments } from "zhin.js";
|
|
5
5
|
import { parseCqMessage } from "./cq-message.js";
|
|
6
|
-
import { toCanonicalSegments } from "./segment-mapper.js";
|
|
7
6
|
import { extractForwardResidFromJsonElement } from "./forward-msg.js";
|
|
8
7
|
const DEDUPE_TTL_MS = 120_000;
|
|
9
8
|
const ICQQ_GROUP_ROLES = new Set(["owner", "admin", "member"]);
|
|
@@ -15,6 +14,32 @@ export function parseIcqqSenderGroupRole(sender) {
|
|
|
15
14
|
const role = raw.trim().toLowerCase();
|
|
16
15
|
return ICQQ_GROUP_ROLES.has(role) ? role : undefined;
|
|
17
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* 判断入站消息是否 @ 了本机账号(uin = endpoint 配置 name,即 QQ 号)。
|
|
19
|
+
* 新 Plugin Runtime 的 Message.content 为纯文本,at 信息只能经 metadata 传递,
|
|
20
|
+
* endpoint 在 admit 时用本函数标注 `metadata.mentioned`(供 Agent Host 的 @ 触发)。
|
|
21
|
+
*/
|
|
22
|
+
export function isIcqqBotMentioned(input) {
|
|
23
|
+
const uin = String(input.uin ?? "").trim();
|
|
24
|
+
if (!uin)
|
|
25
|
+
return false;
|
|
26
|
+
for (const seg of input.content ?? []) {
|
|
27
|
+
if (!seg || (seg.type !== "at" && seg.type !== "mention"))
|
|
28
|
+
continue;
|
|
29
|
+
const data = (seg.data ?? {});
|
|
30
|
+
const target = String(data.qq ?? data.user_id ?? data.id ?? data.target ?? "").trim();
|
|
31
|
+
if (target && target !== "all" && Number(target) === Number(uin)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const raw = input.rawMessage ?? "";
|
|
36
|
+
if (!raw)
|
|
37
|
+
return false;
|
|
38
|
+
const escaped = uin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
39
|
+
// OneBot CQ 码 `[CQ:at,qq=uin]`;QQ 频道 markdown `<@!uin>`
|
|
40
|
+
return new RegExp(`\\[CQ:at,qq=${escaped}\\]`, "u").test(raw)
|
|
41
|
+
|| new RegExp(`<@!?${escaped}>`, "u").test(raw);
|
|
42
|
+
}
|
|
18
43
|
const MESSAGE_POST_TYPES = new Set(["message"]);
|
|
19
44
|
function isMessagePostType(postType) {
|
|
20
45
|
if (typeof postType !== "string" || postType === "")
|
|
@@ -538,4 +563,3 @@ export class InboundMessageDeduper {
|
|
|
538
563
|
this.seen.clear();
|
|
539
564
|
}
|
|
540
565
|
}
|
|
541
|
-
//# sourceMappingURL=icqq-inbound.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
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';
|
|
10
|
+
/** 行级公共前缀:adapter 槽 localName + endpoint live 名(uin)。 */
|
|
11
|
+
export interface IcqqInboxBase {
|
|
12
|
+
readonly adapter: string;
|
|
13
|
+
readonly endpointId: string;
|
|
14
|
+
}
|
|
15
|
+
/** post_type=request / request.* 判定(OneBot 事件壳)。 */
|
|
16
|
+
export declare function isIcqqRequestPayload(payload: unknown): boolean;
|
|
17
|
+
/** post_type=notice / notice.* 判定。 */
|
|
18
|
+
export declare function isIcqqNoticePayload(payload: unknown): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* request 事件载荷 → unified_inbox_request 行。
|
|
21
|
+
* platform_request_id 取 flag(审批回执句柄),缺省回退 seq;两者皆无则无法审批,丢弃。
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildIcqqInboxRequestRow(payload: Record<string, unknown>, base: IcqqInboxBase): Record<string, unknown> | null;
|
|
24
|
+
/**
|
|
25
|
+
* notice 事件载荷 → unified_inbox_notice 行。
|
|
26
|
+
* 事件本身无稳定 id,platform_notice_id 由 time + actor + type 合成(配合去重)。
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildIcqqInboxNoticeRow(payload: Record<string, unknown>, base: IcqqInboxBase): Record<string, unknown> | null;
|
|
29
|
+
/**
|
|
30
|
+
* GET_SYSTEM_MSG 返回项 → unified_inbox_request 行(启动时首次拉取)。
|
|
31
|
+
* friend 列表项 type 语义即好友请求;group 列表项 type 为 add/invite(记 sub_type)。
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildIcqqSystemRequestRow(message: IpcSystemMessage, kind: 'friend' | 'group', base: IcqqInboxBase): Record<string, unknown> | null;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/** post_type=request / request.* 判定(OneBot 事件壳)。 */
|
|
2
|
+
export function isIcqqRequestPayload(payload) {
|
|
3
|
+
const postType = payload?.post_type;
|
|
4
|
+
return typeof postType === 'string'
|
|
5
|
+
&& (postType === 'request' || postType.startsWith('request.'));
|
|
6
|
+
}
|
|
7
|
+
/** post_type=notice / notice.* 判定。 */
|
|
8
|
+
export function isIcqqNoticePayload(payload) {
|
|
9
|
+
const postType = payload?.post_type;
|
|
10
|
+
return typeof postType === 'string'
|
|
11
|
+
&& (postType === 'notice' || postType.startsWith('notice.'));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* request 事件载荷 → unified_inbox_request 行。
|
|
15
|
+
* platform_request_id 取 flag(审批回执句柄),缺省回退 seq;两者皆无则无法审批,丢弃。
|
|
16
|
+
*/
|
|
17
|
+
export function buildIcqqInboxRequestRow(payload, base) {
|
|
18
|
+
const userId = payload.user_id ?? payload.actor_id;
|
|
19
|
+
if (userId == null)
|
|
20
|
+
return null;
|
|
21
|
+
const flag = asNonEmptyString(payload.flag);
|
|
22
|
+
const platformRequestId = flag ?? (payload.seq != null ? String(payload.seq) : '');
|
|
23
|
+
if (!platformRequestId)
|
|
24
|
+
return null;
|
|
25
|
+
const requestType = asNonEmptyString(payload.request_type) ?? asNonEmptyString(payload.type);
|
|
26
|
+
const groupId = payload.group_id;
|
|
27
|
+
const isGroup = requestType === 'group' || groupId != null;
|
|
28
|
+
return {
|
|
29
|
+
adapter: base.adapter,
|
|
30
|
+
endpoint_id: base.endpointId,
|
|
31
|
+
platform_request_id: platformRequestId,
|
|
32
|
+
type: isGroup ? 'group' : (requestType ?? 'friend'),
|
|
33
|
+
scene_type: isGroup ? 'group' : null,
|
|
34
|
+
scene_id: String(groupId ?? userId),
|
|
35
|
+
sub_type: asNonEmptyString(payload.sub_type),
|
|
36
|
+
actor_id: String(userId),
|
|
37
|
+
actor_name: asNonEmptyString(payload.nickname),
|
|
38
|
+
comment: asNonEmptyString(payload.comment),
|
|
39
|
+
created_at: toMillis(payload.time),
|
|
40
|
+
resolved: 0,
|
|
41
|
+
resolved_at: null,
|
|
42
|
+
consumed: 0,
|
|
43
|
+
consumed_at: null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* notice 事件载荷 → unified_inbox_notice 行。
|
|
48
|
+
* 事件本身无稳定 id,platform_notice_id 由 time + actor + type 合成(配合去重)。
|
|
49
|
+
*/
|
|
50
|
+
export function buildIcqqInboxNoticeRow(payload, base) {
|
|
51
|
+
const noticeType = asNonEmptyString(payload.notice_type) ?? asNonEmptyString(payload.type);
|
|
52
|
+
if (!noticeType)
|
|
53
|
+
return null;
|
|
54
|
+
const subType = asNonEmptyString(payload.sub_type);
|
|
55
|
+
const userId = payload.user_id;
|
|
56
|
+
const operatorId = payload.operator_id;
|
|
57
|
+
const groupId = payload.group_id;
|
|
58
|
+
const createdAt = toMillis(payload.time);
|
|
59
|
+
const actorId = operatorId ?? userId;
|
|
60
|
+
const platformNoticeId = asNonEmptyString(payload.notice_id)
|
|
61
|
+
?? (payload.seq != null ? String(payload.seq) : undefined)
|
|
62
|
+
?? `${createdAt}_${String(actorId ?? '')}_${noticeType}${subType ? `.${subType}` : ''}`;
|
|
63
|
+
const targetId = userId != null && String(userId) !== String(actorId ?? '') ? userId : undefined;
|
|
64
|
+
return {
|
|
65
|
+
adapter: base.adapter,
|
|
66
|
+
endpoint_id: base.endpointId,
|
|
67
|
+
platform_notice_id: platformNoticeId,
|
|
68
|
+
type: noticeType,
|
|
69
|
+
scene_type: groupId != null ? 'group' : null,
|
|
70
|
+
scene_id: String(groupId ?? userId ?? operatorId ?? ''),
|
|
71
|
+
sub_type: subType,
|
|
72
|
+
actor_id: actorId != null ? String(actorId) : null,
|
|
73
|
+
actor_name: asNonEmptyString(payload.nickname),
|
|
74
|
+
target_id: targetId != null ? String(targetId) : null,
|
|
75
|
+
target_name: null,
|
|
76
|
+
payload: safeJson(payload),
|
|
77
|
+
created_at: createdAt,
|
|
78
|
+
consumed: 0,
|
|
79
|
+
consumed_at: null,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* GET_SYSTEM_MSG 返回项 → unified_inbox_request 行(启动时首次拉取)。
|
|
84
|
+
* friend 列表项 type 语义即好友请求;group 列表项 type 为 add/invite(记 sub_type)。
|
|
85
|
+
*/
|
|
86
|
+
export function buildIcqqSystemRequestRow(message, kind, base) {
|
|
87
|
+
if (message.user_id == null)
|
|
88
|
+
return null;
|
|
89
|
+
const platformRequestId = asNonEmptyString(message.flag)
|
|
90
|
+
?? (message.seq != null ? String(message.seq) : '');
|
|
91
|
+
if (!platformRequestId)
|
|
92
|
+
return null;
|
|
93
|
+
return {
|
|
94
|
+
adapter: base.adapter,
|
|
95
|
+
endpoint_id: base.endpointId,
|
|
96
|
+
platform_request_id: platformRequestId,
|
|
97
|
+
type: kind,
|
|
98
|
+
scene_type: kind === 'group' ? 'group' : null,
|
|
99
|
+
scene_id: String(kind === 'group' ? (message.group_id ?? message.user_id) : message.user_id),
|
|
100
|
+
sub_type: kind === 'group' ? (asNonEmptyString(message.type) ?? null) : null,
|
|
101
|
+
actor_id: String(message.user_id),
|
|
102
|
+
actor_name: asNonEmptyString(message.nickname),
|
|
103
|
+
comment: asNonEmptyString(message.comment),
|
|
104
|
+
created_at: toMillis(message.time),
|
|
105
|
+
resolved: 0,
|
|
106
|
+
resolved_at: null,
|
|
107
|
+
consumed: 0,
|
|
108
|
+
consumed_at: null,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function asNonEmptyString(value) {
|
|
112
|
+
if (typeof value !== 'string')
|
|
113
|
+
return null;
|
|
114
|
+
const trimmed = value.trim();
|
|
115
|
+
return trimmed ? trimmed : null;
|
|
116
|
+
}
|
|
117
|
+
/** oicq 事件 time 为秒;缺失时回退当前时间。 */
|
|
118
|
+
function toMillis(time) {
|
|
119
|
+
const seconds = Number(time);
|
|
120
|
+
return Number.isFinite(seconds) && seconds > 0 ? seconds * 1000 : Date.now();
|
|
121
|
+
}
|
|
122
|
+
function safeJson(value) {
|
|
123
|
+
try {
|
|
124
|
+
return JSON.stringify(value ?? {});
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return '{}';
|
|
128
|
+
}
|
|
129
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { Actions, formatInboundContent, formatInboundTarget, formatOutboundBody, parseSendTarget, resolveIcqqConfig, type IcqqAdapterConfig, type IcqqInboundMessage, type IcqqWireSegment, type IpcEvent, type IpcMessage, type IpcRequest, type IpcResponse, type ParsedIcqqSendTarget, type ResolvedIcqqConfig, } from './protocol.js';
|
|
2
|
+
export { IcqqIpcEndpoint, type CreateIcqqIpc, type IcqqEndpointOptions, type IcqqInboxHooks, type IcqqIpcTransport, } from './endpoint.js';
|
|
3
|
+
export * from './types.js';
|
|
4
|
+
export { getIcqqAgentDeps, registerIcqqAgentEndpoint, setIcqqAgentDeps, type IcqqAgentDeps, type IcqqAgentEndpoint, } from './icqq-agent-deps.js';
|
|
5
|
+
export { IpcClient } from './ipc-client.js';
|
|
6
|
+
export { resolveIcqqInboundMessageId, } from './icqq-inbound.js';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { Actions, formatInboundContent, formatInboundTarget, formatOutboundBody, parseSendTarget, resolveIcqqConfig, } from './protocol.js';
|
|
2
|
+
export { IcqqIpcEndpoint, } from './endpoint.js';
|
|
3
|
+
export * from './types.js';
|
|
4
|
+
export { getIcqqAgentDeps, registerIcqqAgentEndpoint, setIcqqAgentDeps, } from './icqq-agent-deps.js';
|
|
5
|
+
export { IpcClient } from './ipc-client.js';
|
|
6
|
+
export { resolveIcqqInboundMessageId, } from './icqq-inbound.js';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { IpcResponse, IpcEvent } from "./protocol.js";
|
|
2
|
+
export declare class IpcClient {
|
|
3
|
+
private socket;
|
|
4
|
+
private buffer;
|
|
5
|
+
private pending;
|
|
6
|
+
private eventHandlers;
|
|
7
|
+
private _closed;
|
|
8
|
+
/** 是否为客户端主动 close()(与对端断线区分) */
|
|
9
|
+
private _userClosed;
|
|
10
|
+
private _remoteDisconnectEmitted;
|
|
11
|
+
private onRemoteDisconnect;
|
|
12
|
+
private constructor();
|
|
13
|
+
get closed(): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* 注册「对端断开 / 连接异常关闭」回调(不含本端 close())。
|
|
16
|
+
* 传入 null 可清除回调。
|
|
17
|
+
*/
|
|
18
|
+
setOnRemoteDisconnect(handler: (() => void) | null): void;
|
|
19
|
+
/** 注册数据处理 handler(RPC 模式延迟到 challenge 完成后调用) */
|
|
20
|
+
private attachDataHandler;
|
|
21
|
+
/**
|
|
22
|
+
* 通过 IPC(Unix Socket)连接守护进程并完成认证。
|
|
23
|
+
* @param uin - 目标账号的 QQ 号
|
|
24
|
+
* @returns 已认证的 IpcClient 实例
|
|
25
|
+
* @throws 守护进程未运行或认证失败时抛出错误
|
|
26
|
+
*/
|
|
27
|
+
static connect(uin: number): Promise<IpcClient>;
|
|
28
|
+
/**
|
|
29
|
+
* 通过 RPC(TCP)连接守护进程并完成 HMAC 挑战-响应认证。
|
|
30
|
+
*
|
|
31
|
+
* 流程:
|
|
32
|
+
* 1. 服务端连接后发送 { challenge: "<hex>" }
|
|
33
|
+
* 2. 客户端用 HMAC-SHA256(token, challenge) 生成 digest
|
|
34
|
+
* 3. 客户端发送 { action: "auth", params: { digest: "<hex>" } }
|
|
35
|
+
* 4. 服务端验证 digest,通过则认证成功
|
|
36
|
+
*
|
|
37
|
+
* token 不会明文传输,防止中间人嗅探。
|
|
38
|
+
*
|
|
39
|
+
* @param options.host - 远程主机地址
|
|
40
|
+
* @param options.port - 远程端口
|
|
41
|
+
* @param options.token - 认证 token(用于 HMAC 计算,不会明文传输)
|
|
42
|
+
*/
|
|
43
|
+
static connectRpc(options: {
|
|
44
|
+
host: string;
|
|
45
|
+
port: number;
|
|
46
|
+
token: string;
|
|
47
|
+
}): Promise<IpcClient>;
|
|
48
|
+
/**
|
|
49
|
+
* 通过 RPC 连接守护进程(自动从 daemon.rpc 文件读取地址)。
|
|
50
|
+
* @param uin - 目标账号的 QQ 号
|
|
51
|
+
*/
|
|
52
|
+
static connectRpcByUin(uin: number): Promise<IpcClient>;
|
|
53
|
+
private onData;
|
|
54
|
+
/**
|
|
55
|
+
* 注册 icqq 事件回调。
|
|
56
|
+
* 新版守护进程在认证后会自动广播事件(event.id = "*")。
|
|
57
|
+
*/
|
|
58
|
+
onEvent(onEvent: (event: IpcEvent) => void): () => void;
|
|
59
|
+
/**
|
|
60
|
+
* 发送 IPC 请求并等待响应。
|
|
61
|
+
*/
|
|
62
|
+
request(action: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<IpcResponse>;
|
|
63
|
+
/**
|
|
64
|
+
* @deprecated 兼容旧调用方式:服务端已自动推送事件,type/id 过滤在客户端完成。
|
|
65
|
+
* @returns 订阅句柄,包含 unsubscribe() 方法
|
|
66
|
+
*/
|
|
67
|
+
subscribe(_action: string, params: Record<string, unknown>, onEvent: (event: IpcEvent) => void): {
|
|
68
|
+
id: string;
|
|
69
|
+
unsubscribe: () => Promise<void>;
|
|
70
|
+
};
|
|
71
|
+
/** 关闭连接 */
|
|
72
|
+
close(): void;
|
|
73
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SendContent } from "zhin.js";
|
|
2
|
+
import type { IcqqEndpointConfig } from "./types.js";
|
|
3
|
+
export type IcqqOutboundMediaMode = "file" | "base64";
|
|
4
|
+
/**
|
|
5
|
+
* 出站媒体模式:file=本机落盘后 CQ [image:path](与 icqq 同机时可用);
|
|
6
|
+
* base64=保留 segment.base64,CQ 编码为 [image:base64://...] 经 IPC 交给守护进程解码(异机 RPC 默认)。
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveIcqqOutboundMediaMode(config: Pick<IcqqEndpointConfig, "rpc" | "outboundMedia">): IcqqOutboundMediaMode;
|
|
9
|
+
/**
|
|
10
|
+
* file 模式:将 segment.data.base64 物化为本机路径。
|
|
11
|
+
* base64 模式:不改动(由 toCqString 生成 base64://,供异机守护进程读取)。
|
|
12
|
+
*/
|
|
13
|
+
export declare function materializeOutboundBase64(content: SendContent, mode?: IcqqOutboundMediaMode): SendContent;
|