@zhin.js/adapter-icqq 1.0.71 → 1.1.2
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 +1216 -52
- package/PERMITS.md +24 -0
- package/README.md +123 -429
- package/adapters/icqq/index.js +31 -0
- package/adapters/icqq/index.ts +42 -0
- 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/agents/icqq/skills/icqq-directory/tools/friend_list/index.js +19 -0
- package/agents/icqq/skills/icqq-directory/tools/friend_list/index.ts +19 -0
- package/agents/icqq/skills/icqq-directory/tools/group_list/index.js +20 -0
- package/agents/icqq/skills/icqq-directory/tools/group_list/index.ts +20 -0
- package/agents/icqq/skills/icqq-directory/tools/group_member_info/index.js +26 -0
- package/agents/icqq/skills/icqq-directory/tools/group_member_info/index.ts +28 -0
- package/agents/icqq/skills/icqq-directory/tools/user_info/index.js +15 -0
- package/agents/icqq/skills/icqq-directory/tools/user_info/index.ts +14 -0
- package/agents/icqq/skills/icqq-group-admin/SKILL.md +25 -0
- package/agents/icqq/skills/icqq-group-admin/tools/announce/index.js +18 -0
- package/agents/icqq/skills/icqq-group-admin/tools/announce/index.ts +17 -0
- package/agents/icqq/skills/icqq-group-admin/tools/essence/index.js +24 -0
- package/agents/icqq/skills/icqq-group-admin/tools/essence/index.ts +22 -0
- package/agents/icqq/skills/icqq-group-admin/tools/group_invite/index.js +23 -0
- package/agents/icqq/skills/icqq-group-admin/tools/group_invite/index.ts +20 -0
- package/agents/icqq/skills/icqq-group-admin/tools/group_kick/index.js +27 -0
- package/agents/icqq/skills/icqq-group-admin/tools/group_kick/index.ts +23 -0
- package/agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.js +23 -0
- package/agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.ts +21 -0
- package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.js +25 -0
- package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.ts +22 -0
- package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.js +26 -0
- package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.ts +23 -0
- package/agents/icqq/skills/icqq-group-state/SKILL.md +17 -0
- package/agents/icqq/skills/icqq-group-state/tools/group_files/index.js +24 -0
- package/agents/icqq/skills/icqq-group-state/tools/group_files/index.ts +22 -0
- package/agents/icqq/skills/icqq-group-state/tools/list_muted/index.js +18 -0
- package/agents/icqq/skills/icqq-group-state/tools/list_muted/index.ts +17 -0
- package/agents/icqq/skills/icqq-interaction/SKILL.md +19 -0
- package/agents/icqq/skills/icqq-interaction/tools/group_sign/index.js +20 -0
- package/agents/icqq/skills/icqq-interaction/tools/group_sign/index.ts +18 -0
- package/agents/icqq/skills/icqq-interaction/tools/poke/index.js +17 -0
- package/agents/icqq/skills/icqq-interaction/tools/poke/index.ts +16 -0
- package/agents/icqq/skills/icqq-interaction/tools/send_user_like/index.js +26 -0
- package/agents/icqq/skills/icqq-interaction/tools/send_user_like/index.ts +22 -0
- package/agents/icqq/system.md +3 -0
- package/commands/icqq/endpoint/add/[[id]]/index.js +3 -0
- package/commands/icqq/endpoint/add/[[id]]/index.ts +3 -0
- package/commands/icqq/endpoint/definition.js +31 -0
- package/commands/icqq/endpoint/definition.ts +32 -0
- package/commands/icqq/endpoint/list/index.js +3 -0
- package/commands/icqq/endpoint/list/index.ts +3 -0
- package/commands/icqq/endpoint/remove/[id]/index.js +3 -0
- package/commands/icqq/endpoint/remove/[id]/index.ts +3 -0
- package/lib/client.d.ts +19 -0
- package/lib/client.js +3 -0
- package/lib/content-resolver.d.ts +10 -0
- package/lib/content-resolver.js +98 -0
- package/lib/cq-message.d.ts +14 -0
- package/lib/cq-message.js +106 -0
- package/lib/endpoint.d.ts +30 -0
- package/lib/endpoint.js +1003 -0
- package/lib/forward-msg.d.ts +6 -0
- package/lib/forward-msg.js +107 -0
- package/lib/icqq-guild.d.ts +57 -0
- package/lib/icqq-guild.js +109 -0
- package/lib/icqq-inbound.d.ts +165 -0
- package/lib/icqq-inbound.js +651 -0
- package/lib/icqq-inbox.d.ts +25 -0
- package/lib/icqq-inbox.js +129 -0
- package/lib/icqq-login-assist.d.ts +27 -0
- package/lib/icqq-login-assist.js +109 -0
- package/lib/icqq-runtime-state.d.ts +1 -0
- package/lib/icqq-runtime-state.js +6 -0
- package/lib/index.d.ts +5 -18
- package/lib/index.js +5 -124
- package/lib/outbound-media.d.ts +14 -0
- package/lib/outbound-media.js +76 -0
- package/lib/protocol.d.ts +258 -0
- package/lib/protocol.js +422 -0
- package/lib/types.d.ts +33 -13
- package/lib/types.js +0 -1
- package/package.json +86 -32
- package/plugin.js +19 -0
- package/prompt-sections/platform/index.js +16 -0
- package/prompt-sections/platform/index.ts +16 -0
- package/schema.json +168 -0
- package/src/client.ts +29 -0
- package/src/content-resolver.ts +131 -0
- package/src/cq-message.ts +113 -0
- package/src/endpoint.ts +1084 -0
- package/src/forward-msg.ts +112 -0
- package/src/icqq-guild.ts +154 -0
- package/src/icqq-inbound.ts +814 -0
- package/src/icqq-inbox.ts +147 -0
- package/src/icqq-login-assist.ts +132 -0
- package/src/icqq-runtime-state.ts +7 -0
- package/src/index.ts +30 -149
- package/src/outbound-media.ts +103 -0
- package/src/protocol.ts +551 -0
- package/src/types.ts +35 -13
- package/LICENSE +0 -21
- package/client/ICQQManagement.tsx +0 -139
- package/client/LoginAssistPanel.tsx +0 -293
- package/client/index.tsx +0 -11
- package/client/tsconfig.json +0 -7
- package/client/utils/api.ts +0 -20
- package/dist/index.js +0 -1
- package/lib/adapter.d.ts +0 -34
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -396
- package/lib/adapter.js.map +0 -1
- package/lib/bot.d.ts +0 -46
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js +0 -457
- package/lib/bot.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js.map +0 -1
- package/skills/icqq/SKILL.md +0 -22
- package/src/adapter.ts +0 -448
- package/src/bot.ts +0 -521
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export default defineAgentTool({
|
|
5
|
+
description: '获取 QQ 群的群文件列表',
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
group_id: z.number().describe('群号'),
|
|
8
|
+
}),
|
|
9
|
+
adapter: 'icqq',
|
|
10
|
+
requiresApproval: 'once',
|
|
11
|
+
scopes: ['group'],
|
|
12
|
+
permissions: ['role(master,admin,trusted,owner)'],
|
|
13
|
+
async execute({ group_id }, context) {
|
|
14
|
+
const files = await context.$client.acquireGfs(group_id).ls('/');
|
|
15
|
+
if (!files.length)
|
|
16
|
+
return { files: [], message: '群文件为空' };
|
|
17
|
+
return {
|
|
18
|
+
files: files.slice(0, 30).map((f) => 'size' in f
|
|
19
|
+
? { name: f.name, is_dir: false, size: f.size, uploader: f.user_id, upload_time: f.create_time }
|
|
20
|
+
: { name: f.name, is_dir: true, file_count: f.file_count }),
|
|
21
|
+
count: files.length,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export default defineAgentTool<{ group_id: number }>({
|
|
4
|
+
description: '获取 QQ 群的群文件列表',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
group_id: z.number().describe('群号'),
|
|
7
|
+
}),
|
|
8
|
+
adapter: 'icqq',
|
|
9
|
+
requiresApproval: 'once',
|
|
10
|
+
scopes:['group'],
|
|
11
|
+
permissions: ['role(master,admin,trusted,owner)'],
|
|
12
|
+
async execute({ group_id }, context) {
|
|
13
|
+
const files = await context.$client.acquireGfs(group_id).ls('/');
|
|
14
|
+
if (!files.length) return { files: [], message: '群文件为空' };
|
|
15
|
+
return {
|
|
16
|
+
files: files.slice(0, 30).map((f) => 'size' in f
|
|
17
|
+
? { name: f.name, is_dir: false, size: f.size, uploader: f.user_id, upload_time: f.create_time }
|
|
18
|
+
: { name: f.name, is_dir: true, file_count: f.file_count }),
|
|
19
|
+
count: files.length,
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export default defineAgentTool({
|
|
5
|
+
description: '查询 QQ 群中当前被禁言的成员列表',
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
group_id: z.number().describe('目标群号'),
|
|
8
|
+
}),
|
|
9
|
+
adapter: 'icqq',
|
|
10
|
+
scopes: ['group'],
|
|
11
|
+
requiresApproval: 'never',
|
|
12
|
+
async execute({ group_id }, context) {
|
|
13
|
+
const members = await context.$client.getGroupMemberList(group_id);
|
|
14
|
+
const now = Math.floor(Date.now() / 1000);
|
|
15
|
+
const muted = Array.from(members.values()).filter((m) => m.shutup_time > now);
|
|
16
|
+
return { muted_members: muted, count: muted.length };
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export default defineAgentTool<{ group_id: number }>({
|
|
4
|
+
description: '查询 QQ 群中当前被禁言的成员列表',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
group_id: z.number().describe('目标群号'),
|
|
7
|
+
}),
|
|
8
|
+
adapter: 'icqq',
|
|
9
|
+
scopes:['group'],
|
|
10
|
+
requiresApproval: 'never',
|
|
11
|
+
async execute({ group_id }, context) {
|
|
12
|
+
const members = await context.$client.getGroupMemberList(group_id);
|
|
13
|
+
const now = Math.floor(Date.now() / 1000);
|
|
14
|
+
const muted = Array.from(members.values()).filter((m) => m.shutup_time > now);
|
|
15
|
+
return { muted_members: muted, count: muted.length };
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: icqq-interaction
|
|
3
|
+
platforms:
|
|
4
|
+
- icqq
|
|
5
|
+
description: ICQQ 点赞、戳一戳和群签到互动能力。
|
|
6
|
+
keywords:
|
|
7
|
+
- "icqq"
|
|
8
|
+
- "点赞"
|
|
9
|
+
- "戳一戳"
|
|
10
|
+
- "签到"
|
|
11
|
+
tools:
|
|
12
|
+
- "poke"
|
|
13
|
+
- "send_user_like"
|
|
14
|
+
- "group_sign"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# icqq-interaction
|
|
18
|
+
|
|
19
|
+
用于低风险社交互动。确认目标用户或群后再调用,并遵守各 Tool 的审批策略。
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export default defineAgentTool({
|
|
5
|
+
description: 'QQ 群签到/打卡',
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
group_id: z.number().describe('目标群号'),
|
|
8
|
+
}),
|
|
9
|
+
adapter: 'icqq',
|
|
10
|
+
scopes: ['group'],
|
|
11
|
+
requiresApproval: 'never',
|
|
12
|
+
async execute({ group_id }, context) {
|
|
13
|
+
const client = context.$client;
|
|
14
|
+
const groupInfo = client.pickGroup(group_id);
|
|
15
|
+
if (!groupInfo)
|
|
16
|
+
return { success: false, message: '目标群不存在' };
|
|
17
|
+
await client.sendGroupSign(group_id);
|
|
18
|
+
return { success: true, message: '群签到成功' };
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export default defineAgentTool<{ group_id: number }>({
|
|
4
|
+
description: 'QQ 群签到/打卡',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
group_id: z.number().describe('目标群号'),
|
|
7
|
+
}),
|
|
8
|
+
adapter: 'icqq',
|
|
9
|
+
scopes:['group'],
|
|
10
|
+
requiresApproval: 'never',
|
|
11
|
+
async execute({ group_id }, context) {
|
|
12
|
+
const client = context.$client;
|
|
13
|
+
const groupInfo= client.pickGroup(group_id);
|
|
14
|
+
if(!groupInfo) return { success: false, message: '目标群不存在' };
|
|
15
|
+
await client.sendGroupSign(group_id);
|
|
16
|
+
return { success: true, message: '群签到成功' };
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export default defineAgentTool({
|
|
5
|
+
description: '在 QQ 群中对某个成员执行戳一戳互动操作',
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
group_id: z.number().describe('目标群号'),
|
|
8
|
+
user_id: z.number().describe('要戳的目标成员 QQ号'),
|
|
9
|
+
}),
|
|
10
|
+
adapter: 'icqq',
|
|
11
|
+
scopes: ['group'],
|
|
12
|
+
requiresApproval: 'never',
|
|
13
|
+
async execute({ group_id, user_id }, context) {
|
|
14
|
+
await context.$client.sendGroupPoke(group_id, user_id);
|
|
15
|
+
return { success: true, message: `已戳了戳 ${user_id}` };
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export default defineAgentTool<{ group_id: number; user_id: number }>({
|
|
4
|
+
description: '在 QQ 群中对某个成员执行戳一戳互动操作',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
group_id: z.number().describe('目标群号'),
|
|
7
|
+
user_id: z.number().describe('要戳的目标成员 QQ号'),
|
|
8
|
+
}),
|
|
9
|
+
adapter: 'icqq',
|
|
10
|
+
scopes:['group'],
|
|
11
|
+
requiresApproval: 'never',
|
|
12
|
+
async execute({ group_id, user_id }, context) {
|
|
13
|
+
await context.$client.sendGroupPoke(group_id, user_id);
|
|
14
|
+
return { success: true, message: `已戳了戳 ${user_id}` };
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export default defineAgentTool({
|
|
5
|
+
description: '尝试给用户点尽量多的赞',
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
user_id: z.number().describe('要点赞的目标用户 QQ号'),
|
|
8
|
+
}),
|
|
9
|
+
adapter: 'icqq',
|
|
10
|
+
requiresApproval: 'never',
|
|
11
|
+
async execute({ user_id }, context) {
|
|
12
|
+
const successArray = [];
|
|
13
|
+
await Promise.all([20, 20, 10].map(async (times) => {
|
|
14
|
+
try {
|
|
15
|
+
const isSuccess = await context.$client.sendLike(user_id, times);
|
|
16
|
+
if (isSuccess)
|
|
17
|
+
successArray.push(times);
|
|
18
|
+
}
|
|
19
|
+
catch { /* ignore */ }
|
|
20
|
+
}));
|
|
21
|
+
const successTimes = successArray.reduce((a, b) => a + b, 0);
|
|
22
|
+
if (!successTimes)
|
|
23
|
+
return { success: false, message: '点赞失败' };
|
|
24
|
+
return { success: true, message: `已给 ${user_id} 点赞 ${successTimes} 次` };
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export default defineAgentTool<{ user_id: number }>({
|
|
4
|
+
description: '尝试给用户点尽量多的赞',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
user_id: z.number().describe('要点赞的目标用户 QQ号'),
|
|
7
|
+
}),
|
|
8
|
+
adapter: 'icqq',
|
|
9
|
+
requiresApproval: 'never',
|
|
10
|
+
async execute({ user_id }, context) {
|
|
11
|
+
const successArray: number[] = [];
|
|
12
|
+
await Promise.all([20, 20, 10].map(async (times) => {
|
|
13
|
+
try {
|
|
14
|
+
const isSuccess= await context.$client.sendLike(user_id, times);
|
|
15
|
+
if (isSuccess) successArray.push(times);
|
|
16
|
+
} catch { /* ignore */ }
|
|
17
|
+
}));
|
|
18
|
+
const successTimes = successArray.reduce((a, b) => a + b, 0);
|
|
19
|
+
if (!successTimes) return { success: false, message: '点赞失败' };
|
|
20
|
+
return { success: true, message: `已给 ${user_id} 点赞 ${successTimes} 次` };
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { createEndpointCommands, endpointConfigurationStoreToken } from 'zhin.js/adapter';
|
|
3
|
+
import { defineCommand } from 'zhin.js/command';
|
|
4
|
+
import { icqqRuntimeStateToken } from "../../../lib/icqq-runtime-state.js";
|
|
5
|
+
export const icqqEndpointCommands = createEndpointCommands({
|
|
6
|
+
adapterKey: 'icqq',
|
|
7
|
+
adapterDisplayName: 'ICQQ',
|
|
8
|
+
running: (use) => use(icqqRuntimeStateToken).endpoints.values(),
|
|
9
|
+
describeEntry: () => 'direct(直连 @icqqjs/icqq)',
|
|
10
|
+
addDescription: '登记 ICQQ endpoint(重启 zhin 生效)',
|
|
11
|
+
bindFlow: async ({ id, use }) => {
|
|
12
|
+
if (!id) {
|
|
13
|
+
return '用法:icqq endpoint add <uin>(uin 为纯数字 QQ 号)';
|
|
14
|
+
}
|
|
15
|
+
if (!/^\d+$/.test(id)) {
|
|
16
|
+
return 'icqq endpoint 名必须是纯数字 QQ 号(uin)';
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const { filePath } = await use(endpointConfigurationStoreToken).add({
|
|
20
|
+
adapterKey: 'icqq',
|
|
21
|
+
entry: { id },
|
|
22
|
+
environment: {},
|
|
23
|
+
});
|
|
24
|
+
return (`✅ endpoint「${id}」已追加到 ${filePath} 的 plugins.icqq.endpoints。\n` +
|
|
25
|
+
`重启 zhin 后生效。`);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return `添加失败:${error instanceof Error ? error.message : String(error)}`;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
}, defineCommand);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createEndpointCommands, endpointConfigurationStoreToken } from 'zhin.js/adapter';
|
|
2
|
+
import { defineCommand } from 'zhin.js/command';
|
|
3
|
+
import { icqqRuntimeStateToken } from '../../../src/icqq-runtime-state.js';
|
|
4
|
+
|
|
5
|
+
export const icqqEndpointCommands = createEndpointCommands({
|
|
6
|
+
adapterKey: 'icqq',
|
|
7
|
+
adapterDisplayName: 'ICQQ',
|
|
8
|
+
running: (use) => use(icqqRuntimeStateToken).endpoints.values(),
|
|
9
|
+
describeEntry: () => 'direct(直连 @icqqjs/icqq)',
|
|
10
|
+
addDescription: '登记 ICQQ endpoint(重启 zhin 生效)',
|
|
11
|
+
bindFlow: async ({ id, use }) => {
|
|
12
|
+
if (!id) {
|
|
13
|
+
return '用法:icqq endpoint add <uin>(uin 为纯数字 QQ 号)';
|
|
14
|
+
}
|
|
15
|
+
if (!/^\d+$/.test(id)) {
|
|
16
|
+
return 'icqq endpoint 名必须是纯数字 QQ 号(uin)';
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const { filePath } = await use(endpointConfigurationStoreToken).add({
|
|
20
|
+
adapterKey: 'icqq',
|
|
21
|
+
entry: { id },
|
|
22
|
+
environment: {},
|
|
23
|
+
});
|
|
24
|
+
return (
|
|
25
|
+
`✅ endpoint「${id}」已追加到 ${filePath} 的 plugins.icqq.endpoints。\n` +
|
|
26
|
+
`重启 zhin 后生效。`
|
|
27
|
+
);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return `添加失败:${error instanceof Error ? error.message : String(error)}`;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
}, defineCommand);
|
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Client, EventMap } from '@icqqjs/icqq';
|
|
2
|
+
type EventPayload<TListener> = TListener extends (event: infer TEvent, ...rest: readonly unknown[]) => unknown ? TEvent : never;
|
|
3
|
+
/** The actual ICQQ SDK instance exposed by every ICQQ Endpoint event. */
|
|
4
|
+
export type IcqqClient = Client;
|
|
5
|
+
/** Native ICQQ event-name → first public event payload, derived from the SDK SSOT. */
|
|
6
|
+
export type IcqqClientEventMap = {
|
|
7
|
+
readonly [TName in keyof EventMap]: EventPayload<EventMap[TName]>;
|
|
8
|
+
};
|
|
9
|
+
declare module '@zhin.js/feature-kit' {
|
|
10
|
+
interface AdapterClientRegistry {
|
|
11
|
+
readonly icqq: {
|
|
12
|
+
readonly client: IcqqClient;
|
|
13
|
+
readonly events: IcqqClientEventMap;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Typed, generation-checked identity used by handlers, commands, tools and schedules. */
|
|
18
|
+
export declare const icqqClient: import("@zhin.js/adapter").EndpointClientToken<Client, IcqqClientEventMap>;
|
|
19
|
+
export {};
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Client } from '@icqqjs/icqq';
|
|
2
|
+
import type { EndpointContentPort } from 'zhin.js/adapter';
|
|
3
|
+
import type { ConversationMessage } from '@zhin.js/im-contract';
|
|
4
|
+
/** Owns ICQQ-observed message content and recursive merged-forward expansion. */
|
|
5
|
+
export declare class IcqqContentResolver {
|
|
6
|
+
#private;
|
|
7
|
+
readonly port: EndpointContentPort;
|
|
8
|
+
constructor(client: Client);
|
|
9
|
+
remember(messageId: string, message: ConversationMessage): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { normalizeForwardMsgResponse } from './forward-msg.js';
|
|
2
|
+
/** Owns ICQQ-observed message content and recursive merged-forward expansion. */
|
|
3
|
+
export class IcqqContentResolver {
|
|
4
|
+
#client;
|
|
5
|
+
#messages = new Map();
|
|
6
|
+
port = Object.freeze({
|
|
7
|
+
resolve: (reference, context) => this.#resolve(reference, context),
|
|
8
|
+
});
|
|
9
|
+
constructor(client) {
|
|
10
|
+
this.#client = client;
|
|
11
|
+
}
|
|
12
|
+
remember(messageId, message) {
|
|
13
|
+
this.#messages.set(messageId, message);
|
|
14
|
+
}
|
|
15
|
+
async #resolve(reference, context) {
|
|
16
|
+
context.signal.throwIfAborted();
|
|
17
|
+
if (reference.kind === 'message') {
|
|
18
|
+
const cached = this.#messages.get(reference.message.id);
|
|
19
|
+
return cached
|
|
20
|
+
? Object.freeze({ status: 'resolved', reference, value: cached })
|
|
21
|
+
: Object.freeze({ status: 'not_found', code: 'icqq_message_not_observed' });
|
|
22
|
+
}
|
|
23
|
+
if (reference.kind === 'forward') {
|
|
24
|
+
try {
|
|
25
|
+
const entries = await this.#resolveForwardEntries(reference.forwardId, context, { remainingEntries: context.maxEntries, path: new Set() }, 0);
|
|
26
|
+
if (entries.length === 0) {
|
|
27
|
+
return Object.freeze({ status: 'not_found', code: 'icqq_forward_not_found' });
|
|
28
|
+
}
|
|
29
|
+
return Object.freeze({
|
|
30
|
+
status: 'resolved',
|
|
31
|
+
reference,
|
|
32
|
+
value: Object.freeze(entries),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (context.signal.aborted)
|
|
37
|
+
throw error;
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
status: 'failed',
|
|
40
|
+
code: 'icqq_forward_fetch_failed',
|
|
41
|
+
message: error instanceof Error ? error.message : String(error),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (reference.media.kind === 'url'
|
|
46
|
+
|| reference.media.kind === 'base64'
|
|
47
|
+
|| reference.media.kind === 'path') {
|
|
48
|
+
return Object.freeze({ status: 'resolved', reference, value: reference.media });
|
|
49
|
+
}
|
|
50
|
+
return Object.freeze({
|
|
51
|
+
status: 'unsupported',
|
|
52
|
+
code: 'icqq_media_reference_unsupported',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async #resolveForwardEntries(forwardId, context, state, depth) {
|
|
56
|
+
context.signal.throwIfAborted();
|
|
57
|
+
if (state.remainingEntries <= 0 || state.path.has(forwardId))
|
|
58
|
+
return Object.freeze([]);
|
|
59
|
+
state.path.add(forwardId);
|
|
60
|
+
try {
|
|
61
|
+
// ICQQ has no cancellable getForwardMsg API. Keep the generation lease
|
|
62
|
+
// until the native operation settles, then honor cancellation.
|
|
63
|
+
const raw = await this.#client.getForwardMsg(forwardId);
|
|
64
|
+
context.signal.throwIfAborted();
|
|
65
|
+
const entries = normalizeForwardMsgResponse(raw).slice(0, state.remainingEntries);
|
|
66
|
+
state.remainingEntries -= entries.length;
|
|
67
|
+
const expanded = [];
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
context.signal.throwIfAborted();
|
|
70
|
+
const segments = [];
|
|
71
|
+
for (const segment of entry.segments) {
|
|
72
|
+
if (segment.type !== 'forward' || depth >= context.maxDepth) {
|
|
73
|
+
segments.push(segment);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const nestedId = String(segment.data.forward_id ?? '').trim();
|
|
77
|
+
if (!nestedId || state.path.has(nestedId) || state.remainingEntries <= 0) {
|
|
78
|
+
segments.push(segment);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const nested = await this.#resolveForwardEntries(nestedId, context, state, depth + 1);
|
|
82
|
+
segments.push(Object.freeze({
|
|
83
|
+
...segment,
|
|
84
|
+
data: Object.freeze({
|
|
85
|
+
...segment.data,
|
|
86
|
+
...(nested.length > 0 ? { entries: nested } : {}),
|
|
87
|
+
}),
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
expanded.push(Object.freeze({ ...entry, segments: Object.freeze(segments) }));
|
|
91
|
+
}
|
|
92
|
+
return Object.freeze(expanded);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
state.path.delete(forwardId);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** ICQQ 历史文本视图 → canonical MessageSegment。出站只使用原生 Sendable。 */
|
|
2
|
+
import { type MediaRef } from '@zhin.js/im-contract';
|
|
3
|
+
import type { MessageSegment } from "zhin.js";
|
|
4
|
+
/**
|
|
5
|
+
* CQ 媒体参数 / 入站元素媒体值 → canonical MediaRef:
|
|
6
|
+
* `base64://` → base64;http(s) → url;本机路径 → path;其余视为平台不透明引用(file)。
|
|
7
|
+
*/
|
|
8
|
+
export declare function icqqMediaRefFromString(value: string): MediaRef;
|
|
9
|
+
/**
|
|
10
|
+
* canonical MediaRef(`data.media`,唯一媒体来源)→ ICQQ native
|
|
11
|
+
* element 的 file 参数。base64 使用 ICQQ 原生支持的 `base64://` 前缀。
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveIcqqMediaFile(data: Record<string, unknown> | undefined): string | undefined;
|
|
14
|
+
export declare function parseCqMessage(raw: string): MessageSegment[];
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/** ICQQ 历史文本视图 → canonical MessageSegment。出站只使用原生 Sendable。 */
|
|
2
|
+
import { isMediaRef } from '@zhin.js/im-contract';
|
|
3
|
+
const MAX_CQ_PARSE_LEN = 256_000;
|
|
4
|
+
/**
|
|
5
|
+
* CQ 媒体参数 / 入站元素媒体值 → canonical MediaRef:
|
|
6
|
+
* `base64://` → base64;http(s) → url;本机路径 → path;其余视为平台不透明引用(file)。
|
|
7
|
+
*/
|
|
8
|
+
export function icqqMediaRefFromString(value) {
|
|
9
|
+
if (value.startsWith("base64://")) {
|
|
10
|
+
return { kind: "base64", value: value.slice("base64://".length) };
|
|
11
|
+
}
|
|
12
|
+
if (value.startsWith("http://") || value.startsWith("https://")) {
|
|
13
|
+
return { kind: "url", value };
|
|
14
|
+
}
|
|
15
|
+
if (value.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(value)) {
|
|
16
|
+
return { kind: "path", value };
|
|
17
|
+
}
|
|
18
|
+
return { kind: "file", value };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* canonical MediaRef(`data.media`,唯一媒体来源)→ ICQQ native
|
|
22
|
+
* element 的 file 参数。base64 使用 ICQQ 原生支持的 `base64://` 前缀。
|
|
23
|
+
*/
|
|
24
|
+
export function resolveIcqqMediaFile(data) {
|
|
25
|
+
const media = data?.media;
|
|
26
|
+
if (!isMediaRef(media))
|
|
27
|
+
return undefined;
|
|
28
|
+
if (media.kind === "base64") {
|
|
29
|
+
return media.value.startsWith("base64://") ? media.value : `base64://${media.value}`;
|
|
30
|
+
}
|
|
31
|
+
return media.value;
|
|
32
|
+
}
|
|
33
|
+
function pushCqSegment(segments, type, arg) {
|
|
34
|
+
switch (type) {
|
|
35
|
+
case "face":
|
|
36
|
+
segments.push({ type: "face", data: { id: Number(arg) } });
|
|
37
|
+
break;
|
|
38
|
+
case "image":
|
|
39
|
+
segments.push({ type: "image", data: { media: icqqMediaRefFromString(arg) } });
|
|
40
|
+
break;
|
|
41
|
+
case "at":
|
|
42
|
+
if (arg === "all") {
|
|
43
|
+
segments.push({ type: "at", data: { qq: "all" } });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
segments.push({ type: "at", data: { qq: arg } });
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
case "dice":
|
|
50
|
+
segments.push({ type: "dice", data: {} });
|
|
51
|
+
break;
|
|
52
|
+
case "rps":
|
|
53
|
+
segments.push({ type: "rps", data: {} });
|
|
54
|
+
break;
|
|
55
|
+
case "record":
|
|
56
|
+
case "audio":
|
|
57
|
+
segments.push({ type: "audio", data: { media: icqqMediaRefFromString(arg) } });
|
|
58
|
+
break;
|
|
59
|
+
case "video":
|
|
60
|
+
segments.push({ type: "video", data: { media: icqqMediaRefFromString(arg) } });
|
|
61
|
+
break;
|
|
62
|
+
case "reply":
|
|
63
|
+
segments.push({ type: "reply", data: { message_id: arg } });
|
|
64
|
+
break;
|
|
65
|
+
case "json":
|
|
66
|
+
segments.push({ type: "json", data: { text: arg } });
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
segments.push({ type, data: { text: `[${type}:${arg}]` } });
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function parseCqMessage(raw) {
|
|
74
|
+
const text = raw.length > MAX_CQ_PARSE_LEN ? raw.slice(0, MAX_CQ_PARSE_LEN) : raw;
|
|
75
|
+
const segments = [];
|
|
76
|
+
let i = 0;
|
|
77
|
+
while (i < text.length) {
|
|
78
|
+
if (text[i] !== "[") {
|
|
79
|
+
const next = text.indexOf("[", i);
|
|
80
|
+
const chunk = next === -1 ? text.slice(i) : text.slice(i, next);
|
|
81
|
+
if (chunk)
|
|
82
|
+
segments.push({ type: "text", data: { text: chunk } });
|
|
83
|
+
if (next === -1)
|
|
84
|
+
break;
|
|
85
|
+
i = next;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const close = text.indexOf("]", i + 1);
|
|
89
|
+
if (close === -1) {
|
|
90
|
+
segments.push({ type: "text", data: { text: text.slice(i) } });
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
const inner = text.slice(i + 1, close);
|
|
94
|
+
const colon = inner.indexOf(":");
|
|
95
|
+
const type = (colon === -1 ? inner : inner.slice(0, colon)).trim().toLowerCase();
|
|
96
|
+
const arg = colon === -1 ? "" : inner.slice(colon + 1);
|
|
97
|
+
if (/^[a-z_]+$/.test(type)) {
|
|
98
|
+
pushCqSegment(segments, type, arg);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
segments.push({ type: "text", data: { text: text.slice(i, close + 1) } });
|
|
102
|
+
}
|
|
103
|
+
i = close + 1;
|
|
104
|
+
}
|
|
105
|
+
return segments.length ? segments : [{ type: "text", data: { text: raw } }];
|
|
106
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Client } from '@icqqjs/icqq';
|
|
2
|
+
import { Endpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
3
|
+
import type { EndpointContentPort } from '@zhin.js/adapter';
|
|
4
|
+
import { type LoginAssist } from '@zhin.js/core';
|
|
5
|
+
import type { CapabilityId } from 'zhin.js';
|
|
6
|
+
import { type IcqqInboundMessage, type ResolvedIcqqConfig } from './protocol.js';
|
|
7
|
+
export interface IcqqEndpointOptions {
|
|
8
|
+
readonly id: CapabilityId;
|
|
9
|
+
readonly config: ResolvedIcqqConfig;
|
|
10
|
+
readonly loginAssist: LoginAssist;
|
|
11
|
+
}
|
|
12
|
+
declare class ManagedIcqqClient extends Client {
|
|
13
|
+
cancelReconnect(): void;
|
|
14
|
+
}
|
|
15
|
+
export declare class IcqqEndpoint extends Endpoint<Client> {
|
|
16
|
+
#private;
|
|
17
|
+
readonly client: ManagedIcqqClient;
|
|
18
|
+
readonly endpointName: string;
|
|
19
|
+
readonly content: EndpointContentPort;
|
|
20
|
+
readonly management: EndpointManagement;
|
|
21
|
+
readonly control: EndpointControl;
|
|
22
|
+
constructor(options: IcqqEndpointOptions);
|
|
23
|
+
start(signal: AbortSignal): Promise<void>;
|
|
24
|
+
open(): void;
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
stop(): Promise<void>;
|
|
27
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
28
|
+
admit(msg: IcqqInboundMessage): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export {};
|