@zhin.js/adapter-icqq 1.1.15 → 1.1.17

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.
Files changed (80) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/PERMITS.md +14 -18
  3. package/README.md +12 -0
  4. package/adapters/icqq/index.js +2 -0
  5. package/adapters/icqq/index.ts +2 -0
  6. package/agents/icqq/conversation.js +72 -0
  7. package/agents/icqq/conversation.ts +67 -0
  8. package/agents/icqq/members.js +16 -0
  9. package/agents/icqq/members.ts +17 -0
  10. package/agents/icqq/messages.js +144 -0
  11. package/agents/icqq/messages.ts +144 -0
  12. package/agents/icqq/skills/icqq-conversation/SKILL.md +24 -0
  13. package/agents/icqq/skills/icqq-conversation/tools/conversation_info/index.js +61 -0
  14. package/agents/icqq/skills/icqq-conversation/tools/conversation_info/index.ts +61 -0
  15. package/agents/icqq/skills/icqq-directory/tools/user_info/index.js +2 -1
  16. package/agents/icqq/skills/icqq-directory/tools/user_info/index.ts +2 -1
  17. package/agents/icqq/skills/icqq-group-admin/SKILL.md +11 -1
  18. package/agents/icqq/skills/icqq-group-admin/tools/announce/index.js +2 -1
  19. package/agents/icqq/skills/icqq-group-admin/tools/announce/index.ts +2 -1
  20. package/agents/icqq/skills/icqq-group-admin/tools/essence/index.js +3 -2
  21. package/agents/icqq/skills/icqq-group-admin/tools/essence/index.ts +3 -2
  22. package/agents/icqq/skills/icqq-group-admin/tools/group_invite/index.js +6 -5
  23. package/agents/icqq/skills/icqq-group-admin/tools/group_invite/index.ts +5 -4
  24. package/agents/icqq/skills/icqq-group-admin/tools/group_kick/index.js +4 -3
  25. package/agents/icqq/skills/icqq-group-admin/tools/group_kick/index.ts +4 -3
  26. package/agents/icqq/skills/icqq-group-admin/tools/group_leave/index.js +34 -0
  27. package/agents/icqq/skills/icqq-group-admin/tools/group_leave/index.ts +33 -0
  28. package/agents/icqq/skills/icqq-group-admin/tools/recall_member_message/index.js +34 -0
  29. package/agents/icqq/skills/icqq-group-admin/tools/recall_member_message/index.ts +33 -0
  30. package/agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.js +5 -4
  31. package/agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.ts +4 -3
  32. package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.js +8 -7
  33. package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.ts +6 -5
  34. package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.js +8 -7
  35. package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.ts +6 -5
  36. package/agents/icqq/skills/icqq-group-directory/tools/group_member_info/index.js +18 -7
  37. package/agents/icqq/skills/icqq-group-directory/tools/group_member_info/index.ts +17 -5
  38. package/agents/icqq/skills/icqq-group-interaction/tools/group_sign/index.js +3 -2
  39. package/agents/icqq/skills/icqq-group-interaction/tools/group_sign/index.ts +3 -2
  40. package/agents/icqq/skills/icqq-group-interaction/tools/poke/index.js +2 -1
  41. package/agents/icqq/skills/icqq-group-interaction/tools/poke/index.ts +2 -1
  42. package/agents/icqq/skills/icqq-group-state/tools/group_files/index.js +2 -1
  43. package/agents/icqq/skills/icqq-group-state/tools/group_files/index.ts +2 -1
  44. package/agents/icqq/skills/icqq-interaction/tools/send_user_like/index.js +3 -2
  45. package/agents/icqq/skills/icqq-interaction/tools/send_user_like/index.ts +3 -2
  46. package/agents/icqq/skills/icqq-messages/SKILL.md +28 -0
  47. package/agents/icqq/skills/icqq-messages/tools/forward_messages/index.js +42 -0
  48. package/agents/icqq/skills/icqq-messages/tools/forward_messages/index.ts +40 -0
  49. package/agents/icqq/skills/icqq-messages/tools/quote_reply/index.js +24 -0
  50. package/agents/icqq/skills/icqq-messages/tools/quote_reply/index.ts +24 -0
  51. package/agents/icqq/skills/icqq-messages/tools/recall_own_message/index.js +18 -0
  52. package/agents/icqq/skills/icqq-messages/tools/recall_own_message/index.ts +17 -0
  53. package/agents/icqq/system.md +5 -1
  54. package/commands/icqq/endpoint/definition.js +9 -7
  55. package/commands/icqq/endpoint/definition.ts +9 -9
  56. package/lib/endpoint.d.ts +1 -0
  57. package/lib/endpoint.js +12 -8
  58. package/lib/group-moderation.js +7 -2
  59. package/lib/group-scene.d.ts +11 -0
  60. package/lib/group-scene.js +71 -0
  61. package/lib/i18n.d.ts +1 -0
  62. package/lib/i18n.js +47 -0
  63. package/lib/icqq-inbound.d.ts +2 -0
  64. package/lib/icqq-inbound.js +3 -0
  65. package/lib/icqq-inbox.d.ts +5 -0
  66. package/lib/icqq-inbox.js +14 -0
  67. package/lib/icqq-login-assist.d.ts +2 -0
  68. package/lib/icqq-login-assist.js +8 -7
  69. package/lib/protocol.d.ts +3 -0
  70. package/lib/protocol.js +18 -3
  71. package/package.json +16 -16
  72. package/schema.json +250 -42
  73. package/src/endpoint.ts +13 -7
  74. package/src/group-moderation.ts +7 -2
  75. package/src/group-scene.ts +76 -0
  76. package/src/i18n.ts +48 -0
  77. package/src/icqq-inbound.ts +5 -0
  78. package/src/icqq-inbox.ts +15 -0
  79. package/src/icqq-login-assist.ts +10 -7
  80. package/src/protocol.ts +19 -3
@@ -1,4 +1,6 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../../messages.js";
3
+ import { freshMember } from "../../../../members.js";
2
4
  import { defineAgentTool } from '@zhin.js/tool';
3
5
  import { z } from 'zod';
4
6
  export default defineAgentTool({
@@ -12,15 +14,24 @@ export default defineAgentTool({
12
14
  permissions: ['role(master,admin,trusted,owner)'],
13
15
  requiresApproval: 'never',
14
16
  async execute({ group_id, user_id }, context) {
15
- const group = context.$client.pickGroup(group_id);
16
- if (!group)
17
- return { success: false, message: '目标群不存在' };
18
- const memberInfo = group.pickMember(user_id);
19
- if (!memberInfo)
20
- return { success: false, message: '目标成员不存在' };
17
+ // pickMember 返回的是实体对象,序列化后只剩 `{}`:模型会据此以为成员不在群里。按 group_kick 的口径实时查询。
18
+ const info = await freshMember(context.$client, group_id, user_id);
19
+ if (!info)
20
+ return { success: false, message: t(context.locale, 'memberMissing') };
21
21
  return {
22
22
  success: true,
23
- member: memberInfo,
23
+ member: {
24
+ group_id: info.group_id,
25
+ user_id: info.user_id,
26
+ nickname: info.nickname,
27
+ card: info.card,
28
+ role: info.role,
29
+ title: info.title,
30
+ level: info.level,
31
+ join_time: info.join_time,
32
+ last_sent_time: info.last_sent_time,
33
+ shutup_time: info.shutup_time,
34
+ },
24
35
  };
25
36
  },
26
37
  });
@@ -1,3 +1,5 @@
1
+ import { messages as t } from "../../../../messages.js";
2
+ import { freshMember } from "../../../../members.js";
1
3
  import { defineAgentTool } from '@zhin.js/tool';
2
4
  import { z } from 'zod';
3
5
  interface GroupMemberInfoInput {
@@ -16,13 +18,23 @@ export default defineAgentTool<GroupMemberInfoInput>({
16
18
  permissions: ['role(master,admin,trusted,owner)'],
17
19
  requiresApproval: 'never',
18
20
  async execute({ group_id, user_id }, context) {
19
- const group = context.$client.pickGroup(group_id);
20
- if (!group) return { success: false, message: '目标群不存在' };
21
- const memberInfo = group.pickMember(user_id);
22
- if (!memberInfo) return { success: false, message: '目标成员不存在' };
21
+ // pickMember 返回的是实体对象,序列化后只剩 `{}`:模型会据此以为成员不在群里。按 group_kick 的口径实时查询。
22
+ const info = await freshMember(context.$client, group_id, user_id);
23
+ if (!info) return { success: false, message: t(context.locale, 'memberMissing') };
23
24
  return {
24
25
  success: true,
25
- member: memberInfo,
26
+ member: {
27
+ group_id: info.group_id,
28
+ user_id: info.user_id,
29
+ nickname: info.nickname,
30
+ card: info.card,
31
+ role: info.role,
32
+ title: info.title,
33
+ level: info.level,
34
+ join_time: info.join_time,
35
+ last_sent_time: info.last_sent_time,
36
+ shutup_time: info.shutup_time,
37
+ },
26
38
  };
27
39
  },
28
40
  });
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../../messages.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
5
  export default defineAgentTool({
@@ -13,8 +14,8 @@ export default defineAgentTool({
13
14
  const client = context.$client;
14
15
  const groupInfo = client.pickGroup(group_id);
15
16
  if (!groupInfo)
16
- return { success: false, message: '目标群不存在' };
17
+ return { success: false, message: t(context.locale, 'groupMissing') };
17
18
  await client.sendGroupSign(group_id);
18
- return { success: true, message: '群签到成功' };
19
+ return { success: true, message: t(context.locale, 'checkedIn') };
19
20
  },
20
21
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from "../../../../messages.js";
1
2
  import { defineAgentTool } from '@zhin.js/tool';
2
3
  import { z } from 'zod';
3
4
  export default defineAgentTool<{ group_id: number }>({
@@ -11,8 +12,8 @@ export default defineAgentTool<{ group_id: number }>({
11
12
  async execute({ group_id }, context) {
12
13
  const client = context.$client;
13
14
  const groupInfo= client.pickGroup(group_id);
14
- if(!groupInfo) return { success: false, message: '目标群不存在' };
15
+ if(!groupInfo) return { success: false, message: t(context.locale, 'groupMissing') };
15
16
  await client.sendGroupSign(group_id);
16
- return { success: true, message: '群签到成功' };
17
+ return { success: true, message: t(context.locale, 'checkedIn') };
17
18
  },
18
19
  });
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../../messages.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
5
  export default defineAgentTool({
@@ -12,6 +13,6 @@ export default defineAgentTool({
12
13
  requiresApproval: 'never',
13
14
  async execute({ group_id, user_id }, context) {
14
15
  await context.$client.sendGroupPoke(group_id, user_id);
15
- return { success: true, message: `已戳了戳 ${user_id}` };
16
+ return { success: true, message: t(context.locale, 'poked', { p0: user_id }) };
16
17
  },
17
18
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from "../../../../messages.js";
1
2
  import { defineAgentTool } from '@zhin.js/tool';
2
3
  import { z } from 'zod';
3
4
  export default defineAgentTool<{ group_id: number; user_id: number }>({
@@ -11,6 +12,6 @@ export default defineAgentTool<{ group_id: number; user_id: number }>({
11
12
  requiresApproval: 'never',
12
13
  async execute({ group_id, user_id }, context) {
13
14
  await context.$client.sendGroupPoke(group_id, user_id);
14
- return { success: true, message: `已戳了戳 ${user_id}` };
15
+ return { success: true, message: t(context.locale, 'poked', { p0: user_id }) };
15
16
  },
16
17
  });
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../../messages.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
5
  export default defineAgentTool({
@@ -13,7 +14,7 @@ export default defineAgentTool({
13
14
  async execute({ group_id }, context) {
14
15
  const files = await context.$client.acquireGfs(group_id).ls('/');
15
16
  if (!files.length)
16
- return { files: [], message: '群文件为空' };
17
+ return { files: [], message: t(context.locale, 'filesEmpty') };
17
18
  return {
18
19
  files: files.slice(0, 30).map((f) => 'size' in f
19
20
  ? { name: f.name, is_dir: false, size: f.size, uploader: f.user_id, upload_time: f.create_time }
@@ -1,3 +1,4 @@
1
+ import { messages as t } from "../../../../messages.js";
1
2
  import { defineAgentTool } from '@zhin.js/tool';
2
3
  import { z } from 'zod';
3
4
  export default defineAgentTool<{ group_id: number }>({
@@ -11,7 +12,7 @@ export default defineAgentTool<{ group_id: number }>({
11
12
  permissions: ['role(master,admin,trusted,owner)'],
12
13
  async execute({ group_id }, context) {
13
14
  const files = await context.$client.acquireGfs(group_id).ls('/');
14
- if (!files.length) return { files: [], message: '群文件为空' };
15
+ if (!files.length) return { files: [], message: t(context.locale, 'filesEmpty') };
15
16
  return {
16
17
  files: files.slice(0, 30).map((f) => 'size' in f
17
18
  ? { name: f.name, is_dir: false, size: f.size, uploader: f.user_id, upload_time: f.create_time }
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../../messages.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
5
  export default defineAgentTool({
@@ -20,7 +21,7 @@ export default defineAgentTool({
20
21
  }));
21
22
  const successTimes = successArray.reduce((a, b) => a + b, 0);
22
23
  if (!successTimes)
23
- return { success: false, message: '点赞失败' };
24
- return { success: true, message: `已给 ${user_id} 点赞 ${successTimes} 次` };
24
+ return { success: false, message: t(context.locale, 'likeFailed') };
25
+ return { success: true, message: t(context.locale, 'likesSent', { p0: user_id, p1: successTimes }) };
25
26
  },
26
27
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from "../../../../messages.js";
1
2
  import { defineAgentTool } from '@zhin.js/tool';
2
3
  import { z } from 'zod';
3
4
  export default defineAgentTool<{ user_id: number }>({
@@ -16,7 +17,7 @@ export default defineAgentTool<{ user_id: number }>({
16
17
  } catch { /* ignore */ }
17
18
  }));
18
19
  const successTimes = successArray.reduce((a, b) => a + b, 0);
19
- if (!successTimes) return { success: false, message: '点赞失败' };
20
- return { success: true, message: `已给 ${user_id} 点赞 ${successTimes} 次` };
20
+ if (!successTimes) return { success: false, message: t(context.locale, 'likeFailed') };
21
+ return { success: true, message: t(context.locale, 'likesSent', { p0: user_id, p1: successTimes }) };
21
22
  },
22
23
  });
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: icqq-messages
3
+ platforms:
4
+ - icqq
5
+ description: ICQQ 当前会话的引用回复、真实消息合并转发,以及撤回机器人自己的发言。
6
+ keywords:
7
+ - 引用
8
+ - 回复原消息
9
+ - 转发
10
+ - 撤回
11
+ - 自己的消息
12
+ tools:
13
+ - quote_reply
14
+ - forward_messages
15
+ - recall_own_message
16
+ scopes:
17
+ - private
18
+ - group
19
+ ---
20
+ # icqq-messages
21
+
22
+ Only use real message IDs from the current conversation. Do not invent IDs, source authors or forwarded content. The tools resolve the originals and send through the unified outbound pipeline. Cross-conversation forwarding is not supported.
23
+
24
+ `quote_reply` sends a text reply quoting one original; `forward_messages` sends a merged forward of the supplied originals. Use `inspect_conversation_reference` to read an incoming quote/forward when available; it does not send messages.
25
+
26
+ `recall_own_message` needs no management role or per-call approval, including when the bot is an ordinary group member. It verifies the bot authored the message. The platform decides the recall time window; never promise success before receiving confirmation or retry an unconfirmed result automatically. To recall another member's message, load `icqq-group-admin` instead.
27
+
28
+ These tools currently support QQ private/group conversations, not guild channels. A failed or suppressed outbound receipt is not a delivered message.
@@ -0,0 +1,42 @@
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 { currentConversation, resolveMessage, sendToConversation } from '../../../../conversation.js';
5
+ import { messages as t } from '../../../../messages.js';
6
+ export default defineAgentTool({
7
+ description: '将当前 QQ 群或私聊中的真实消息合并转发到本会话;保留真实作者和内容,不支持跨会话转发',
8
+ adapter: 'icqq',
9
+ scopes: ['private', 'group'],
10
+ requiresApproval: 'never',
11
+ inputSchema: z.object({ message_ids: z.array(z.string().min(1)).min(1).describe('当前会话的真实消息 ID(或消息头里的 m<n> 别名),按转发顺序排列') }),
12
+ async execute({ message_ids }, context) {
13
+ const conversation = currentConversation(context);
14
+ if (conversation.kind === 'channel')
15
+ throw new Error(t(context.locale, 'channelOperationUnsupported'));
16
+ const originals = [];
17
+ for (const id of message_ids)
18
+ originals.push(await resolveMessage(context, id));
19
+ const client = context.$client;
20
+ const target = conversation.kind === 'group' ? client.pickGroup(Number(conversation.id))
21
+ : conversation.parent?.kind === 'group' ? client.pickMember(Number(conversation.parent.id), Number(conversation.id))
22
+ : client.pickFriend(Number(conversation.id));
23
+ // SDK preparation uploads a resource; only the gateway below delivers a message.
24
+ const card = await target.makeForwardMsg(originals.map(message => ({
25
+ user_id: message.sender.user_id,
26
+ nickname: message.sender.nickname,
27
+ time: message.time,
28
+ message: message.message,
29
+ })));
30
+ context.signal.throwIfAborted();
31
+ const data = typeof card.data === 'string' ? JSON.parse(card.data) : card.data;
32
+ const detail = data?.meta?.detail;
33
+ if (typeof detail?.resid !== 'string' || !detail.resid || typeof detail.uniseq !== 'string' || !detail.uniseq) {
34
+ throw new Error('ICQQ did not return a complete merged-forward resource');
35
+ }
36
+ return sendToConversation(context, {
37
+ type: 'forward',
38
+ data: { forward_id: detail.resid },
39
+ platform: { icqqForwardFilename: detail.uniseq },
40
+ });
41
+ },
42
+ });
@@ -0,0 +1,40 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { z } from 'zod';
3
+ import { currentConversation, resolveMessage, sendToConversation } from '../../../../conversation.js';
4
+ import { messages as t } from '../../../../messages.js';
5
+
6
+ export default defineAgentTool({
7
+ description: '将当前 QQ 群或私聊中的真实消息合并转发到本会话;保留真实作者和内容,不支持跨会话转发',
8
+ adapter: 'icqq',
9
+ scopes: ['private', 'group'],
10
+ requiresApproval: 'never',
11
+ inputSchema: z.object({ message_ids: z.array(z.string().min(1)).min(1).describe('当前会话的真实消息 ID(或消息头里的 m<n> 别名),按转发顺序排列') }),
12
+ async execute({ message_ids }, context) {
13
+ const conversation = currentConversation(context);
14
+ if (conversation.kind === 'channel') throw new Error(t(context.locale, 'channelOperationUnsupported'));
15
+ const originals = [];
16
+ for (const id of message_ids) originals.push(await resolveMessage(context, id));
17
+ const client = context.$client;
18
+ const target = conversation.kind === 'group' ? client.pickGroup(Number(conversation.id))
19
+ : conversation.parent?.kind === 'group' ? client.pickMember(Number(conversation.parent.id), Number(conversation.id))
20
+ : client.pickFriend(Number(conversation.id));
21
+ // SDK preparation uploads a resource; only the gateway below delivers a message.
22
+ const card = await target.makeForwardMsg(originals.map(message => ({
23
+ user_id: message.sender.user_id,
24
+ nickname: message.sender.nickname,
25
+ time: message.time,
26
+ message: message.message,
27
+ })));
28
+ context.signal.throwIfAborted();
29
+ const data = typeof card.data === 'string' ? JSON.parse(card.data) : card.data;
30
+ const detail = data?.meta?.detail;
31
+ if (typeof detail?.resid !== 'string' || !detail.resid || typeof detail.uniseq !== 'string' || !detail.uniseq) {
32
+ throw new Error('ICQQ did not return a complete merged-forward resource');
33
+ }
34
+ return sendToConversation(context, {
35
+ type: 'forward',
36
+ data: { forward_id: detail.resid },
37
+ platform: { icqqForwardFilename: detail.uniseq },
38
+ });
39
+ },
40
+ });
@@ -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
+ import { resolveMessage, sendToConversation } from '../../../../conversation.js';
5
+ export default defineAgentTool({
6
+ description: '引用当前 QQ 群或私聊中的真实消息并回复文字;无需群管角色,发送走统一链路',
7
+ adapter: 'icqq',
8
+ scopes: ['private', 'group'],
9
+ requiresApproval: 'never',
10
+ inputSchema: z.object({
11
+ message_id: z.string().min(1).describe('当前会话中真实的原消息 ID,或消息头里的 m<n> 别名'),
12
+ text: z.string().min(1).describe('本次回复的文字'),
13
+ }),
14
+ async execute({ message_id, text }, context) {
15
+ const original = await resolveMessage(context, message_id);
16
+ return sendToConversation(context, [
17
+ { type: 'reply', data: { message_id }, platform: { icqqQuote: {
18
+ user_id: original.sender.user_id, seq: original.seq, rand: original.rand,
19
+ time: original.time, message: original.raw_message,
20
+ } } },
21
+ { type: 'text', data: { text } },
22
+ ]);
23
+ },
24
+ });
@@ -0,0 +1,24 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { z } from 'zod';
3
+ import { resolveMessage, sendToConversation } from '../../../../conversation.js';
4
+
5
+ export default defineAgentTool({
6
+ description: '引用当前 QQ 群或私聊中的真实消息并回复文字;无需群管角色,发送走统一链路',
7
+ adapter: 'icqq',
8
+ scopes: ['private', 'group'],
9
+ requiresApproval: 'never',
10
+ inputSchema: z.object({
11
+ message_id: z.string().min(1).describe('当前会话中真实的原消息 ID,或消息头里的 m<n> 别名'),
12
+ text: z.string().min(1).describe('本次回复的文字'),
13
+ }),
14
+ async execute({ message_id, text }, context) {
15
+ const original = await resolveMessage(context, message_id);
16
+ return sendToConversation(context, [
17
+ { type: 'reply', data: { message_id }, platform: { icqqQuote: {
18
+ user_id: original.sender.user_id, seq: original.seq, rand: original.rand,
19
+ time: original.time, message: original.raw_message,
20
+ } } },
21
+ { type: 'text', data: { text } },
22
+ ]);
23
+ },
24
+ });
@@ -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
+ import { recallMessage, resolveMessage } from '../../../../conversation.js';
5
+ import { messages as t } from '../../../../messages.js';
6
+ export default defineAgentTool({
7
+ description: '撤回机器人在当前 QQ 群或私聊中自己的发言;无需群管角色或逐次审批,时限由平台决定',
8
+ adapter: 'icqq',
9
+ scopes: ['private', 'group'],
10
+ requiresApproval: 'never',
11
+ inputSchema: z.object({ message_id: z.string().min(1).describe('机器人在当前会话中发送的真实消息 ID,或消息头里的 m<n> 别名') }),
12
+ async execute({ message_id }, context) {
13
+ const message = await resolveMessage(context, message_id);
14
+ if (message.sender.user_id !== context.$client.uin)
15
+ return { success: false, message: t(context.locale, 'ownMessageRequired') };
16
+ return recallMessage(context, message_id);
17
+ },
18
+ });
@@ -0,0 +1,17 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { z } from 'zod';
3
+ import { recallMessage, resolveMessage } from '../../../../conversation.js';
4
+ import { messages as t } from '../../../../messages.js';
5
+
6
+ export default defineAgentTool({
7
+ description: '撤回机器人在当前 QQ 群或私聊中自己的发言;无需群管角色或逐次审批,时限由平台决定',
8
+ adapter: 'icqq',
9
+ scopes: ['private', 'group'],
10
+ requiresApproval: 'never',
11
+ inputSchema: z.object({ message_id: z.string().min(1).describe('机器人在当前会话中发送的真实消息 ID,或消息头里的 m<n> 别名') }),
12
+ async execute({ message_id }, context) {
13
+ const message = await resolveMessage(context, message_id);
14
+ if (message.sender.user_id !== context.$client.uin) return { success: false, message: t(context.locale, 'ownMessageRequired') };
15
+ return recallMessage(context, message_id);
16
+ },
17
+ });
@@ -1,3 +1,7 @@
1
1
  # ICQQ Platform Agent
2
2
 
3
- You are the icqq platform specialist. Interpret requests in the current icqq conversation and load only the narrowest private Skill needed for the task.
3
+ You are the icqq platform specialist. Interpret requests in the current icqq conversation and load only the narrowest available Skill needed for the task.
4
+
5
+ Use the current Bot snapshot for your platform role. Load `icqq-conversation` and query `conversation_info` when the role is unknown or you need more scene or permission details. Sender roles describe the requester, not you. Do not infer your role from old session summaries or another endpoint account.
6
+
7
+ Discover message and management operations from the current scene's Skill catalog; follow each tool's target, platform and approval rules. Recalling your own messages needs no group management role or per-call approval. Leaving a group requires fresh approval and is forbidden when you own the group. Do not load a Skill that is absent from the current catalog.
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../lib/i18n.js";
2
3
  import { createEndpointCommands, endpointConfigurationStoreToken } from 'zhin.js/adapter';
3
4
  import { defineCommand } from 'zhin.js/command';
4
5
  import { icqqRuntimeStateToken } from "../../../lib/icqq-runtime-state.js";
@@ -6,14 +7,16 @@ export const icqqEndpointCommands = createEndpointCommands({
6
7
  adapterKey: 'icqq',
7
8
  adapterDisplayName: 'ICQQ',
8
9
  running: (use) => use(icqqRuntimeStateToken).endpoints.values(),
9
- describeEntry: () => 'direct(直连 @icqqjs/icqq)',
10
+ describeEntry: (_entry, locale) => t(locale, 'direct'),
10
11
  addDescription: '登记 ICQQ endpoint(重启 zhin 生效)',
11
- bindFlow: async ({ id, use }) => {
12
+ addDescriptionI18n: { en: 'Register an ICQQ endpoint; restart zhin to apply' },
13
+ bindFlow: async ({ id, use, locale, formatCommand }) => {
12
14
  if (!id) {
13
- return '用法:icqq endpoint add <uin>(uin 为纯数字 QQ 号)';
15
+ const command = formatCommand?.('icqq endpoint add');
16
+ return command ? t(locale, 'addUsage', { command }) : t(locale, 'uinRequired');
14
17
  }
15
18
  if (!/^\d+$/.test(id)) {
16
- return 'icqq endpoint 名必须是纯数字 QQ 号(uin)';
19
+ return t(locale, 'uinInvalid');
17
20
  }
18
21
  try {
19
22
  const { filePath } = await use(endpointConfigurationStoreToken).add({
@@ -21,11 +24,10 @@ export const icqqEndpointCommands = createEndpointCommands({
21
24
  entry: { id },
22
25
  environment: {},
23
26
  });
24
- return (`✅ endpoint「${id}」已追加到 ${filePath} 的 plugins.icqq.endpoints。\n` +
25
- `重启 zhin 后生效。`);
27
+ return t(locale, 'endpointAdded', { id, file: filePath });
26
28
  }
27
29
  catch (error) {
28
- return `添加失败:${error instanceof Error ? error.message : String(error)}`;
30
+ return t(locale, 'endpointAddFailed', { error: error instanceof Error ? error.message : String(error) });
29
31
  }
30
32
  },
31
33
  }, defineCommand);
@@ -1,3 +1,4 @@
1
+ import { messages as t } from '../../../src/i18n.js';
1
2
  import { createEndpointCommands, endpointConfigurationStoreToken } from 'zhin.js/adapter';
2
3
  import { defineCommand } from 'zhin.js/command';
3
4
  import { icqqRuntimeStateToken } from '../../../src/icqq-runtime-state.js';
@@ -6,14 +7,16 @@ export const icqqEndpointCommands = createEndpointCommands({
6
7
  adapterKey: 'icqq',
7
8
  adapterDisplayName: 'ICQQ',
8
9
  running: (use) => use(icqqRuntimeStateToken).endpoints.values(),
9
- describeEntry: () => 'direct(直连 @icqqjs/icqq)',
10
+ describeEntry: (_entry, locale) => t(locale, 'direct'),
10
11
  addDescription: '登记 ICQQ endpoint(重启 zhin 生效)',
11
- bindFlow: async ({ id, use }) => {
12
+ addDescriptionI18n: { en: 'Register an ICQQ endpoint; restart zhin to apply' },
13
+ bindFlow: async ({ id, use, locale, formatCommand }) => {
12
14
  if (!id) {
13
- return '用法:icqq endpoint add <uin>(uin 为纯数字 QQ 号)';
15
+ const command = formatCommand?.('icqq endpoint add');
16
+ return command ? t(locale, 'addUsage', { command }) : t(locale, 'uinRequired');
14
17
  }
15
18
  if (!/^\d+$/.test(id)) {
16
- return 'icqq endpoint 名必须是纯数字 QQ 号(uin)';
19
+ return t(locale, 'uinInvalid');
17
20
  }
18
21
  try {
19
22
  const { filePath } = await use(endpointConfigurationStoreToken).add({
@@ -21,12 +24,9 @@ export const icqqEndpointCommands = createEndpointCommands({
21
24
  entry: { id },
22
25
  environment: {},
23
26
  });
24
- return (
25
- `✅ endpoint「${id}」已追加到 ${filePath} 的 plugins.icqq.endpoints。\n` +
26
- `重启 zhin 后生效。`
27
- );
27
+ return t(locale, 'endpointAdded', { id, file: filePath });
28
28
  } catch (error) {
29
- return `添加失败:${error instanceof Error ? error.message : String(error)}`;
29
+ return t(locale, 'endpointAddFailed', { error: error instanceof Error ? error.message : String(error) });
30
30
  }
31
31
  },
32
32
  }, defineCommand);
package/lib/endpoint.d.ts CHANGED
@@ -9,6 +9,7 @@ export interface IcqqEndpointOptions {
9
9
  readonly id: CapabilityId;
10
10
  readonly config: ResolvedIcqqConfig;
11
11
  readonly loginAssist: LoginAssist;
12
+ readonly locale?: import('zhin.js/i18n').Locale;
12
13
  }
13
14
  export declare class IcqqEndpoint extends Endpoint<Client> {
14
15
  #private;
package/lib/endpoint.js CHANGED
@@ -8,12 +8,13 @@ import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logge
8
8
  import { runIcqqLoginAssistStep } from './icqq-login-assist.js';
9
9
  import { IcqqContentResolver } from './content-resolver.js';
10
10
  import { InboundMessageDeduper, findIcqqNestedMessageSource, isIcqqBotMentioned, isIcqqMessagePostType, normalizeIcqqInboundMessage, messageLookupFromIcqqSource, resolveIcqqQuoteIdFromEvent, resolveIcqqInboundMedia, resolveIcqqSelfUserId, shouldSkipSelfInboundMessage, } from './icqq-inbound.js';
11
- import { isIcqqNoticePayload, isIcqqRequestPayload, } from './icqq-inbox.js';
11
+ import { icqqNoticeDedupeKey, isIcqqNoticePayload, isIcqqRequestPayload, } from './icqq-inbox.js';
12
12
  import { IcqqGuildCatalog, normalizeIcqqGuildInboundMessage, } from './icqq-guild.js';
13
13
  import { prepareIcqqOutboundMedia, resolveIcqqOutboundMediaMode, } from './outbound-media.js';
14
14
  import { formatInboundContent, formatOutboundMessages, normalizeIcqqImageMedia, icqqInboundConversation, icqqOutboundTarget, } from './protocol.js';
15
15
  import { ManagedIcqqClient } from './managed-client.js';
16
16
  import { inspectGroupMember, moderateGroupMember } from './group-moderation.js';
17
+ import { inspectGroupMemberProfile, inspectGroupScene } from './group-scene.js';
17
18
  const BOUND_EVENTS = [
18
19
  'message.private',
19
20
  'message.group',
@@ -57,6 +58,8 @@ export class IcqqEndpoint extends Endpoint {
57
58
  content;
58
59
  management = Object.freeze({
59
60
  inspectGroupMember: (group, user, signal) => inspectGroupMember(this.client, group, user, signal),
61
+ inspectGroupScene: (group, signal) => inspectGroupScene(this.client, group, signal),
62
+ inspectGroupMemberProfile: (group, user, signal) => inspectGroupMemberProfile(this.client, group, user, signal),
60
63
  moderateGroupMember: (group, user, action, signal) => moderateGroupMember(this.client, group, user, action, signal, () => this.#open && !this.#retiring && !this.#stopped),
61
64
  listFriends: async () => Array.from(this.client.fl.values()).map((f) => ({
62
65
  user_id: f.user_id,
@@ -87,6 +90,11 @@ export class IcqqEndpoint extends Endpoint {
87
90
  deleteFriend: async (userId) => {
88
91
  await this.client.deleteFriend(Number(userId));
89
92
  },
93
+ sendGroupNotice: async (groupId, content) => {
94
+ if (await this.client.sendGroupNotice(Number(groupId), content) === false) {
95
+ throw new Error('QQ did not confirm the group notice');
96
+ }
97
+ },
90
98
  });
91
99
  control = Object.freeze({
92
100
  recall: async (message) => {
@@ -622,6 +630,7 @@ export class IcqqEndpoint extends Endpoint {
622
630
  this.#logSystemEvent(`login.${step}`, event);
623
631
  void runIcqqLoginAssistStep({
624
632
  assist: this.#options.loginAssist,
633
+ locale: this.#options.locale,
625
634
  client: this.client,
626
635
  adapter: 'icqq',
627
636
  endpointKey: this.endpointName,
@@ -737,6 +746,7 @@ export class IcqqEndpoint extends Endpoint {
737
746
  channelType: normalized.channelType,
738
747
  channelId: normalized.channelId,
739
748
  channelParentGroupId: normalized.channelParentGroupId,
749
+ name: normalized.groupName,
740
750
  });
741
751
  const quoteId = resolveIcqqQuoteIdFromEvent(data);
742
752
  const quoted = messageLookupFromIcqqSource(findIcqqNestedMessageSource(data));
@@ -851,13 +861,7 @@ export class IcqqEndpoint extends Endpoint {
851
861
  #onNoticeEvent(payload) {
852
862
  if (!payload || !isIcqqNoticePayload(payload))
853
863
  return;
854
- const key = [
855
- payload.time,
856
- payload.operator_id ?? payload.user_id,
857
- payload.notice_type ?? payload.type,
858
- payload.sub_type,
859
- ].join('_');
860
- if (!this.#sideEventDeduper.shouldProcess(`notice:${key}`))
864
+ if (!this.#sideEventDeduper.shouldProcess(icqqNoticeDedupeKey(payload)))
861
865
  return;
862
866
  this.#dispatchIcqqSideEvent(payload);
863
867
  }
@@ -3,17 +3,22 @@ export async function inspectGroupMember(client, groupId, userId, signal) {
3
3
  signal.throwIfAborted();
4
4
  const group = numericId(groupId);
5
5
  const user = numericId(userId);
6
+ const selfLookup = client.getGroupMemberInfo(group, client.uin, true);
6
7
  const [self, member] = await Promise.all([
7
- client.getGroupMemberInfo(group, client.uin, true),
8
- client.getGroupMemberInfo(group, user, true),
8
+ selfLookup,
9
+ user === client.uin ? selfLookup : client.getGroupMemberInfo(group, user, true),
9
10
  ]);
10
11
  signal.throwIfAborted();
11
12
  const joinedAt = member.join_time * 1000;
13
+ const platformRole = member.user_id === user && member.group_id === group
14
+ && (member.role === 'owner' || member.role === 'admin' || member.role === 'member')
15
+ ? member.role : undefined;
12
16
  return {
13
17
  canModerate: client.isOnline() && user !== client.uin
14
18
  && self.user_id === client.uin && self.group_id === group
15
19
  && member.user_id === user && member.group_id === group
16
20
  && (self.role === 'owner' || self.role === 'admin') && member.role === 'member',
21
+ ...(platformRole ? { platformRole } : {}),
17
22
  ...(Number.isSafeInteger(joinedAt) && joinedAt > 0 && joinedAt <= Date.now() ? { joinedAt } : {}),
18
23
  };
19
24
  }
@@ -0,0 +1,11 @@
1
+ import type { Client } from '@icqqjs/icqq';
2
+ import type { GroupMemberProfile, GroupSceneFacts } from 'zhin.js/adapter';
3
+ /** Listing a very large roster only to find admins is not worth the packet; adminIds stay unknown. */
4
+ export declare const ADMIN_SCAN_MAX_MEMBERS = 1000;
5
+ /**
6
+ * ADR 0041 scene facts: one uncached group-info read, plus (small groups only) one roster read for
7
+ * admins. A roster failure leaves adminIds unknown instead of failing the other facts.
8
+ */
9
+ export declare function inspectGroupScene(client: Client, groupId: string, signal: AbortSignal): Promise<GroupSceneFacts>;
10
+ /** ADR 0041 member facts from one uncached lookup (same rationale as the agent `freshMember` helper). */
11
+ export declare function inspectGroupMemberProfile(client: Client, groupId: string, userId: string, signal: AbortSignal): Promise<GroupMemberProfile>;