@zhin.js/adapter-icqq 1.1.15 → 1.1.16

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 (67) hide show
  1. package/CHANGELOG.md +36 -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/messages.js +144 -0
  9. package/agents/icqq/messages.ts +144 -0
  10. package/agents/icqq/skills/icqq-conversation/SKILL.md +24 -0
  11. package/agents/icqq/skills/icqq-conversation/tools/conversation_info/index.js +61 -0
  12. package/agents/icqq/skills/icqq-conversation/tools/conversation_info/index.ts +61 -0
  13. package/agents/icqq/skills/icqq-directory/tools/user_info/index.js +2 -1
  14. package/agents/icqq/skills/icqq-directory/tools/user_info/index.ts +2 -1
  15. package/agents/icqq/skills/icqq-group-admin/SKILL.md +11 -1
  16. package/agents/icqq/skills/icqq-group-admin/tools/announce/index.js +2 -1
  17. package/agents/icqq/skills/icqq-group-admin/tools/announce/index.ts +2 -1
  18. package/agents/icqq/skills/icqq-group-admin/tools/essence/index.js +2 -1
  19. package/agents/icqq/skills/icqq-group-admin/tools/essence/index.ts +2 -1
  20. package/agents/icqq/skills/icqq-group-admin/tools/group_invite/index.js +4 -3
  21. package/agents/icqq/skills/icqq-group-admin/tools/group_invite/index.ts +4 -3
  22. package/agents/icqq/skills/icqq-group-admin/tools/group_kick/index.js +4 -3
  23. package/agents/icqq/skills/icqq-group-admin/tools/group_kick/index.ts +4 -3
  24. package/agents/icqq/skills/icqq-group-admin/tools/group_leave/index.js +33 -0
  25. package/agents/icqq/skills/icqq-group-admin/tools/group_leave/index.ts +32 -0
  26. package/agents/icqq/skills/icqq-group-admin/tools/recall_member_message/index.js +33 -0
  27. package/agents/icqq/skills/icqq-group-admin/tools/recall_member_message/index.ts +32 -0
  28. package/agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.js +3 -2
  29. package/agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.ts +3 -2
  30. package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.js +4 -3
  31. package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.ts +4 -3
  32. package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.js +4 -3
  33. package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.ts +4 -3
  34. package/agents/icqq/skills/icqq-group-directory/tools/group_member_info/index.js +3 -2
  35. package/agents/icqq/skills/icqq-group-directory/tools/group_member_info/index.ts +3 -2
  36. package/agents/icqq/skills/icqq-group-interaction/tools/group_sign/index.js +3 -2
  37. package/agents/icqq/skills/icqq-group-interaction/tools/group_sign/index.ts +3 -2
  38. package/agents/icqq/skills/icqq-group-interaction/tools/poke/index.js +2 -1
  39. package/agents/icqq/skills/icqq-group-interaction/tools/poke/index.ts +2 -1
  40. package/agents/icqq/skills/icqq-group-state/tools/group_files/index.js +2 -1
  41. package/agents/icqq/skills/icqq-group-state/tools/group_files/index.ts +2 -1
  42. package/agents/icqq/skills/icqq-interaction/tools/send_user_like/index.js +3 -2
  43. package/agents/icqq/skills/icqq-interaction/tools/send_user_like/index.ts +3 -2
  44. package/agents/icqq/skills/icqq-messages/SKILL.md +28 -0
  45. package/agents/icqq/skills/icqq-messages/tools/forward_messages/index.js +42 -0
  46. package/agents/icqq/skills/icqq-messages/tools/forward_messages/index.ts +40 -0
  47. package/agents/icqq/skills/icqq-messages/tools/quote_reply/index.js +24 -0
  48. package/agents/icqq/skills/icqq-messages/tools/quote_reply/index.ts +24 -0
  49. package/agents/icqq/skills/icqq-messages/tools/recall_own_message/index.js +18 -0
  50. package/agents/icqq/skills/icqq-messages/tools/recall_own_message/index.ts +17 -0
  51. package/agents/icqq/system.md +4 -0
  52. package/commands/icqq/endpoint/definition.js +9 -7
  53. package/commands/icqq/endpoint/definition.ts +9 -9
  54. package/lib/endpoint.d.ts +1 -0
  55. package/lib/endpoint.js +6 -0
  56. package/lib/i18n.d.ts +1 -0
  57. package/lib/i18n.js +47 -0
  58. package/lib/icqq-login-assist.d.ts +2 -0
  59. package/lib/icqq-login-assist.js +8 -7
  60. package/lib/protocol.d.ts +1 -0
  61. package/lib/protocol.js +14 -1
  62. package/package.json +16 -16
  63. package/schema.json +250 -42
  64. package/src/endpoint.ts +7 -0
  65. package/src/i18n.ts +48 -0
  66. package/src/icqq-login-assist.ts +10 -7
  67. package/src/protocol.ts +13 -1
@@ -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,按转发顺序排列') }),
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,按转发顺序排列') }),
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'),
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'),
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') }),
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') }),
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
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.
4
+
5
+ For your own group/guild role, current scene information or platform permissions, load `icqq-conversation` and query `conversation_info`. Sender roles describe the requester, not you. Do not infer your role from old session summaries or another endpoint account.
6
+
7
+ For quoting, forwarding or recalling your own speech, load `icqq-messages`. Your own-message recall needs no group management role or per-call approval. For recalling another member's message or leaving a group, load `icqq-group-admin` and follow the tool-specific rules; only leaving requires fresh approval. All these message tools are restricted to the current conversation. Guild-channel limitations are explicit in `conversation_info`.
@@ -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
@@ -87,6 +87,11 @@ export class IcqqEndpoint extends Endpoint {
87
87
  deleteFriend: async (userId) => {
88
88
  await this.client.deleteFriend(Number(userId));
89
89
  },
90
+ sendGroupNotice: async (groupId, content) => {
91
+ if (await this.client.sendGroupNotice(Number(groupId), content) === false) {
92
+ throw new Error('QQ did not confirm the group notice');
93
+ }
94
+ },
90
95
  });
91
96
  control = Object.freeze({
92
97
  recall: async (message) => {
@@ -622,6 +627,7 @@ export class IcqqEndpoint extends Endpoint {
622
627
  this.#logSystemEvent(`login.${step}`, event);
623
628
  void runIcqqLoginAssistStep({
624
629
  assist: this.#options.loginAssist,
630
+ locale: this.#options.locale,
625
631
  client: this.client,
626
632
  adapter: 'icqq',
627
633
  endpointKey: this.endpointName,
package/lib/i18n.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare const messages: (locale: import("zhin.js").Locale | undefined, key: "direct" | "addUsage" | "uinRequired" | "uinInvalid" | "endpointAdded" | "endpointAddFailed" | "loginQr" | "loginSlider" | "loginSms" | "loginAuth" | "loginOther", values?: import("zhin.js").MessageValues) => string;
package/lib/i18n.js ADDED
@@ -0,0 +1,47 @@
1
+ import { createMessages } from 'zhin.js/i18n';
2
+ export const messages = createMessages({
3
+ "direct": {
4
+ "zh-CN": "direct(直连 @icqqjs/icqq)",
5
+ "en": "direct (@icqqjs/icqq)"
6
+ },
7
+ "addUsage": {
8
+ "zh-CN": "用法:{command} <uin>(uin 为纯数字 QQ 号)",
9
+ "en": "Usage: {command} <uin> (a numeric QQ account ID)"
10
+ },
11
+ "uinRequired": {
12
+ "zh-CN": "请提供纯数字 QQ 号(uin)",
13
+ "en": "Provide a numeric QQ account ID (uin)"
14
+ },
15
+ "uinInvalid": {
16
+ "zh-CN": "icqq endpoint 名必须是纯数字 QQ 号(uin)",
17
+ "en": "The ICQQ endpoint name must be a numeric QQ account ID (uin)"
18
+ },
19
+ "endpointAdded": {
20
+ "zh-CN": "✅ endpoint「{id}」已追加到 {file} 的 plugins.icqq.endpoints。\n重启 zhin 后生效。",
21
+ "en": "✅ Endpoint “{id}” added to plugins.icqq.endpoints in {file}.\nRestart zhin to apply."
22
+ },
23
+ "endpointAddFailed": {
24
+ "zh-CN": "添加失败:{error}",
25
+ "en": "Could not add the endpoint: {error}"
26
+ },
27
+ "loginQr": {
28
+ "zh-CN": "扫码登录:请在手机 QQ 扫描二维码,完成后在 Console / 终端确认继续",
29
+ "en": "Scan the QR code in mobile QQ, then confirm in Console or your terminal."
30
+ },
31
+ "loginSlider": {
32
+ "zh-CN": "滑动验证:打开链接完成滑块,将返回的 ticket(可含 randstr)提交继续",
33
+ "en": "Open the link, complete the slider challenge, and submit the returned ticket (with randstr if provided)."
34
+ },
35
+ "loginSms": {
36
+ "zh-CN": "设备锁:已请求短信验证码,请提交短信验证码继续",
37
+ "en": "Device verification: an SMS code was requested. Submit the code to continue."
38
+ },
39
+ "loginAuth": {
40
+ "zh-CN": "身份验证:完成后在 Console / 终端确认继续",
41
+ "en": "Complete identity verification, then confirm in Console or your terminal."
42
+ },
43
+ "loginOther": {
44
+ "zh-CN": "登录辅助({step})",
45
+ "en": "Login assistance ({step})"
46
+ }
47
+ });
@@ -1,3 +1,4 @@
1
+ import type { Locale } from 'zhin.js/i18n';
1
2
  import type { LoginAssist, LoginAssistType } from '@zhin.js/core';
2
3
  type IcqqLoginClient = {
3
4
  login(password?: string): Promise<unknown>;
@@ -23,5 +24,6 @@ export declare function runIcqqLoginAssistStep(input: {
23
24
  logger: Logger;
24
25
  step: LoginAssistType;
25
26
  event: unknown;
27
+ locale?: Locale;
26
28
  }): Promise<void>;
27
29
  export {};
@@ -1,3 +1,4 @@
1
+ import { messages as t } from './i18n.js';
1
2
  import { formatCompact } from '@zhin.js/logger';
2
3
  /**
3
4
  * Drive ICQQ login challenges through LoginAssist (refresh-safe pending tasks).
@@ -7,7 +8,7 @@ import { formatCompact } from '@zhin.js/logger';
7
8
  export async function runIcqqLoginAssistStep(input) {
8
9
  const { assist, client, adapter, endpointKey, owner, logger, step, event } = input;
9
10
  assist.cancelOwned(owner, `replaced_by_${step}`);
10
- const payload = buildPayload(step, event);
11
+ const payload = buildPayload(step, event, input.locale);
11
12
  logger.info(formatCompact({
12
13
  op: 'login_assist_pending',
13
14
  endpoint: endpointKey,
@@ -56,33 +57,33 @@ export async function runIcqqLoginAssistStep(input) {
56
57
  }));
57
58
  }
58
59
  }
59
- function buildPayload(step, event) {
60
+ function buildPayload(step, event, locale) {
60
61
  const raw = event && typeof event === 'object' ? event : {};
61
62
  switch (step) {
62
63
  case 'qrcode':
63
64
  return {
64
- message: '扫码登录:请在手机 QQ 扫描二维码,完成后在 Console / 终端确认继续',
65
+ message: t(locale, 'loginQr'),
65
66
  image: bufferToDataUrl(raw.image),
66
67
  };
67
68
  case 'slider':
68
69
  return {
69
- message: '滑动验证:打开链接完成滑块,将返回的 ticket(可含 randstr)提交继续',
70
+ message: t(locale, 'loginSlider'),
70
71
  url: typeof raw.url === 'string' ? raw.url : undefined,
71
72
  };
72
73
  case 'device':
73
74
  return {
74
- message: '设备锁:已请求短信验证码,请提交短信验证码继续',
75
+ message: t(locale, 'loginSms'),
75
76
  url: typeof raw.url === 'string' ? raw.url : undefined,
76
77
  phone: raw.phone,
77
78
  };
78
79
  case 'auth':
79
80
  return {
80
- message: '身份验证:完成后在 Console / 终端确认继续',
81
+ message: t(locale, 'loginAuth'),
81
82
  url: typeof raw.url === 'string' ? raw.url : undefined,
82
83
  device: raw.device,
83
84
  };
84
85
  default:
85
- return { message: `登录辅助(${step})` };
86
+ return { message: t(locale, 'loginOther', { step }) };
86
87
  }
87
88
  }
88
89
  function normalizeSubmitValue(value) {
package/lib/protocol.d.ts CHANGED
@@ -198,6 +198,7 @@ export interface ResolvedIcqqConfig {
198
198
  export interface IcqqWireSegment {
199
199
  readonly type: string;
200
200
  readonly data?: Record<string, unknown>;
201
+ readonly platform?: Readonly<Record<string, unknown>>;
201
202
  }
202
203
  export interface IcqqInboundMessage {
203
204
  readonly id: string;
package/lib/protocol.js CHANGED
@@ -265,6 +265,7 @@ const ICQQ_STANDALONE_ELEMENTS = new Set([
265
265
  'json',
266
266
  'share',
267
267
  'long_msg',
268
+ 'multimsg',
268
269
  ]);
269
270
  /** Normalize inline image bytes before either temporary-file or native-base64 delivery. */
270
271
  export function normalizeIcqqImageMedia(content) {
@@ -336,7 +337,7 @@ export function formatOutboundMessages(payload) {
336
337
  }
337
338
  const messages = groups.filter(group => group.some(element => typeof element === 'string'
338
339
  ? element.trim() !== ''
339
- : element.type !== 'reply'));
340
+ : element.type !== 'reply' && element.type !== 'quote'));
340
341
  if (messages.length === 0)
341
342
  return ['\u200b'];
342
343
  return messages.map(group => group.length === 1 ? group[0] : group);
@@ -380,6 +381,14 @@ function formatOutboundElements(payload) {
380
381
  const id = String(item.data?.message_id ?? item.data?.id ?? '').trim();
381
382
  if (!id)
382
383
  throw new TypeError('ICQQ reply segment requires message_id');
384
+ const quote = item.platform?.icqqQuote;
385
+ if (quote) {
386
+ const { user_id, seq, rand, time, message } = quote;
387
+ if (typeof user_id !== 'number' || typeof seq !== 'number' || typeof rand !== 'number'
388
+ || typeof time !== 'number' || typeof message !== 'string')
389
+ throw new TypeError('Invalid ICQQ quote source');
390
+ return { type: 'quote', user_id, seq, rand, time, message };
391
+ }
383
392
  return { type: 'reply', id };
384
393
  }
385
394
  case 'record':
@@ -410,6 +419,10 @@ function formatOutboundElements(payload) {
410
419
  const resid = String(item.data?.forward_id ?? item.data?.resid ?? item.data?.id ?? '').trim();
411
420
  if (!resid)
412
421
  throw new TypeError('ICQQ forward segment requires forward_id');
422
+ // Prepared merged forwards need their filename; a long_msg would expand inline.
423
+ const filename = item.platform?.icqqForwardFilename;
424
+ if (typeof filename === 'string' && filename)
425
+ return { type: 'multimsg', resid, filename };
413
426
  return { type: 'long_msg', resid };
414
427
  }
415
428
  case 'share':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-icqq",
3
- "version": "1.1.15",
3
+ "version": "1.1.16",
4
4
  "description": "Zhin.js ICQQ adapter — direct connection to @icqqjs/icqq",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -33,24 +33,24 @@
33
33
  "directory": "plugins/adapters/icqq"
34
34
  },
35
35
  "dependencies": {
36
- "@zhin.js/adapter": "1.1.22",
37
- "@zhin.js/agent-feature": "1.1.9",
38
- "@zhin.js/core": "1.1.50",
39
- "@zhin.js/feature-kit": "1.1.8",
40
- "@zhin.js/im-contract": "1.1.6",
36
+ "@zhin.js/adapter": "1.1.23",
37
+ "@zhin.js/agent-feature": "1.1.10",
38
+ "@zhin.js/core": "1.1.51",
39
+ "@zhin.js/feature-kit": "1.1.9",
40
+ "@zhin.js/im-contract": "1.1.7",
41
41
  "@zhin.js/logger": "1.1.2",
42
- "@zhin.js/permission": "1.1.8",
43
- "@zhin.js/skill": "1.1.9",
44
- "@zhin.js/tool": "1.1.9"
42
+ "@zhin.js/permission": "1.1.9",
43
+ "@zhin.js/skill": "1.1.10",
44
+ "@zhin.js/tool": "1.1.10"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@icqqjs/icqq": "^1.12.2",
48
48
  "zod": "^4.0.0",
49
- "@zhin.js/adapter": "^1.1.22",
50
- "@zhin.js/command": "^1.1.10",
51
- "@zhin.js/core": "^1.1.50",
52
- "@zhin.js/prompt-section": "^1.1.8",
53
- "zhin.js": "^1.1.15"
49
+ "@zhin.js/adapter": "^1.1.23",
50
+ "@zhin.js/command": "^1.1.11",
51
+ "@zhin.js/core": "^1.1.51",
52
+ "@zhin.js/prompt-section": "^1.1.9",
53
+ "zhin.js": "^1.1.16"
54
54
  },
55
55
  "peerDependenciesMeta": {
56
56
  "@icqqjs/icqq": {
@@ -75,8 +75,8 @@
75
75
  "typescript": "^6.0.3",
76
76
  "vitest": "^4.1.10",
77
77
  "zod": "^4.4.3",
78
- "@zhin.js/prompt-section": "1.1.8",
79
- "zhin.js": "1.1.15"
78
+ "@zhin.js/prompt-section": "1.1.9",
79
+ "zhin.js": "1.1.16"
80
80
  },
81
81
  "files": [
82
82
  "adapters",