@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.
- package/CHANGELOG.md +15 -0
- package/README.md +36 -28
- package/adapters/icqq.js +4 -9
- package/adapters/icqq.ts +5 -14
- 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 +4 -74
- 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 +4 -13
- 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/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 +24 -21
- package/lib/types.d.ts +4 -40
- package/lib/types.js +0 -5
- package/package.json +25 -13
- package/plugin.js +6 -1
- package/schema.json +119 -61
- package/src/agent-prompt.ts +17 -22
- package/src/cq-message.ts +2 -2
- package/src/endpoint.ts +358 -600
- package/src/forward-msg.ts +12 -26
- package/src/icqq-agent-deps.ts +3 -17
- package/src/icqq-endpoint-commands.ts +4 -13
- package/src/icqq-guild.ts +16 -25
- package/src/icqq-inbound.ts +26 -26
- package/src/icqq-inbox.ts +2 -10
- package/src/icqqjs-icqq.d.ts +88 -0
- package/src/index.ts +2 -9
- package/src/outbound-media.ts +4 -9
- package/src/protocol.ts +60 -66
- 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/src/forward-msg.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
365
|
+
client: { getForwardMsg(resId: string, fileName?: string): Promise<unknown[]> },
|
|
371
366
|
id: string,
|
|
372
367
|
): Promise<string> {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
395
|
+
client: { getForwardMsg(resId: string, fileName?: string): Promise<unknown[]> } | null | undefined,
|
|
410
396
|
payload: QuotedMessagePayload,
|
|
411
|
-
|
|
397
|
+
rawData?: unknown,
|
|
412
398
|
): Promise<QuotedMessagePayload> {
|
|
413
|
-
if (!
|
|
399
|
+
if (!client || hasMergedForwardBlock(payload)) return payload;
|
|
414
400
|
|
|
415
401
|
const resid =
|
|
416
402
|
extractForwardResidFromPayload(payload) ??
|
|
417
|
-
(
|
|
418
|
-
(
|
|
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(
|
|
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);
|
package/src/icqq-agent-deps.ts
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
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: (
|
|
19
|
-
addDescription: '登记 ICQQ endpoint
|
|
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
|
|
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
|
-
|
|
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 {
|
|
5
|
+
import type { GuildMessageEventData } from "./protocol.js";
|
|
6
6
|
import { parseCqMessage } from "./cq-message.js";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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:
|
|
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(
|
|
47
|
-
const
|
|
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
|
|
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(
|
|
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
|
|
59
|
-
|
|
60
|
-
|
|
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(
|
|
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:
|
|
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
|
|
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:
|
|
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 ?? "");
|
package/src/icqq-inbound.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* icqq
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/** 群聊时来自
|
|
90
|
+
/** 群聊时来自 sender.role(私聊无) */
|
|
91
91
|
senderRole?: GroupRole;
|
|
92
92
|
content: MessageSegment[];
|
|
93
93
|
rawMessage: string;
|
|
94
94
|
timestampMs: number;
|
|
95
|
-
raw:
|
|
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
|
-
/** 从
|
|
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
|
-
/**
|
|
152
|
-
export function
|
|
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 (
|
|
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 (
|
|
171
|
+
if (isRecognizedIcqqPayload(rest)) {
|
|
172
172
|
return rest;
|
|
173
173
|
}
|
|
174
174
|
return nested;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
function
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
593
|
+
/** 从事件顶层字段解析引用 message_id(与 message 段互补) */
|
|
594
594
|
export function resolveIcqqQuoteIdFromEvent(
|
|
595
|
-
data:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
667
|
+
data: IcqqMessageEvent,
|
|
668
668
|
): NormalizedIcqqInbound | null {
|
|
669
669
|
if (!isIcqqMessagePostType(data)) return null;
|
|
670
|
-
const ext = data as
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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';
|
package/src/outbound-media.ts
CHANGED
|
@@ -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, "
|
|
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
|
|
43
|
+
return "file";
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
/**
|
|
51
47
|
* file 模式:将 canonical MediaRef(`data.media`,kind=base64)物化为本机路径,
|
|
52
|
-
* 并把 media 改写为 path
|
|
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,
|