@zhin.js/adapter-icqq 8.0.13 → 9.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 +58 -0
- package/README.md +8 -4
- package/adapters/icqq.js +3 -55
- package/adapters/icqq.ts +12 -62
- package/lib/cq-message.d.ts +5 -13
- package/lib/cq-message.js +6 -72
- package/lib/endpoint.d.ts +9 -10
- package/lib/endpoint.js +429 -141
- package/lib/forward-msg.d.ts +3 -22
- package/lib/forward-msg.js +38 -307
- package/lib/icqq-inbound.d.ts +29 -3
- package/lib/icqq-inbound.js +95 -12
- package/lib/icqq-login-assist.d.ts +27 -0
- package/lib/icqq-login-assist.js +109 -0
- package/lib/index.d.ts +1 -1
- package/lib/outbound-media.d.ts +6 -2
- package/lib/outbound-media.js +15 -8
- package/lib/protocol.d.ts +10 -1
- package/lib/protocol.js +82 -20
- package/package.json +13 -13
- package/src/cq-message.ts +7 -74
- package/src/endpoint.ts +463 -153
- package/src/forward-msg.ts +35 -342
- package/src/icqq-inbound.ts +112 -13
- package/src/icqq-login-assist.ts +132 -0
- package/src/index.ts +0 -1
- package/src/outbound-media.ts +20 -5
- package/src/protocol.ts +82 -22
- package/tools/announce.js +1 -1
- package/tools/announce.ts +1 -1
- package/tools/essence.js +1 -1
- package/tools/essence.ts +1 -1
- package/tools/friend_list.js +5 -4
- package/tools/friend_list.ts +7 -5
- package/tools/group_list.js +6 -5
- package/tools/group_list.ts +8 -6
- package/tools/group_member_info.js +27 -0
- package/tools/group_member_info.ts +30 -0
- package/tools/set_mute.js +1 -1
- package/tools/set_mute.ts +1 -1
- package/tools/set_title.js +1 -1
- package/tools/set_title.ts +1 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { LoginAssist, LoginAssistType, PendingLoginTaskPayload } from '@zhin.js/core';
|
|
2
|
+
import { formatCompact } from '@zhin.js/logger';
|
|
3
|
+
|
|
4
|
+
type IcqqLoginClient = {
|
|
5
|
+
login(password?: string): Promise<unknown>;
|
|
6
|
+
submitSlider(ticket: string): Promise<unknown>;
|
|
7
|
+
sendSmsCode(): Promise<unknown>;
|
|
8
|
+
submitSmsCode(code: string): Promise<unknown>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type Logger = {
|
|
12
|
+
info(message: string): void;
|
|
13
|
+
warn(message: string): void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Drive ICQQ login challenges through LoginAssist (refresh-safe pending tasks).
|
|
18
|
+
* Mirrors the classic stdin flow: qrcode/auth → login(); slider → submitSlider;
|
|
19
|
+
* device → sendSmsCode then submitSmsCode.
|
|
20
|
+
*/
|
|
21
|
+
export async function runIcqqLoginAssistStep(input: {
|
|
22
|
+
assist: LoginAssist;
|
|
23
|
+
client: IcqqLoginClient;
|
|
24
|
+
adapter: string;
|
|
25
|
+
endpointKey: string;
|
|
26
|
+
owner: object;
|
|
27
|
+
logger: Logger;
|
|
28
|
+
step: LoginAssistType;
|
|
29
|
+
event: unknown;
|
|
30
|
+
}): Promise<void> {
|
|
31
|
+
const { assist, client, adapter, endpointKey, owner, logger, step, event } = input;
|
|
32
|
+
assist.cancelOwned(owner, `replaced_by_${step}`);
|
|
33
|
+
|
|
34
|
+
const payload = buildPayload(step, event);
|
|
35
|
+
logger.info(formatCompact({
|
|
36
|
+
op: 'login_assist_pending',
|
|
37
|
+
endpoint: endpointKey,
|
|
38
|
+
type: step,
|
|
39
|
+
message: payload.message,
|
|
40
|
+
...(typeof payload.url === 'string' ? { url: payload.url } : {}),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
if (step === 'device') {
|
|
45
|
+
try {
|
|
46
|
+
await client.sendSmsCode();
|
|
47
|
+
} catch (error) {
|
|
48
|
+
logger.warn(formatCompact({
|
|
49
|
+
op: 'login_assist_send_sms_failed',
|
|
50
|
+
endpoint: endpointKey,
|
|
51
|
+
error: error instanceof Error ? error.message : String(error),
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const value = await assist.waitForInput(adapter, endpointKey, step, payload, { owner });
|
|
57
|
+
const text = normalizeSubmitValue(value);
|
|
58
|
+
|
|
59
|
+
switch (step) {
|
|
60
|
+
case 'qrcode':
|
|
61
|
+
case 'auth':
|
|
62
|
+
await client.login();
|
|
63
|
+
break;
|
|
64
|
+
case 'slider':
|
|
65
|
+
await client.submitSlider(text);
|
|
66
|
+
break;
|
|
67
|
+
case 'device':
|
|
68
|
+
case 'sms':
|
|
69
|
+
await client.submitSmsCode(text);
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
await client.login();
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
logger.warn(formatCompact({
|
|
77
|
+
op: 'login_assist_failed',
|
|
78
|
+
endpoint: endpointKey,
|
|
79
|
+
type: step,
|
|
80
|
+
error: error instanceof Error ? error.message : String(error),
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildPayload(step: LoginAssistType, event: unknown): PendingLoginTaskPayload {
|
|
86
|
+
const raw = event && typeof event === 'object' ? event as Record<string, unknown> : {};
|
|
87
|
+
switch (step) {
|
|
88
|
+
case 'qrcode':
|
|
89
|
+
return {
|
|
90
|
+
message: '扫码登录:请在手机 QQ 扫描二维码,完成后在 Console / 终端确认继续',
|
|
91
|
+
image: bufferToDataUrl(raw.image),
|
|
92
|
+
};
|
|
93
|
+
case 'slider':
|
|
94
|
+
return {
|
|
95
|
+
message: '滑动验证:打开链接完成滑块,将返回的 ticket(可含 randstr)提交继续',
|
|
96
|
+
url: typeof raw.url === 'string' ? raw.url : undefined,
|
|
97
|
+
};
|
|
98
|
+
case 'device':
|
|
99
|
+
return {
|
|
100
|
+
message: '设备锁:已请求短信验证码,请提交短信验证码继续',
|
|
101
|
+
url: typeof raw.url === 'string' ? raw.url : undefined,
|
|
102
|
+
phone: raw.phone,
|
|
103
|
+
};
|
|
104
|
+
case 'auth':
|
|
105
|
+
return {
|
|
106
|
+
message: '身份验证:完成后在 Console / 终端确认继续',
|
|
107
|
+
url: typeof raw.url === 'string' ? raw.url : undefined,
|
|
108
|
+
device: raw.device,
|
|
109
|
+
};
|
|
110
|
+
default:
|
|
111
|
+
return { message: `登录辅助(${step})` };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizeSubmitValue(value: string | Record<string, unknown>): string {
|
|
116
|
+
if (typeof value === 'string') return value.trim();
|
|
117
|
+
if (typeof value.ticket === 'string') return value.ticket.trim();
|
|
118
|
+
if (typeof value.code === 'string') return value.code.trim();
|
|
119
|
+
if (typeof value.value === 'string') return value.value.trim();
|
|
120
|
+
return JSON.stringify(value);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function bufferToDataUrl(image: unknown): string | undefined {
|
|
124
|
+
if (Buffer.isBuffer(image)) {
|
|
125
|
+
return `data:image/png;base64,${image.toString('base64')}`;
|
|
126
|
+
}
|
|
127
|
+
if (image instanceof Uint8Array) {
|
|
128
|
+
return `data:image/png;base64,${Buffer.from(image).toString('base64')}`;
|
|
129
|
+
}
|
|
130
|
+
if (typeof image === 'string' && image.trim()) return image.trim();
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
package/src/index.ts
CHANGED
package/src/outbound-media.ts
CHANGED
|
@@ -46,18 +46,26 @@ export function resolveIcqqOutboundMediaMode(
|
|
|
46
46
|
/**
|
|
47
47
|
* file 模式:将 canonical MediaRef(`data.media`,kind=base64)物化为本机路径,
|
|
48
48
|
* 并把 media 改写为 path 引用(icqq 客户端直接读盘)。
|
|
49
|
-
* base64 模式:不改动(由
|
|
49
|
+
* base64 模式:不改动(由 native Sendable projection 生成 base64:// file)。
|
|
50
50
|
*/
|
|
51
|
-
export
|
|
51
|
+
export interface PreparedIcqqOutboundMedia {
|
|
52
|
+
readonly content: SendContent;
|
|
53
|
+
dispose(): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function prepareIcqqOutboundMedia(
|
|
52
57
|
content: SendContent,
|
|
53
58
|
mode: IcqqOutboundMediaMode = "file",
|
|
54
|
-
):
|
|
55
|
-
if (mode === "base64") return content;
|
|
59
|
+
): PreparedIcqqOutboundMedia {
|
|
60
|
+
if (mode === "base64") return { content, dispose() {} };
|
|
56
61
|
|
|
62
|
+
const createdFiles: string[] = [];
|
|
63
|
+
const inputWasArray = Array.isArray(content);
|
|
57
64
|
const segments = Array.isArray(content) ? content : [content];
|
|
58
|
-
|
|
65
|
+
const materialized = segments.map((seg) => {
|
|
59
66
|
if (typeof seg === "string") return seg;
|
|
60
67
|
const { type, data } = seg as MessageSegment;
|
|
68
|
+
if (!data || typeof data !== "object") return seg;
|
|
61
69
|
const d = data as Record<string, unknown>;
|
|
62
70
|
const media = d.media;
|
|
63
71
|
if (!isMediaRef(media) || media.kind !== "base64") return seg;
|
|
@@ -73,6 +81,7 @@ export function materializeOutboundBase64(
|
|
|
73
81
|
mime || (type === "image" ? "image/jpeg" : type === "video" ? "video/mp4" : "audio/mpeg"),
|
|
74
82
|
type === "record" || type === "audio" ? "audio" : type,
|
|
75
83
|
);
|
|
84
|
+
createdFiles.push(filePath);
|
|
76
85
|
return {
|
|
77
86
|
type,
|
|
78
87
|
data: {
|
|
@@ -85,4 +94,10 @@ export function materializeOutboundBase64(
|
|
|
85
94
|
},
|
|
86
95
|
} as MessageSegment;
|
|
87
96
|
});
|
|
97
|
+
return {
|
|
98
|
+
content: inputWasArray ? materialized : materialized[0]!,
|
|
99
|
+
dispose() {
|
|
100
|
+
for (const filePath of createdFiles) fs.rmSync(filePath, { force: true });
|
|
101
|
+
},
|
|
102
|
+
};
|
|
88
103
|
}
|
package/src/protocol.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { pickCredential } from 'zhin.js/adapter';
|
|
2
|
+
import type { MessageElem, Sendable } from '@icqqjs/icqq';
|
|
2
3
|
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
3
4
|
import type { MessageSegment } from "zhin.js";
|
|
4
|
-
import {
|
|
5
|
+
import { resolveIcqqMediaFile } from "./cq-message.js";
|
|
5
6
|
|
|
6
7
|
export type ActionResult = {
|
|
7
8
|
id: string;
|
|
@@ -301,6 +302,7 @@ export interface IcqqInboundMessage {
|
|
|
301
302
|
readonly sender: { readonly id: string; readonly name?: string };
|
|
302
303
|
readonly channelType: 'private' | 'group' | 'channel';
|
|
303
304
|
readonly metadata?: Record<string, unknown>;
|
|
305
|
+
readonly replyTo?: { readonly id: string };
|
|
304
306
|
}
|
|
305
307
|
|
|
306
308
|
export type ParsedIcqqSendTarget =
|
|
@@ -404,7 +406,20 @@ export function icqqOutboundTarget(conversation: ConversationRef): ParsedIcqqSen
|
|
|
404
406
|
return { kind: 'channel', guildId, channelId: conversation.id };
|
|
405
407
|
}
|
|
406
408
|
|
|
407
|
-
|
|
409
|
+
const ICQQ_STANDALONE_ELEMENTS = new Set([
|
|
410
|
+
'record',
|
|
411
|
+
'video',
|
|
412
|
+
'file',
|
|
413
|
+
'json',
|
|
414
|
+
'long_msg',
|
|
415
|
+
] as const);
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Project canonical Zhin segments directly into the native ICQQ Sendable
|
|
419
|
+
* algebra. ICQQ's direct client does not parse Zhin's historical `[image:x]`
|
|
420
|
+
* notation; passing that notation would send it as literal text.
|
|
421
|
+
*/
|
|
422
|
+
export function formatOutboundBody(payload: unknown): Sendable {
|
|
408
423
|
if (typeof payload === 'string') {
|
|
409
424
|
const text = payload.trim();
|
|
410
425
|
return text || '\u200b';
|
|
@@ -422,39 +437,84 @@ export function formatOutboundBody(payload: unknown): string {
|
|
|
422
437
|
const text = String(payload).trim();
|
|
423
438
|
return text || '\u200b';
|
|
424
439
|
}
|
|
425
|
-
const
|
|
440
|
+
const elements = payload.flatMap<string | MessageElem>((seg) => {
|
|
426
441
|
if (typeof seg === 'string') return seg;
|
|
427
442
|
const item = seg as IcqqWireSegment;
|
|
428
443
|
switch (item.type) {
|
|
429
444
|
case 'text':
|
|
430
445
|
return String(item.data?.text ?? '');
|
|
431
446
|
case 'at':
|
|
432
|
-
case 'mention':
|
|
433
|
-
|
|
447
|
+
case 'mention': {
|
|
448
|
+
const target = item.data?.qq ?? item.data?.id ?? item.data?.target;
|
|
449
|
+
if (target === 'all') return { type: 'at', qq: 'all' };
|
|
450
|
+
const qq = Number(target);
|
|
451
|
+
if (!Number.isSafeInteger(qq) || qq <= 0) {
|
|
452
|
+
throw new TypeError(`Invalid ICQQ mention target: ${String(target ?? '')}`);
|
|
453
|
+
}
|
|
454
|
+
return { type: 'at', qq };
|
|
455
|
+
}
|
|
434
456
|
case 'image': {
|
|
435
|
-
const arg =
|
|
436
|
-
|
|
457
|
+
const arg = resolveIcqqMediaFile(item.data);
|
|
458
|
+
if (!arg) throw new TypeError('ICQQ image segment requires canonical data.media');
|
|
459
|
+
return { type: 'image', file: arg };
|
|
460
|
+
}
|
|
461
|
+
case 'face': {
|
|
462
|
+
const id = Number(item.data?.id);
|
|
463
|
+
if (!Number.isSafeInteger(id) || id < 0) {
|
|
464
|
+
throw new TypeError(`Invalid ICQQ face id: ${String(item.data?.id ?? '')}`);
|
|
465
|
+
}
|
|
466
|
+
return { type: 'face', id };
|
|
467
|
+
}
|
|
468
|
+
case 'reply': {
|
|
469
|
+
const id = String(item.data?.message_id ?? item.data?.id ?? '').trim();
|
|
470
|
+
if (!id) throw new TypeError('ICQQ reply segment requires message_id');
|
|
471
|
+
return { type: 'reply', id };
|
|
437
472
|
}
|
|
438
|
-
case 'face':
|
|
439
|
-
return `[face:${item.data?.id ?? ''}]`;
|
|
440
|
-
case 'reply':
|
|
441
|
-
return `[reply:${item.data?.message_id ?? item.data?.id ?? ''}]`;
|
|
442
473
|
case 'record':
|
|
443
474
|
case 'audio': {
|
|
444
|
-
const arg =
|
|
445
|
-
|
|
475
|
+
const arg = resolveIcqqMediaFile(item.data);
|
|
476
|
+
if (!arg) throw new TypeError('ICQQ audio segment requires canonical data.media');
|
|
477
|
+
return { type: 'record', file: arg };
|
|
446
478
|
}
|
|
447
479
|
case 'video': {
|
|
448
|
-
const arg =
|
|
449
|
-
|
|
480
|
+
const arg = resolveIcqqMediaFile(item.data);
|
|
481
|
+
if (!arg) throw new TypeError('ICQQ video segment requires canonical data.media');
|
|
482
|
+
return { type: 'video', file: arg };
|
|
483
|
+
}
|
|
484
|
+
case 'file': {
|
|
485
|
+
const arg = resolveIcqqMediaFile(item.data);
|
|
486
|
+
if (!arg) throw new TypeError('ICQQ file segment requires canonical data.media');
|
|
487
|
+
const name = String(item.data?.name ?? '').trim();
|
|
488
|
+
return { type: 'file', file: arg, ...(name ? { name } : {}) };
|
|
489
|
+
}
|
|
490
|
+
case 'dice':
|
|
491
|
+
return { type: 'dice' };
|
|
492
|
+
case 'rps':
|
|
493
|
+
return { type: 'rps' };
|
|
494
|
+
case 'forward': {
|
|
495
|
+
const resid = String(item.data?.forward_id ?? item.data?.resid ?? item.data?.id ?? '').trim();
|
|
496
|
+
if (!resid) throw new TypeError('ICQQ forward segment requires forward_id');
|
|
497
|
+
return { type: 'long_msg', resid };
|
|
450
498
|
}
|
|
451
499
|
case 'share':
|
|
452
500
|
return formatMusicShareJson(item.data);
|
|
453
|
-
default:
|
|
454
|
-
|
|
501
|
+
default: {
|
|
502
|
+
const text = item.data?.text;
|
|
503
|
+
if (typeof text === 'string' && text) return text;
|
|
504
|
+
throw new TypeError(`Unsupported ICQQ outbound segment: ${item.type}`);
|
|
505
|
+
}
|
|
455
506
|
}
|
|
456
|
-
}).
|
|
457
|
-
|
|
507
|
+
}).filter((element) => typeof element !== 'string' || element.length > 0);
|
|
508
|
+
|
|
509
|
+
if (elements.length === 0) return '\u200b';
|
|
510
|
+
const standalone = elements.find(
|
|
511
|
+
(element): element is MessageElem =>
|
|
512
|
+
typeof element !== 'string' && ICQQ_STANDALONE_ELEMENTS.has(element.type as never),
|
|
513
|
+
);
|
|
514
|
+
if (standalone && elements.length !== 1) {
|
|
515
|
+
throw new TypeError(`ICQQ ${standalone.type} element must be sent as a standalone message`);
|
|
516
|
+
}
|
|
517
|
+
return elements.length === 1 ? elements[0]! : elements;
|
|
458
518
|
}
|
|
459
519
|
|
|
460
520
|
export function formatInboundContent(rawMessage: string): string {
|
|
@@ -468,8 +528,8 @@ function isWireSegment(value: unknown): value is IcqqWireSegment {
|
|
|
468
528
|
&& typeof (value as { type?: unknown }).type === 'string';
|
|
469
529
|
}
|
|
470
530
|
|
|
471
|
-
function formatMusicShareJson(data: Record<string, unknown> | undefined):
|
|
472
|
-
if (!data)
|
|
531
|
+
function formatMusicShareJson(data: Record<string, unknown> | undefined): MessageElem {
|
|
532
|
+
if (!data) throw new TypeError('ICQQ share segment requires data');
|
|
473
533
|
const config = data.config as {
|
|
474
534
|
appid?: number;
|
|
475
535
|
package?: string;
|
|
@@ -511,5 +571,5 @@ function formatMusicShareJson(data: Record<string, unknown> | undefined): string
|
|
|
511
571
|
},
|
|
512
572
|
},
|
|
513
573
|
};
|
|
514
|
-
return
|
|
574
|
+
return { type: 'json', data: json };
|
|
515
575
|
}
|
package/tools/announce.js
CHANGED
|
@@ -10,7 +10,7 @@ export default defineAgentTool({
|
|
|
10
10
|
content: z.string().describe('公告内容'),
|
|
11
11
|
}),
|
|
12
12
|
platforms: ['icqq'],
|
|
13
|
-
permissions: ['
|
|
13
|
+
permissions: ['role(master,admin,trusted,owner)'],
|
|
14
14
|
async execute({ endpoint_id, group_id, content }) {
|
|
15
15
|
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
16
16
|
await endpoint.sendGroupNotice(group_id, content);
|
package/tools/announce.ts
CHANGED
|
@@ -9,7 +9,7 @@ export default defineAgentTool<{ endpoint_id: string; group_id: number; content:
|
|
|
9
9
|
content: z.string().describe('公告内容'),
|
|
10
10
|
}),
|
|
11
11
|
platforms: ['icqq'],
|
|
12
|
-
permissions: ['
|
|
12
|
+
permissions: ['role(master,admin,trusted,owner)'],
|
|
13
13
|
async execute({ endpoint_id, group_id, content }: { endpoint_id: string; group_id: number; content: string }) {
|
|
14
14
|
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
15
15
|
await endpoint.sendGroupNotice(group_id, content);
|
package/tools/essence.js
CHANGED
|
@@ -10,7 +10,7 @@ export default defineAgentTool({
|
|
|
10
10
|
action: z.enum(['add', 'remove']).describe('add=设为精华, remove=移除精华'),
|
|
11
11
|
}),
|
|
12
12
|
platforms: ['icqq'],
|
|
13
|
-
|
|
13
|
+
approval: 'always',
|
|
14
14
|
async execute({ endpoint_id, message_id, action }) {
|
|
15
15
|
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
16
16
|
if (action === 'add') {
|
package/tools/essence.ts
CHANGED
|
@@ -9,7 +9,7 @@ export default defineAgentTool<{ endpoint_id: string; message_id: string; action
|
|
|
9
9
|
action: z.enum(['add', 'remove']).describe('add=设为精华, remove=移除精华'),
|
|
10
10
|
}),
|
|
11
11
|
platforms: ['icqq'],
|
|
12
|
-
|
|
12
|
+
approval: 'always',
|
|
13
13
|
async execute({ endpoint_id, message_id, action }: { endpoint_id: string; message_id: string; action: 'add' | 'remove' }) {
|
|
14
14
|
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
15
15
|
if (action === 'add') {
|
package/tools/friend_list.js
CHANGED
|
@@ -11,10 +11,11 @@ export default defineAgentTool({
|
|
|
11
11
|
approval: 'never',
|
|
12
12
|
async execute({ endpoint_id }) {
|
|
13
13
|
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const friends = Array.from(endpoint.fl.values()).map((friend) => ({
|
|
15
|
+
user_id: friend.user_id,
|
|
16
|
+
nickname: friend.nickname,
|
|
17
|
+
remark: friend.remark,
|
|
17
18
|
}));
|
|
18
|
-
return { friends: friends.slice(0, 50), count: fl.size };
|
|
19
|
+
return { friends: friends.slice(0, 50), count: endpoint.fl.size };
|
|
19
20
|
},
|
|
20
21
|
});
|
package/tools/friend_list.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { getIcqqAgentDeps } from '../src/icqq-agent-deps.js';
|
|
4
|
+
|
|
4
5
|
export default defineAgentTool<{ endpoint_id: string }>({
|
|
5
6
|
description: '获取 QQ 好友列表',
|
|
6
7
|
inputSchema: z.object({
|
|
@@ -8,12 +9,13 @@ export default defineAgentTool<{ endpoint_id: string }>({
|
|
|
8
9
|
}),
|
|
9
10
|
platforms: ['icqq'],
|
|
10
11
|
approval: 'never',
|
|
11
|
-
async execute({ endpoint_id }
|
|
12
|
+
async execute({ endpoint_id }) {
|
|
12
13
|
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const friends = Array.from(endpoint.fl.values()).map((friend) => ({
|
|
15
|
+
user_id: friend.user_id,
|
|
16
|
+
nickname: friend.nickname,
|
|
17
|
+
remark: friend.remark,
|
|
16
18
|
}));
|
|
17
|
-
return { friends: friends.slice(0, 50), count: fl.size };
|
|
19
|
+
return { friends: friends.slice(0, 50), count: endpoint.fl.size };
|
|
18
20
|
},
|
|
19
21
|
});
|
package/tools/group_list.js
CHANGED
|
@@ -11,11 +11,12 @@ export default defineAgentTool({
|
|
|
11
11
|
approval: 'never',
|
|
12
12
|
async execute({ endpoint_id }) {
|
|
13
13
|
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
member_count:
|
|
14
|
+
const groups = Array.from(endpoint.gl.values()).map((group) => ({
|
|
15
|
+
group_id: group.group_id,
|
|
16
|
+
group_name: group.group_name,
|
|
17
|
+
member_count: group.member_count,
|
|
18
|
+
max_member_count: group.max_member_count,
|
|
18
19
|
}));
|
|
19
|
-
return { groups: groups.slice(0, 50), count: gl.size };
|
|
20
|
+
return { groups: groups.slice(0, 50), count: endpoint.gl.size };
|
|
20
21
|
},
|
|
21
22
|
});
|
package/tools/group_list.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { getIcqqAgentDeps } from '../src/icqq-agent-deps.js';
|
|
4
|
+
|
|
4
5
|
export default defineAgentTool<{ endpoint_id: string }>({
|
|
5
6
|
description: '获取 Endpoint 的 QQ 群列表',
|
|
6
7
|
inputSchema: z.object({
|
|
@@ -8,13 +9,14 @@ export default defineAgentTool<{ endpoint_id: string }>({
|
|
|
8
9
|
}),
|
|
9
10
|
platforms: ['icqq'],
|
|
10
11
|
approval: 'never',
|
|
11
|
-
async execute({ endpoint_id }
|
|
12
|
+
async execute({ endpoint_id }) {
|
|
12
13
|
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
member_count:
|
|
14
|
+
const groups = Array.from(endpoint.gl.values()).map((group) => ({
|
|
15
|
+
group_id: group.group_id,
|
|
16
|
+
group_name: group.group_name,
|
|
17
|
+
member_count: group.member_count,
|
|
18
|
+
max_member_count: group.max_member_count,
|
|
17
19
|
}));
|
|
18
|
-
return { groups: groups.slice(0, 50), count: gl.size };
|
|
20
|
+
return { groups: groups.slice(0, 50), count: endpoint.gl.size };
|
|
19
21
|
},
|
|
20
22
|
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { getIcqqAgentDeps } from "../lib/icqq-agent-deps.js";
|
|
5
|
+
export default defineAgentTool({
|
|
6
|
+
description: '获取 QQ 群成员信息',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
endpoint_id: z.string().describe('Endpoint QQ号'),
|
|
9
|
+
group_id: z.number().describe('目标群号'),
|
|
10
|
+
user_id: z.number().describe('目标成员 QQ号'),
|
|
11
|
+
}),
|
|
12
|
+
platforms: ['icqq'],
|
|
13
|
+
approval: 'never',
|
|
14
|
+
async execute({ endpoint_id, group_id, user_id }) {
|
|
15
|
+
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
16
|
+
const group = endpoint.pickGroup(group_id);
|
|
17
|
+
if (!group)
|
|
18
|
+
return { success: false, message: '目标群不存在' };
|
|
19
|
+
const memberInfo = group.pickMember(user_id);
|
|
20
|
+
if (!memberInfo)
|
|
21
|
+
return { success: false, message: '目标成员不存在' };
|
|
22
|
+
return {
|
|
23
|
+
success: true,
|
|
24
|
+
member: memberInfo,
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getIcqqAgentDeps } from '../src/icqq-agent-deps.js';
|
|
4
|
+
interface GroupMemberInfoInput {
|
|
5
|
+
endpoint_id: string;
|
|
6
|
+
group_id: number;
|
|
7
|
+
user_id: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default defineAgentTool<GroupMemberInfoInput>({
|
|
11
|
+
description: '获取 QQ 群成员信息',
|
|
12
|
+
inputSchema: z.object({
|
|
13
|
+
endpoint_id: z.string().describe('Endpoint QQ号'),
|
|
14
|
+
group_id: z.number().describe('目标群号'),
|
|
15
|
+
user_id: z.number().describe('目标成员 QQ号'),
|
|
16
|
+
}),
|
|
17
|
+
platforms: ['icqq'],
|
|
18
|
+
approval: 'never',
|
|
19
|
+
async execute({ endpoint_id, group_id, user_id }) {
|
|
20
|
+
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
21
|
+
const group = endpoint.pickGroup(group_id);
|
|
22
|
+
if (!group) return { success: false, message: '目标群不存在' };
|
|
23
|
+
const memberInfo = group.pickMember(user_id);
|
|
24
|
+
if (!memberInfo) return { success: false, message: '目标成员不存在' };
|
|
25
|
+
return {
|
|
26
|
+
success: true,
|
|
27
|
+
member: memberInfo,
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
});
|
package/tools/set_mute.js
CHANGED
|
@@ -11,7 +11,7 @@ export default defineAgentTool({
|
|
|
11
11
|
duration: z.number().describe('禁言时间(秒),0=解除禁言').default(0),
|
|
12
12
|
}),
|
|
13
13
|
platforms: ['icqq'],
|
|
14
|
-
|
|
14
|
+
approval: 'always',
|
|
15
15
|
async execute({ endpoint_id, group_id, user_id, duration }) {
|
|
16
16
|
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
17
17
|
const selfInfo = endpoint.pickMember(group_id, endpoint.uin);
|
package/tools/set_mute.ts
CHANGED
|
@@ -10,7 +10,7 @@ export default defineAgentTool<{ endpoint_id: string; group_id: number; user_id:
|
|
|
10
10
|
duration: z.number().describe('禁言时间(秒),0=解除禁言').default(0),
|
|
11
11
|
}),
|
|
12
12
|
platforms: ['icqq'],
|
|
13
|
-
|
|
13
|
+
approval: 'always',
|
|
14
14
|
async execute({ endpoint_id, group_id, user_id, duration }: { endpoint_id: string; group_id: number; user_id: number; duration?: number }) {
|
|
15
15
|
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
16
16
|
const selfInfo= endpoint.pickMember(group_id,endpoint.uin);
|
package/tools/set_title.js
CHANGED
|
@@ -12,7 +12,7 @@ export default defineAgentTool({
|
|
|
12
12
|
duration: z.number().optional().describe('持续时间(秒),-1永久').default(-1),
|
|
13
13
|
}),
|
|
14
14
|
platforms: ['icqq'],
|
|
15
|
-
|
|
15
|
+
approval: 'always',
|
|
16
16
|
async execute({ endpoint_id, group_id, user_id, title, duration }) {
|
|
17
17
|
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
18
18
|
const selfInfo = endpoint.pickMember(group_id, endpoint.uin);
|
package/tools/set_title.ts
CHANGED
|
@@ -11,7 +11,7 @@ export default defineAgentTool<{ endpoint_id: string; group_id: number; user_id:
|
|
|
11
11
|
duration: z.number().optional().describe('持续时间(秒),-1永久').default(-1),
|
|
12
12
|
}),
|
|
13
13
|
platforms: ['icqq'],
|
|
14
|
-
|
|
14
|
+
approval: 'always',
|
|
15
15
|
async execute({ endpoint_id, group_id, user_id, title, duration }: { endpoint_id: string; group_id: number; user_id: number; title: string; duration?: number }) {
|
|
16
16
|
const endpoint = getIcqqAgentDeps().getEndpoint(endpoint_id);
|
|
17
17
|
const selfInfo= endpoint.pickMember(group_id,endpoint.uin);
|