@zhin.js/adapter-icqq 1.1.0 → 1.1.3
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 +107 -0
- package/README.md +15 -6
- package/adapters/{icqq.js → icqq/index.js} +6 -6
- package/adapters/{icqq.ts → icqq/index.ts} +10 -10
- package/agents/icqq/agent.json +20 -0
- package/agents/icqq/boundaries.md +3 -0
- package/agents/icqq/conventions.md +3 -0
- package/agents/icqq/skills/icqq-directory/SKILL.md +20 -0
- package/{tools/friend_list.js → agents/icqq/skills/icqq-directory/tools/friend_list/index.js} +2 -1
- package/{tools/friend_list.ts → agents/icqq/skills/icqq-directory/tools/friend_list/index.ts} +2 -1
- package/{tools/group_list.js → agents/icqq/skills/icqq-directory/tools/group_list/index.js} +2 -1
- package/{tools/group_list.ts → agents/icqq/skills/icqq-directory/tools/group_list/index.ts} +2 -1
- package/{tools/group_member_info.js → agents/icqq/skills/icqq-directory/tools/group_member_info/index.js} +3 -1
- package/{tools/group_member_info.ts → agents/icqq/skills/icqq-directory/tools/group_member_info/index.ts} +3 -1
- package/{tools/user_info.js → agents/icqq/skills/icqq-directory/tools/user_info/index.js} +1 -0
- package/{tools/user_info.ts → agents/icqq/skills/icqq-directory/tools/user_info/index.ts} +1 -0
- package/agents/icqq/skills/icqq-group-admin/SKILL.md +25 -0
- package/{tools/announce.js → agents/icqq/skills/icqq-group-admin/tools/announce/index.js} +2 -0
- package/{tools/announce.ts → agents/icqq/skills/icqq-group-admin/tools/announce/index.ts} +2 -0
- package/{tools/essence.js → agents/icqq/skills/icqq-group-admin/tools/essence/index.js} +3 -1
- package/{tools/essence.ts → agents/icqq/skills/icqq-group-admin/tools/essence/index.ts} +3 -1
- package/{tools/group_invite.js → agents/icqq/skills/icqq-group-admin/tools/group_invite/index.js} +1 -1
- package/{tools/group_invite.ts → agents/icqq/skills/icqq-group-admin/tools/group_invite/index.ts} +1 -1
- package/{tools/group_kick.js → agents/icqq/skills/icqq-group-admin/tools/group_kick/index.js} +4 -3
- package/{tools/group_kick.ts → agents/icqq/skills/icqq-group-admin/tools/group_kick/index.ts} +4 -3
- package/{tools/set_anonymous.js → agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.js} +3 -1
- package/{tools/set_anonymous.ts → agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.ts} +3 -1
- package/{tools/set_mute.js → agents/icqq/skills/icqq-group-admin/tools/set_mute/index.js} +3 -2
- package/{tools/set_mute.ts → agents/icqq/skills/icqq-group-admin/tools/set_mute/index.ts} +3 -2
- package/{tools/set_title.js → agents/icqq/skills/icqq-group-admin/tools/set_title/index.js} +3 -2
- package/{tools/set_title.ts → agents/icqq/skills/icqq-group-admin/tools/set_title/index.ts} +3 -2
- package/agents/icqq/skills/icqq-group-state/SKILL.md +17 -0
- package/{tools/group_files.js → agents/icqq/skills/icqq-group-state/tools/group_files/index.js} +3 -1
- package/{tools/group_files.ts → agents/icqq/skills/icqq-group-state/tools/group_files/index.ts} +3 -1
- package/{tools/list_muted.js → agents/icqq/skills/icqq-group-state/tools/list_muted/index.js} +2 -1
- package/{tools/list_muted.ts → agents/icqq/skills/icqq-group-state/tools/list_muted/index.ts} +2 -1
- package/agents/icqq/skills/icqq-interaction/SKILL.md +19 -0
- package/{tools/group_sign.js → agents/icqq/skills/icqq-interaction/tools/group_sign/index.js} +2 -1
- package/{tools/group_sign.ts → agents/icqq/skills/icqq-interaction/tools/group_sign/index.ts} +2 -1
- package/{tools/poke.js → agents/icqq/skills/icqq-interaction/tools/poke/index.js} +2 -1
- package/{tools/poke.ts → agents/icqq/skills/icqq-interaction/tools/poke/index.ts} +2 -1
- package/{tools/send_user_like.js → agents/icqq/skills/icqq-interaction/tools/send_user_like/index.js} +4 -3
- package/{tools/send_user_like.ts → agents/icqq/skills/icqq-interaction/tools/send_user_like/index.ts} +3 -3
- package/agents/icqq/system.md +3 -0
- package/commands/{endpoint/add/[[id]].js → icqq/endpoint/add/[[id]]/index.js} +1 -1
- package/commands/icqq/endpoint/add/[[id]]/index.ts +3 -0
- package/{lib/icqq-endpoint-commands.js → commands/icqq/endpoint/definition.js} +10 -5
- package/{src/icqq-endpoint-commands.ts → commands/icqq/endpoint/definition.ts} +9 -5
- package/commands/{endpoint/list.js → icqq/endpoint/list/index.js} +1 -1
- package/commands/icqq/endpoint/list/index.ts +3 -0
- package/commands/{endpoint/remove/[id].js → icqq/endpoint/remove/[id]/index.js} +1 -1
- package/commands/icqq/endpoint/remove/[id]/index.ts +3 -0
- package/lib/content-resolver.d.ts +10 -0
- package/lib/content-resolver.js +98 -0
- package/lib/cq-message.d.ts +1 -1
- package/lib/cq-message.js +1 -1
- package/lib/endpoint.d.ts +1 -2
- package/lib/endpoint.js +34 -77
- package/lib/icqq-inbound.js +40 -0
- package/lib/icqq-runtime-state.js +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/outbound-media.d.ts +1 -1
- package/lib/outbound-media.js +1 -1
- package/lib/protocol.d.ts +4 -22
- package/lib/protocol.js +52 -55
- package/lib/types.d.ts +0 -6
- package/package.json +29 -23
- package/plugin.js +1 -1
- package/prompt-sections/platform/index.js +16 -0
- package/{agent/prompt-sections/platform.ts → prompt-sections/platform/index.ts} +3 -4
- package/schema.json +7 -1
- package/src/content-resolver.ts +131 -0
- package/src/cq-message.ts +1 -1
- package/src/endpoint.ts +43 -97
- package/src/icqq-inbound.ts +39 -0
- package/src/icqq-runtime-state.ts +1 -1
- package/src/index.ts +1 -1
- package/src/outbound-media.ts +2 -2
- package/src/protocol.ts +56 -80
- package/src/types.ts +0 -7
- package/commands/endpoint/add/[[id]].ts +0 -3
- package/commands/endpoint/list.ts +0 -3
- package/commands/endpoint/remove/[id].ts +0 -3
- package/lib/icqq-endpoint-commands.d.ts +0 -1
- /package/{agent/PERMITS.md → PERMITS.md} +0 -0
package/lib/protocol.d.ts
CHANGED
|
@@ -161,9 +161,9 @@ export declare const Actions: {
|
|
|
161
161
|
readonly SET_NOTIFY: "set_notify";
|
|
162
162
|
readonly GET_NOTIFY: "get_notify";
|
|
163
163
|
};
|
|
164
|
-
/**
|
|
165
|
-
export interface
|
|
166
|
-
readonly id
|
|
164
|
+
/** One expanded ICQQ endpoint configuration produced by AdapterIndex. */
|
|
165
|
+
export interface IcqqEndpointConfig {
|
|
166
|
+
readonly id: string;
|
|
167
167
|
readonly autoReconnect?: boolean;
|
|
168
168
|
readonly outboundMedia?: 'file' | 'base64';
|
|
169
169
|
readonly password?: string;
|
|
@@ -177,24 +177,6 @@ export interface IcqqAdapterConfig {
|
|
|
177
177
|
readonly autoServer?: boolean;
|
|
178
178
|
readonly qqnt?: boolean;
|
|
179
179
|
readonly ntLogin?: boolean;
|
|
180
|
-
/** Transitional: legacy root `endpoints[]` with `context: icqq`. */
|
|
181
|
-
readonly endpoints?: ReadonlyArray<{
|
|
182
|
-
readonly context?: string;
|
|
183
|
-
readonly id?: string;
|
|
184
|
-
readonly autoReconnect?: boolean;
|
|
185
|
-
readonly outboundMedia?: 'file' | 'base64';
|
|
186
|
-
readonly password?: string;
|
|
187
|
-
readonly platform?: number;
|
|
188
|
-
readonly ver?: string;
|
|
189
|
-
readonly dataDir?: string;
|
|
190
|
-
readonly signApiAddr?: string;
|
|
191
|
-
readonly ignoreSelf?: boolean;
|
|
192
|
-
readonly resend?: boolean;
|
|
193
|
-
readonly cacheGroupMember?: boolean;
|
|
194
|
-
readonly autoServer?: boolean;
|
|
195
|
-
readonly qqnt?: boolean;
|
|
196
|
-
readonly ntLogin?: boolean;
|
|
197
|
-
}>;
|
|
198
180
|
}
|
|
199
181
|
export interface ResolvedIcqqConfig {
|
|
200
182
|
readonly context: 'icqq';
|
|
@@ -251,7 +233,7 @@ export type ParsedIcqqSendTarget = {
|
|
|
251
233
|
readonly guildId: string;
|
|
252
234
|
readonly channelId: string;
|
|
253
235
|
};
|
|
254
|
-
export declare function resolveIcqqConfig(config
|
|
236
|
+
export declare function resolveIcqqConfig(config: IcqqEndpointConfig): ResolvedIcqqConfig;
|
|
255
237
|
/**
|
|
256
238
|
* 入站归一化 → ConversationRef:群临时会话(temp)映射为群容器内的 private
|
|
257
239
|
* 会话(parent kind 'group');频道消息的所属 guild 进 `parent`(kind 'channel')。
|
package/lib/protocol.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { pickCredential } from 'zhin.js/adapter';
|
|
2
1
|
import { resolveIcqqMediaFile } from "./cq-message.js";
|
|
3
2
|
export { resolveIcqqInboundMessageId } from "./icqq-inbound.js";
|
|
4
3
|
/**
|
|
@@ -172,25 +171,24 @@ export const Actions = {
|
|
|
172
171
|
SET_NOTIFY: "set_notify",
|
|
173
172
|
GET_NOTIFY: "get_notify",
|
|
174
173
|
};
|
|
175
|
-
export function resolveIcqqConfig(config
|
|
176
|
-
const
|
|
177
|
-
const id = pickCredential(config.id, entry?.id, process.env.ICQQ_ACCOUNT);
|
|
174
|
+
export function resolveIcqqConfig(config) {
|
|
175
|
+
const id = typeof config.id === 'string' ? config.id.trim() : '';
|
|
178
176
|
if (!/^\d+$/.test(id)) {
|
|
179
|
-
throw new TypeError('ICQQ
|
|
177
|
+
throw new TypeError('ICQQ endpoint requires a numeric id (QQ uin)');
|
|
180
178
|
}
|
|
181
|
-
const autoReconnect = config.autoReconnect ??
|
|
182
|
-
const outboundMedia = config.outboundMedia
|
|
183
|
-
const password = config.password
|
|
184
|
-
const platform = config.platform ??
|
|
185
|
-
const ver = config.ver ??
|
|
186
|
-
const dataDir = config.dataDir ??
|
|
187
|
-
const signApiAddr = config.signApiAddr
|
|
188
|
-
const ignoreSelf = config.ignoreSelf ??
|
|
189
|
-
const resend = config.resend
|
|
190
|
-
const cacheGroupMember = config.cacheGroupMember ??
|
|
191
|
-
const autoServer = config.autoServer ??
|
|
192
|
-
const qqnt = config.qqnt
|
|
193
|
-
const ntLogin = config.ntLogin
|
|
179
|
+
const autoReconnect = config.autoReconnect ?? true;
|
|
180
|
+
const outboundMedia = config.outboundMedia;
|
|
181
|
+
const password = config.password;
|
|
182
|
+
const platform = config.platform ?? 2;
|
|
183
|
+
const ver = config.ver ?? '9.1.70';
|
|
184
|
+
const dataDir = config.dataDir ?? `data/icqq/${id}`;
|
|
185
|
+
const signApiAddr = config.signApiAddr;
|
|
186
|
+
const ignoreSelf = config.ignoreSelf ?? true;
|
|
187
|
+
const resend = config.resend;
|
|
188
|
+
const cacheGroupMember = config.cacheGroupMember ?? true;
|
|
189
|
+
const autoServer = config.autoServer ?? true;
|
|
190
|
+
const qqnt = config.qqnt;
|
|
191
|
+
const ntLogin = config.ntLogin;
|
|
194
192
|
return {
|
|
195
193
|
context: 'icqq',
|
|
196
194
|
id,
|
|
@@ -262,6 +260,7 @@ const ICQQ_STANDALONE_ELEMENTS = new Set([
|
|
|
262
260
|
'video',
|
|
263
261
|
'file',
|
|
264
262
|
'json',
|
|
263
|
+
'share',
|
|
265
264
|
'long_msg',
|
|
266
265
|
]);
|
|
267
266
|
/**
|
|
@@ -356,7 +355,7 @@ export function formatOutboundBody(payload) {
|
|
|
356
355
|
return { type: 'long_msg', resid };
|
|
357
356
|
}
|
|
358
357
|
case 'share':
|
|
359
|
-
return
|
|
358
|
+
return formatShare(item.data);
|
|
360
359
|
default: {
|
|
361
360
|
const text = item.data?.text;
|
|
362
361
|
if (typeof text === 'string' && text)
|
|
@@ -382,44 +381,42 @@ function isWireSegment(value) {
|
|
|
382
381
|
&& value !== null
|
|
383
382
|
&& typeof value.type === 'string';
|
|
384
383
|
}
|
|
385
|
-
function
|
|
384
|
+
function formatShare(data) {
|
|
386
385
|
if (!data)
|
|
387
386
|
throw new TypeError('ICQQ share segment requires data');
|
|
388
387
|
const config = data.config;
|
|
389
|
-
const title = String(data.title ?? '');
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
},
|
|
388
|
+
const title = String(data.title ?? '').trim();
|
|
389
|
+
const url = String(data.url ?? '').trim();
|
|
390
|
+
if (!title)
|
|
391
|
+
throw new TypeError('ICQQ share segment requires title');
|
|
392
|
+
if (!url)
|
|
393
|
+
throw new TypeError('ICQQ share segment requires url');
|
|
394
|
+
const optionalString = (value) => {
|
|
395
|
+
const text = String(value ?? '').trim();
|
|
396
|
+
return text || undefined;
|
|
397
|
+
};
|
|
398
|
+
const appid = Number(config?.appid);
|
|
399
|
+
const appname = optionalString(config?.package);
|
|
400
|
+
const appsign = optionalString(config?.sign);
|
|
401
|
+
const shareConfig = Number.isSafeInteger(appid) && appid > 0
|
|
402
|
+
? {
|
|
403
|
+
appid,
|
|
404
|
+
...(appname ? { appname } : {}),
|
|
405
|
+
...(appsign ? { appsign } : {}),
|
|
406
|
+
}
|
|
407
|
+
: undefined;
|
|
408
|
+
const summary = optionalString(data.description) ?? optionalString(data.artist);
|
|
409
|
+
const content = optionalString(data.content);
|
|
410
|
+
const image = optionalString(data.image);
|
|
411
|
+
const audio = optionalString(data.audio);
|
|
412
|
+
return {
|
|
413
|
+
type: 'share',
|
|
414
|
+
title,
|
|
415
|
+
url,
|
|
416
|
+
...(summary ? { summary } : {}),
|
|
417
|
+
...(content ? { content } : {}),
|
|
418
|
+
...(image ? { image } : {}),
|
|
419
|
+
...(audio ? { audio } : {}),
|
|
420
|
+
...(shareConfig ? { config: shareConfig } : {}),
|
|
423
421
|
};
|
|
424
|
-
return { type: 'json', data: json };
|
|
425
422
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -9,12 +9,6 @@ export interface IcqqSenderInfo {
|
|
|
9
9
|
card?: string;
|
|
10
10
|
title?: string;
|
|
11
11
|
}
|
|
12
|
-
export interface IcqqEndpointConfig {
|
|
13
|
-
context: "icqq";
|
|
14
|
-
name: `${number}`;
|
|
15
|
-
autoReconnect?: boolean;
|
|
16
|
-
outboundMedia?: "file" | "base64";
|
|
17
|
-
}
|
|
18
12
|
export interface FriendInfo {
|
|
19
13
|
user_id: number;
|
|
20
14
|
nickname: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-icqq",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "Zhin.js ICQQ adapter — direct connection to @icqqjs/icqq",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -34,31 +34,29 @@
|
|
|
34
34
|
"directory": "plugins/adapters/icqq"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@zhin.js/adapter": "1.1.
|
|
38
|
-
"@zhin.js/
|
|
39
|
-
"@zhin.js/
|
|
40
|
-
"@zhin.js/
|
|
41
|
-
"@zhin.js/
|
|
42
|
-
"@zhin.js/
|
|
43
|
-
"@zhin.js/
|
|
37
|
+
"@zhin.js/adapter": "1.1.15",
|
|
38
|
+
"@zhin.js/agent-feature": "1.1.2",
|
|
39
|
+
"@zhin.js/core": "1.1.38",
|
|
40
|
+
"@zhin.js/feature-kit": "1.1.2",
|
|
41
|
+
"@zhin.js/im-contract": "1.1.2",
|
|
42
|
+
"@zhin.js/logger": "1.1.1",
|
|
43
|
+
"@zhin.js/permission": "1.1.2",
|
|
44
|
+
"@zhin.js/skill": "1.1.2",
|
|
45
|
+
"@zhin.js/tool": "1.1.2"
|
|
44
46
|
},
|
|
45
47
|
"peerDependencies": {
|
|
46
48
|
"@icqqjs/icqq": "^1.12.2",
|
|
47
49
|
"zod": "^4.0.0",
|
|
48
|
-
"@zhin.js/adapter": "^1.1.
|
|
49
|
-
"@zhin.js/
|
|
50
|
-
"@zhin.js/
|
|
51
|
-
"@zhin.js/
|
|
52
|
-
"
|
|
53
|
-
"zhin.js": "^1.1.0"
|
|
50
|
+
"@zhin.js/adapter": "^1.1.15",
|
|
51
|
+
"@zhin.js/command": "^1.1.2",
|
|
52
|
+
"@zhin.js/core": "^1.1.38",
|
|
53
|
+
"@zhin.js/prompt-section": "^1.1.2",
|
|
54
|
+
"zhin.js": "^1.1.3"
|
|
54
55
|
},
|
|
55
56
|
"peerDependenciesMeta": {
|
|
56
57
|
"@icqqjs/icqq": {
|
|
57
58
|
"optional": true
|
|
58
59
|
},
|
|
59
|
-
"@zhin.js/agent": {
|
|
60
|
-
"optional": true
|
|
61
|
-
},
|
|
62
60
|
"@zhin.js/command": {
|
|
63
61
|
"optional": true
|
|
64
62
|
},
|
|
@@ -78,9 +76,8 @@
|
|
|
78
76
|
"typescript": "^6.0.3",
|
|
79
77
|
"vitest": "^4.1.10",
|
|
80
78
|
"zod": "^4.4.3",
|
|
81
|
-
"@zhin.js/
|
|
82
|
-
"
|
|
83
|
-
"zhin.js": "1.1.0"
|
|
79
|
+
"@zhin.js/prompt-section": "1.1.2",
|
|
80
|
+
"zhin.js": "1.1.3"
|
|
84
81
|
},
|
|
85
82
|
"files": [
|
|
86
83
|
"adapters",
|
|
@@ -89,10 +86,11 @@
|
|
|
89
86
|
"schema.json",
|
|
90
87
|
"src",
|
|
91
88
|
"lib",
|
|
92
|
-
"
|
|
93
|
-
"tools",
|
|
89
|
+
"agents",
|
|
94
90
|
"README.md",
|
|
95
|
-
"CHANGELOG.md"
|
|
91
|
+
"CHANGELOG.md",
|
|
92
|
+
"PERMITS.md",
|
|
93
|
+
"prompt-sections"
|
|
96
94
|
],
|
|
97
95
|
"publishConfig": {
|
|
98
96
|
"access": "public",
|
|
@@ -119,6 +117,14 @@
|
|
|
119
117
|
{
|
|
120
118
|
"package": "@zhin.js/tool",
|
|
121
119
|
"api": "^1.0.0"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"package": "@zhin.js/skill",
|
|
123
|
+
"api": "^1.0.0"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"package": "@zhin.js/agent-feature",
|
|
127
|
+
"api": "^1.0.0"
|
|
122
128
|
}
|
|
123
129
|
],
|
|
124
130
|
"plugins": []
|
package/plugin.js
CHANGED
|
@@ -9,7 +9,7 @@ export default definePlugin({
|
|
|
9
9
|
displayName: 'ICQQ Adapter',
|
|
10
10
|
},
|
|
11
11
|
setup(context) {
|
|
12
|
-
// 运行中 endpoint 注册表(icqq
|
|
12
|
+
// 运行中 endpoint 注册表(icqq endpoint list 的"运行中"数据源)
|
|
13
13
|
context.resources.provide(icqqRuntimeStateToken, createEndpointRuntimeState());
|
|
14
14
|
const disposePlatform = context.resources.has(permissionHostToken)
|
|
15
15
|
? context.resources.use(permissionHostToken).registerPlatform('icqq', createSceneRolePlatformChecker())
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineAgentPromptSection } from '@zhin.js/prompt-section';
|
|
3
|
+
export default defineAgentPromptSection({
|
|
4
|
+
title: 'ICQQ / QQ',
|
|
5
|
+
content: [
|
|
6
|
+
'ICQQ capabilities are grouped into directory, interaction, group-admin, and group-state Skills.',
|
|
7
|
+
'Use discover for the user intent, then load only the matching Skill before calling its private Tool.',
|
|
8
|
+
'The current IM operation selects the bot Client automatically; never guess user_id or group_id.',
|
|
9
|
+
].join('\n'),
|
|
10
|
+
layer: 'tools',
|
|
11
|
+
order: 70,
|
|
12
|
+
retention: 'preferred',
|
|
13
|
+
maxChars: 1200,
|
|
14
|
+
profiles: ['interactive'],
|
|
15
|
+
platforms: ['icqq'],
|
|
16
|
+
});
|
|
@@ -3,10 +3,9 @@ import { defineAgentPromptSection } from '@zhin.js/prompt-section';
|
|
|
3
3
|
export default defineAgentPromptSection({
|
|
4
4
|
title: 'ICQQ / QQ',
|
|
5
5
|
content: [
|
|
6
|
-
'
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
'Other social tools include icqq__poke and icqq__friend_list.',
|
|
6
|
+
'ICQQ capabilities are grouped into directory, interaction, group-admin, and group-state Skills.',
|
|
7
|
+
'Use discover for the user intent, then load only the matching Skill before calling its private Tool.',
|
|
8
|
+
'The current IM operation selects the bot Client automatically; never guess user_id or group_id.',
|
|
10
9
|
].join('\n'),
|
|
11
10
|
layer: 'tools',
|
|
12
11
|
order: 70,
|
package/schema.json
CHANGED
|
@@ -76,13 +76,15 @@
|
|
|
76
76
|
},
|
|
77
77
|
"endpoints": {
|
|
78
78
|
"type": "array",
|
|
79
|
+
"minItems": 1,
|
|
79
80
|
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
80
81
|
"items": {
|
|
81
82
|
"type": "object",
|
|
82
|
-
"additionalProperties":
|
|
83
|
+
"additionalProperties": false,
|
|
83
84
|
"properties": {
|
|
84
85
|
"id": {
|
|
85
86
|
"type": "string",
|
|
87
|
+
"pattern": "^[0-9]+$",
|
|
86
88
|
"description": "QQ uin"
|
|
87
89
|
},
|
|
88
90
|
"master": {
|
|
@@ -147,6 +149,10 @@
|
|
|
147
149
|
"ntLogin": {
|
|
148
150
|
"type": "boolean",
|
|
149
151
|
"description": "是否使用 NT 登录方式"
|
|
152
|
+
},
|
|
153
|
+
"commandPrefix": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"description": "本 endpoint 的命令前缀"
|
|
150
156
|
}
|
|
151
157
|
},
|
|
152
158
|
"required": ["id"]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { Client } from '@icqqjs/icqq';
|
|
2
|
+
import type {
|
|
3
|
+
EndpointContentPort,
|
|
4
|
+
EndpointContentResolveContext,
|
|
5
|
+
} from 'zhin.js/adapter';
|
|
6
|
+
import type {
|
|
7
|
+
ConversationMessage,
|
|
8
|
+
ConversationReference,
|
|
9
|
+
ForwardEntry,
|
|
10
|
+
Segment,
|
|
11
|
+
} from '@zhin.js/im-contract';
|
|
12
|
+
import { normalizeForwardMsgResponse } from './forward-msg.js';
|
|
13
|
+
|
|
14
|
+
/** Owns ICQQ-observed message content and recursive merged-forward expansion. */
|
|
15
|
+
export class IcqqContentResolver {
|
|
16
|
+
readonly #client: Client;
|
|
17
|
+
readonly #messages = new Map<string, ConversationMessage>();
|
|
18
|
+
|
|
19
|
+
readonly port: EndpointContentPort = Object.freeze({
|
|
20
|
+
resolve: (
|
|
21
|
+
reference: ConversationReference,
|
|
22
|
+
context: EndpointContentResolveContext,
|
|
23
|
+
) => this.#resolve(reference, context),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
constructor(client: Client) {
|
|
27
|
+
this.#client = client;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
remember(messageId: string, message: ConversationMessage): void {
|
|
31
|
+
this.#messages.set(messageId, message);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async #resolve(
|
|
35
|
+
reference: ConversationReference,
|
|
36
|
+
context: EndpointContentResolveContext,
|
|
37
|
+
) {
|
|
38
|
+
context.signal.throwIfAborted();
|
|
39
|
+
if (reference.kind === 'message') {
|
|
40
|
+
const cached = this.#messages.get(reference.message.id);
|
|
41
|
+
return cached
|
|
42
|
+
? Object.freeze({ status: 'resolved' as const, reference, value: cached })
|
|
43
|
+
: Object.freeze({ status: 'not_found' as const, code: 'icqq_message_not_observed' });
|
|
44
|
+
}
|
|
45
|
+
if (reference.kind === 'forward') {
|
|
46
|
+
try {
|
|
47
|
+
const entries = await this.#resolveForwardEntries(
|
|
48
|
+
reference.forwardId,
|
|
49
|
+
context,
|
|
50
|
+
{ remainingEntries: context.maxEntries, path: new Set<string>() },
|
|
51
|
+
0,
|
|
52
|
+
);
|
|
53
|
+
if (entries.length === 0) {
|
|
54
|
+
return Object.freeze({ status: 'not_found' as const, code: 'icqq_forward_not_found' });
|
|
55
|
+
}
|
|
56
|
+
return Object.freeze({
|
|
57
|
+
status: 'resolved' as const,
|
|
58
|
+
reference,
|
|
59
|
+
value: Object.freeze(entries),
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (context.signal.aborted) throw error;
|
|
63
|
+
return Object.freeze({
|
|
64
|
+
status: 'failed' as const,
|
|
65
|
+
code: 'icqq_forward_fetch_failed',
|
|
66
|
+
message: error instanceof Error ? error.message : String(error),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (
|
|
71
|
+
reference.media.kind === 'url'
|
|
72
|
+
|| reference.media.kind === 'base64'
|
|
73
|
+
|| reference.media.kind === 'path'
|
|
74
|
+
) {
|
|
75
|
+
return Object.freeze({ status: 'resolved' as const, reference, value: reference.media });
|
|
76
|
+
}
|
|
77
|
+
return Object.freeze({
|
|
78
|
+
status: 'unsupported' as const,
|
|
79
|
+
code: 'icqq_media_reference_unsupported',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async #resolveForwardEntries(
|
|
84
|
+
forwardId: string,
|
|
85
|
+
context: EndpointContentResolveContext,
|
|
86
|
+
state: { remainingEntries: number; readonly path: Set<string> },
|
|
87
|
+
depth: number,
|
|
88
|
+
): Promise<readonly ForwardEntry[]> {
|
|
89
|
+
context.signal.throwIfAborted();
|
|
90
|
+
if (state.remainingEntries <= 0 || state.path.has(forwardId)) return Object.freeze([]);
|
|
91
|
+
state.path.add(forwardId);
|
|
92
|
+
try {
|
|
93
|
+
// ICQQ has no cancellable getForwardMsg API. Keep the generation lease
|
|
94
|
+
// until the native operation settles, then honor cancellation.
|
|
95
|
+
const raw = await this.#client.getForwardMsg(forwardId);
|
|
96
|
+
context.signal.throwIfAborted();
|
|
97
|
+
const entries = normalizeForwardMsgResponse(raw).slice(0, state.remainingEntries);
|
|
98
|
+
state.remainingEntries -= entries.length;
|
|
99
|
+
const expanded: ForwardEntry[] = [];
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
context.signal.throwIfAborted();
|
|
102
|
+
const segments: Segment[] = [];
|
|
103
|
+
for (const segment of entry.segments) {
|
|
104
|
+
if (segment.type !== 'forward' || depth >= context.maxDepth) {
|
|
105
|
+
segments.push(segment);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const nestedId = String(
|
|
109
|
+
(segment.data as { forward_id?: unknown }).forward_id ?? '',
|
|
110
|
+
).trim();
|
|
111
|
+
if (!nestedId || state.path.has(nestedId) || state.remainingEntries <= 0) {
|
|
112
|
+
segments.push(segment);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const nested = await this.#resolveForwardEntries(nestedId, context, state, depth + 1);
|
|
116
|
+
segments.push(Object.freeze({
|
|
117
|
+
...segment,
|
|
118
|
+
data: Object.freeze({
|
|
119
|
+
...segment.data,
|
|
120
|
+
...(nested.length > 0 ? { entries: nested } : {}),
|
|
121
|
+
}),
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
expanded.push(Object.freeze({ ...entry, segments: Object.freeze(segments) }));
|
|
125
|
+
}
|
|
126
|
+
return Object.freeze(expanded);
|
|
127
|
+
} finally {
|
|
128
|
+
state.path.delete(forwardId);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
package/src/cq-message.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** ICQQ 历史文本视图 → canonical MessageSegment。出站只使用原生 Sendable。 */
|
|
2
|
-
import { isMediaRef, type MediaRef } from
|
|
2
|
+
import { isMediaRef, type MediaRef } from '@zhin.js/im-contract';
|
|
3
3
|
import type { MessageSegment } from "zhin.js";
|
|
4
4
|
|
|
5
5
|
const MAX_CQ_PARSE_LEN = 256_000;
|