@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,67 @@
1
1
  # @zhin.js/adapter-icqq
2
2
 
3
+ ## 1.1.17
4
+
5
+ ### Patch Changes
6
+
7
+ - e23cabc: ADR 0041 slice B: the turn context now carries real scene facts. `EndpointManagement` gains `inspectGroupScene` (name, owner, admins, member count, global mute) and `inspectGroupMemberProfile` (role, card, title, mute expiry; one lookup), implemented by the ICQQ adapter from uncached group/member reads. The CLI resolves them per turn (bot role always fresh; group and sender facts reuse a 60 s, 256-entry in-memory cache scoped to the generation; failures omit fields) and the inbound actor now carries `groupNickname` / `groupTitle` / `mutedUntil` plus the platform role. The Session / Bot state blocks are replaced by one projected scene line `[scene_id:…;scene_type:…;scene_name:…;owner_id:…;admins:…;member_count:…;global_mute:…;assistant_role:…;]` (values escaped and clipped to 64 chars). No new on-disk state.
8
+ - 4d728c0: ADR 0043 §4 + §5 (H4) and the ADR 0041 actor-only amendment. Participant headers are no longer stored: user messages keep only `actor`, assistant messages keep `assistantActor` (plus the delivered `messageId`), and the header is rendered at the LLM boundary; legacy rows that still carry `name` are read as before. Headers now use session message aliases `id:m<n>` (an ordinal over the stored active path, so already-sent prefixes never change), `time:HH:mm` shown only when a minute has passed since the last shown time (the first message and the first message of a day also carry `date`), and `reply_to:m<n>` when the message replies to one inside the visible window. A reply to a message outside the window gets one `[quote id:…;user_id:…;time:…;] <first 120 chars>` line (media as placeholders, a forward expanded one level) so the model no longer needs `inspect_conversation_reference` for ordinary quotes; the quote is resolved by the ingress through the reference port (3 s timeout) and stored as structured `UserMessage.quote`. Tool arguments named `message_id` / `message_ids` / `*_message_id` / `reference` and `reply` segments accept the alias as well as the platform id; `inspect_conversation_reference` also accepts the quoted message id.
9
+ - 51f1328: 修复 icqq 群成员相关 Agent 工具:`group_member_info` 返回空对象(模型据此误判成员不在群里)、`group_invite` 永远判定"已在群里"、`set_mute` / `set_title` / `set_anonymous` 读旧缓存误判 Bot 不是管理员。统一改为实时查询群成员。
10
+ - d0e1fac: Keep stable prompt instructions ahead of turn-specific capabilities, project unchanged context without placeholder text, and retain scoped state snapshots with readable updates and compaction recovery. Preserve native conversation/tool chronology and separate participant identity from runtime guidance.
11
+
12
+ Resolve the ICQQ bot account and current-group role before capability discovery, and support explicitly declared current-conversation bot-role requirements without changing existing requester permissions or approvals. Keep role lookup failures unknown and preserve execution-time platform checks.
13
+
14
+ - 3771f7a: 修复同一群先后两人入群时后一次入群被当重复事件丢掉:side event 去重键(core 与 icqq)加入成员与操作人。
15
+ - 4f96ad5: Fix member-join welcome display: icqq now sends the mention's display name as the `at` element text, the joining member's nickname from the notice is threaded into `target.name`, and icqq group messages populate `conversation.name` from `group_name` so `{group}` renders the group name instead of the group number.
16
+ - Updated dependencies [f67883e]
17
+ - Updated dependencies [e23cabc]
18
+ - Updated dependencies [4d728c0]
19
+ - Updated dependencies [eb8feeb]
20
+ - Updated dependencies [24a2b71]
21
+ - Updated dependencies [d0e1fac]
22
+ - Updated dependencies [3771f7a]
23
+ - Updated dependencies [4f96ad5]
24
+ - @zhin.js/core@1.1.52
25
+ - @zhin.js/adapter@1.1.24
26
+ - @zhin.js/tool@1.1.11
27
+ - @zhin.js/im-contract@1.1.8
28
+
29
+ ## 1.1.16
30
+
31
+ ### Patch Changes
32
+
33
+ - 334e92d: 公告:草稿 → 审批 → 发送(ADR 0037 第 1 期切片 B)。
34
+
35
+ - 新表 `ai_announcements` 与 `AiAnnouncements`:状态只前进 `draft` → `pending` → `approved` → `sent`,旁路 `rejected` / `expired` / `failed`;`revision` 乐观并发;改正文回到 `draft` 并重算 `content_hash`;待审批默认 24 小时超时。
36
+ - 新工具 `announcement_draft`(`risk: safe`):模型只能建草稿,不能提交、审批或发送。
37
+ - Console `plugin.root.announcements` / `announcement`(读)与 `announcement-create` / `-edit` / `-submit` / `-approve` / `-reject` / `-resend`(动作,暂用 `control:read` / `control:write`)。
38
+ - 批准即发送:摘要复核 → 控制面 → 会话画像 → 硬闸以公告审批记录作批准(`ToolHardGate.checkPreApproved`,不弹第二张卡)→ `EndpointManagement.sendGroupNotice`(icqq / napcat)。被拦或失败置 `failed`,不自动重试;重发回到 `pending` 需再批一次;摘要不符拒绝(`announcement_hash_mismatch`)。
39
+ - 审计追加 kind `announcement` 与码 `announcement_drafted` / `_submitted` / `_approved` / `_rejected` / `_expired` / `_sent` / `_send_failed` / `_resend` / `_hash_mismatch`。
40
+
41
+ - 08e64de: Expose current ICQQ conversation and bot-role inspection through a private Agent skill. Add conversation-scoped quote, merged-forward, own/member recall, and owner-approved leave-group tools with live platform checks and documented guild-channel limitations.
42
+
43
+ Include real message IDs in the existing turn and conversation context so message operations can target actual messages without guessing.
44
+
45
+ - 53bc6a0: 适配器配置标签、端点命令和工具反馈支持中英文;复用当前操作语言,保持协议值、用户内容、身份、权限与发送链路不变。Sandbox 展示使用项目语言,QQ 绑定与交互状态保留完整会话身份。
46
+ - Updated dependencies [334e92d]
47
+ - Updated dependencies [18296cf]
48
+ - Updated dependencies [08e64de]
49
+ - Updated dependencies [9cade30]
50
+ - Updated dependencies [53bc6a0]
51
+ - Updated dependencies [7595136]
52
+ - Updated dependencies [66b6404]
53
+ - Updated dependencies [adb4af2]
54
+ - Updated dependencies [69f9ef6]
55
+ - Updated dependencies [17d1301]
56
+ - @zhin.js/adapter@1.1.23
57
+ - @zhin.js/core@1.1.51
58
+ - @zhin.js/permission@1.1.9
59
+ - @zhin.js/tool@1.1.10
60
+ - @zhin.js/skill@1.1.10
61
+ - @zhin.js/im-contract@1.1.7
62
+ - @zhin.js/agent-feature@1.1.10
63
+ - @zhin.js/feature-kit@1.1.9
64
+
3
65
  ## 1.1.15
4
66
 
5
67
  ### Patch Changes
package/PERMITS.md CHANGED
@@ -1,24 +1,20 @@
1
- # ICQQ Platform Permits(QQ 群)
1
+ # ICQQ Agent 权限与会话信息
2
2
 
3
- ## 入站字段
3
+ 调用者的角色与 Bot 的平台角色相互独立。`role(master,admin,trusted,owner)` 由框架校验调用者;它不代表 Bot 是群管理员。群管操作还需检查 Bot 与目标成员的实时平台身份。
4
4
 
5
- 群成员 `owner` / `admin` / `member` 写入 `$sender.role`(IPC 侧已有)。
5
+ 选择 ICQQ Agent 后,先从目录加载对应 Skill,其私有工具才会暴露。`icqq-conversation` 的 `conversation_info` 按当前 endpoint 查询 Bot 自己的身份及群/频道信息,不把共享会话画像当作权限依据,也不在每条聊天消息前增加一次角色查询。
6
6
 
7
- ## Permit 词汇表
7
+ | Skill / 工具 | 调用方要求 | 平台能力与审批 |
8
+ |---|---|---|
9
+ | `icqq-conversation` / `conversation_info` | 当前 ICQQ 会话 | 只读,无逐次审批;群角色实时查询,频道查不到自身记录时为 unknown |
10
+ | `icqq-messages` / `quote_reply`、`forward_messages` | 普通消息操作权限 | 当前群/私聊的真实消息,无逐次审批;发送经过统一出站链路 |
11
+ | `icqq-messages` / `recall_own_message` | 不要求群管角色 | 必须是 Bot 自己在当前群/私聊的发言,无逐次审批;由平台决定可撤回时限 |
12
+ | `icqq-group-admin` / `recall_member_message` | master/admin/trusted/owner | 无逐次审批;Bot 管理员可撤回普通成员,Bot 群主可撤回管理员或普通成员,执行前实时检查 |
13
+ | `icqq-group-admin` / `group_leave` | 仅 master | 当前群,每次人工审批;Bot 为群主时拒绝,以免 SDK 解散群 |
14
+ | `icqq-group-admin` / `group_kick` | master/admin/trusted/owner | 每次人工审批;执行前检查 Bot 与目标成员角色 |
8
15
 
9
- 平台范围用工具的 `platforms: ['icqq']`,不要写非法的 `platform(icqq)`(缺 perm 时 PermissionHost 会直接拒绝,工具对模型不可见)。
16
+ 已配置的自动群审核独立按获准规则执行撤回;上述工具不新增审核触发条件、敏感类别、处罚或目标群。Bot 对自己的消息始终不需要群管权限;平台拒绝或未确认时不能宣称撤回成功,也不自动重试。
10
17
 
11
- | `platform(icqq,…)` | 含义 |
12
- |--------------------|------|
13
- | `scene_owner` | 群主 |
14
- | `scene_admin` | 群管理员(含群主) |
18
+ 引用和转发只接受当前会话内可核实的原消息 ID,不允许跨会话读取/转发或伪造作者。读取入站引用和转发仍使用 `inspect_conversation_reference`,它不是发送工具。
15
19
 
16
- ## 扩展工具示例
17
-
18
- | 工具 | permit |
19
- |------|--------|
20
- | `icqq_set_title` | `scene_owner` |
21
- | `icqq_announce` / `icqq_essence` / `icqq_list_muted` | `scene_admin` |
22
- | `icqq_set_anonymous` | `scene_owner` |
23
-
24
- 工厂群管工具使用默认 `scene_admin` / `scene_owner`。
20
+ 当前 guild-channel 接入可查询频道信息和 Bot 的 guild 角色,但没有这些消息操作和退群工具。SDK 的成员列表可能不完整;`channel_admin` 也未携带所管子频道的范围,不能据此判断当前子频道有管理权限。不要把 QQ 号、群号、guild ID 和 tiny ID 混用。
package/README.md CHANGED
@@ -167,6 +167,12 @@ IM turn 的场景,才使用 `icqqClient.get(context, endpointId)` 显式选择
167
167
  - `system.*`(登录扫码等)分发到 `system.receive`。
168
168
  - **登录辅助**:`system.login.qrcode|slider|device|auth` 经 `loginAssistToken`(`LoginAssist`)挂起待办;刷新后可用 Console `login.list` / `login.submit` 或终端 stdin 继续(对齐 icqq 官方 stdin 流程)。`system.online` / `login.error` 会清理该 endpoint 待办。
169
169
 
170
+ ## Agent 会话与消息操作
171
+
172
+ ICQQ Agent 可按需加载 `icqq-conversation` 查询当前群/频道和 Bot 自身角色。`icqq-messages` 提供当前会话的引用回复、真实消息合并转发和撤回自己发言;自己的消息撤回不要求群管身份或逐次审批,时限由平台决定。
173
+
174
+ `icqq-group-admin` 新增撤回他人消息与退群:前者保留群管发起权限、实时平台角色检查,无需逐次审批;退群仅主人可发起、每次审批,Bot 为群主时拒绝执行。引用/转发走统一发送链路,不支持跨会话转发。当前 guild-channel 接入仅提供身份查询,不能使用这些群/私聊操作。详见 [权限与能力说明](./PERMITS.md)。
175
+
170
176
  ## 故障排查
171
177
 
172
178
  | 现象 | 排查 |
@@ -192,3 +198,9 @@ ICQQ 要求分享、文件、语音、视频、JSON 卡片和合并转发单独
192
198
 
193
199
  - 回执的消息 ID 取第一条拿到 ID 的部分;所有部分都没有 ID(如单独一张分享)时,仍报告投递结果未知。
194
200
  - 第一条就失败时原样报告失败;后面的部分失败时报告 `partial_delivery`(结果未知),因为前面的部分已经送达,重发会重复。
201
+
202
+ ## 语言
203
+
204
+ Endpoint 管理和工具反馈跟随当前操作语言;登录没有当前聊天发送者,LoginAssist 提示采用项目语言。提示文案变化不改变扫码、短信、滑块验证过程,群管理权限、审批与平台结果检查保持原规则。
205
+
206
+ Endpoint 添加用法中的调用头由当前环境的实际命令前缀与插件命名空间提供;未知时只提示所需参数,不生成猜测命令。
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { projectLocaleToken } from 'zhin.js/i18n';
2
3
  /**
3
4
  * Convention entry: discover `adapters/icqq/index.ts` → defineAdapter.
4
5
  */
@@ -46,6 +47,7 @@ export default defineAdapter({
46
47
  return new IcqqEndpoint({
47
48
  id: context.id,
48
49
  loginAssist: context.use(loginAssistToken),
50
+ locale: context.use(projectLocaleToken),
49
51
  config,
50
52
  });
51
53
  },
@@ -1,3 +1,4 @@
1
+ import { projectLocaleToken } from 'zhin.js/i18n';
1
2
  /**
2
3
  * Convention entry: discover `adapters/icqq/index.ts` → defineAdapter.
3
4
  */
@@ -57,6 +58,7 @@ export default defineAdapter<IcqqEndpointConfig>({
57
58
  return new IcqqEndpoint({
58
59
  id: context.id,
59
60
  loginAssist: context.use(loginAssistToken),
61
+ locale: context.use(projectLocaleToken),
60
62
  config,
61
63
  });
62
64
  },
@@ -0,0 +1,72 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { parseDmMessageId, parseGroupMessageId } from '@icqqjs/icqq';
3
+ import { outboundMessageToken } from '@zhin.js/core/runtime';
4
+ import { messages as t } from './messages.js';
5
+ /** The trusted turn owns the account and address; model input cannot select another one. */
6
+ export function currentConversation(context) {
7
+ const { origin } = context;
8
+ if (origin.kind !== 'im' || origin.platform !== 'icqq' || !origin.conversation) {
9
+ throw new Error(t(context.locale, 'conversationRequired'));
10
+ }
11
+ context.signal.throwIfAborted();
12
+ return origin.conversation;
13
+ }
14
+ export function currentGroup(context) {
15
+ const conversation = currentConversation(context);
16
+ if (conversation.kind !== 'group')
17
+ throw new Error(t(context.locale, 'groupRequired'));
18
+ return Number(conversation.id);
19
+ }
20
+ /** getMsg returns the last history item, which is not necessarily the requested message. */
21
+ export async function resolveMessage(context, id) {
22
+ const conversation = currentConversation(context);
23
+ const invalid = () => new Error(t(context.locale, 'messageOutsideConversation'));
24
+ if (conversation.kind === 'channel')
25
+ throw new Error(t(context.locale, 'channelOperationUnsupported'));
26
+ // Check the encoded destination before requesting any history from the platform.
27
+ try {
28
+ const address = id.length > 24 ? parseGroupMessageId(id) : parseDmMessageId(id);
29
+ if (conversation.kind === 'group'
30
+ ? !('group_id' in address) || String(address.group_id) !== conversation.id
31
+ : 'group_id' in address || String(address.user_id) !== conversation.id)
32
+ throw invalid();
33
+ }
34
+ catch {
35
+ throw invalid();
36
+ }
37
+ const message = await context.$client.getMsg(id);
38
+ context.signal.throwIfAborted();
39
+ if (!message || message.message_id !== id)
40
+ throw invalid();
41
+ if (conversation.kind === 'group') {
42
+ if (message.message_type !== 'group' || String(message.group_id) !== conversation.id)
43
+ throw invalid();
44
+ }
45
+ else {
46
+ if (message.message_type !== 'private')
47
+ throw invalid();
48
+ const self = context.$client.uin;
49
+ const peer = Number(conversation.id);
50
+ if (!((message.from_id === self && message.to_id === peer)
51
+ || (message.from_id === peer && message.to_id === self)))
52
+ throw invalid();
53
+ const parent = conversation.parent?.kind === 'group' ? Number(conversation.parent.id) : undefined;
54
+ if (message.sender.group_id !== parent)
55
+ throw invalid();
56
+ }
57
+ return message;
58
+ }
59
+ export async function sendToConversation(context, content) {
60
+ const conversation = currentConversation(context);
61
+ const receipt = await context.use(outboundMessageToken).send({
62
+ requester: context.owner.id,
63
+ conversation,
64
+ content,
65
+ });
66
+ return { success: receipt.status === 'sent', receipt };
67
+ }
68
+ export async function recallMessage(context, id) {
69
+ context.signal.throwIfAborted();
70
+ const success = await context.$client.deleteMsg(id);
71
+ return { success, message: t(context.locale, success ? 'messageRecalled' : 'recallUnconfirmed') };
72
+ }
@@ -0,0 +1,67 @@
1
+ import { parseDmMessageId, parseGroupMessageId, type GroupMessage, type PrivateMessage } from '@icqqjs/icqq';
2
+ import { outboundMessageToken, type SendContent } from '@zhin.js/core/runtime';
3
+ import type { ToolExecutionContext } from '@zhin.js/tool';
4
+ import { messages as t } from './messages.js';
5
+
6
+ export type IcqqToolContext = ToolExecutionContext<unknown, 'icqq'>;
7
+
8
+ /** The trusted turn owns the account and address; model input cannot select another one. */
9
+ export function currentConversation(context: IcqqToolContext) {
10
+ const { origin } = context;
11
+ if (origin.kind !== 'im' || origin.platform !== 'icqq' || !origin.conversation) {
12
+ throw new Error(t(context.locale, 'conversationRequired'));
13
+ }
14
+ context.signal.throwIfAborted();
15
+ return origin.conversation;
16
+ }
17
+
18
+ export function currentGroup(context: IcqqToolContext) {
19
+ const conversation = currentConversation(context);
20
+ if (conversation.kind !== 'group') throw new Error(t(context.locale, 'groupRequired'));
21
+ return Number(conversation.id);
22
+ }
23
+
24
+ /** getMsg returns the last history item, which is not necessarily the requested message. */
25
+ export async function resolveMessage(context: IcqqToolContext, id: string): Promise<GroupMessage | PrivateMessage> {
26
+ const conversation = currentConversation(context);
27
+ const invalid = () => new Error(t(context.locale, 'messageOutsideConversation'));
28
+ if (conversation.kind === 'channel') throw new Error(t(context.locale, 'channelOperationUnsupported'));
29
+ // Check the encoded destination before requesting any history from the platform.
30
+ try {
31
+ const address = id.length > 24 ? parseGroupMessageId(id) : parseDmMessageId(id);
32
+ if (conversation.kind === 'group'
33
+ ? !('group_id' in address) || String(address.group_id) !== conversation.id
34
+ : 'group_id' in address || String(address.user_id) !== conversation.id) throw invalid();
35
+ } catch { throw invalid(); }
36
+ const message = await context.$client.getMsg(id);
37
+ context.signal.throwIfAborted();
38
+ if (!message || message.message_id !== id) throw invalid();
39
+ if (conversation.kind === 'group') {
40
+ if (message.message_type !== 'group' || String(message.group_id) !== conversation.id) throw invalid();
41
+ } else {
42
+ if (message.message_type !== 'private') throw invalid();
43
+ const self = context.$client.uin;
44
+ const peer = Number(conversation.id);
45
+ if (!((message.from_id === self && message.to_id === peer)
46
+ || (message.from_id === peer && message.to_id === self))) throw invalid();
47
+ const parent = conversation.parent?.kind === 'group' ? Number(conversation.parent.id) : undefined;
48
+ if (message.sender.group_id !== parent) throw invalid();
49
+ }
50
+ return message;
51
+ }
52
+
53
+ export async function sendToConversation(context: IcqqToolContext, content: SendContent) {
54
+ const conversation = currentConversation(context);
55
+ const receipt = await context.use(outboundMessageToken).send({
56
+ requester: context.owner.id,
57
+ conversation,
58
+ content,
59
+ });
60
+ return { success: receipt.status === 'sent', receipt };
61
+ }
62
+
63
+ export async function recallMessage(context: IcqqToolContext, id: string) {
64
+ context.signal.throwIfAborted();
65
+ const success = await context.$client.deleteMsg(id);
66
+ return { success, message: t(context.locale, success ? 'messageRecalled' : 'recallUnconfirmed') };
67
+ }
@@ -0,0 +1,16 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ /**
3
+ * 实时查询群成员(跳过缓存);不在群里或查询失败返回 undefined。
4
+ * 不要用 `pickMember`:它总会返回实体对象(判空永远为真),`is_admin` 等也读旧缓存——Bot 刚被设为管理员时会误判。
5
+ */
6
+ export async function freshMember(client, groupId, userId) {
7
+ try {
8
+ return await client.getGroupMemberInfo(groupId, userId, true);
9
+ }
10
+ catch {
11
+ return undefined;
12
+ }
13
+ }
14
+ export function isGroupManager(info) {
15
+ return info?.role === 'owner' || info?.role === 'admin';
16
+ }
@@ -0,0 +1,17 @@
1
+ import type { Client, MemberInfo } from '@icqqjs/icqq';
2
+
3
+ /**
4
+ * 实时查询群成员(跳过缓存);不在群里或查询失败返回 undefined。
5
+ * 不要用 `pickMember`:它总会返回实体对象(判空永远为真),`is_admin` 等也读旧缓存——Bot 刚被设为管理员时会误判。
6
+ */
7
+ export async function freshMember(client: Client, groupId: number, userId: number): Promise<MemberInfo | undefined> {
8
+ try {
9
+ return await client.getGroupMemberInfo(groupId, userId, true);
10
+ } catch {
11
+ return undefined;
12
+ }
13
+ }
14
+
15
+ export function isGroupManager(info: MemberInfo | undefined): boolean {
16
+ return info?.role === 'owner' || info?.role === 'admin';
17
+ }
@@ -0,0 +1,144 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { createMessages } from 'zhin.js/i18n';
3
+ export const messages = createMessages({
4
+ conversationRequired: {
5
+ 'zh-CN': '此操作需要当前 ICQQ 会话',
6
+ en: 'This operation requires the current ICQQ conversation',
7
+ },
8
+ groupRequired: {
9
+ 'zh-CN': '此操作仅支持当前 QQ 群',
10
+ en: 'This operation requires the current QQ group',
11
+ },
12
+ messageOutsideConversation: {
13
+ 'zh-CN': '无法在当前会话中核实该原消息,请勿猜测消息 ID',
14
+ en: 'The original message could not be verified in this conversation; do not guess message IDs',
15
+ },
16
+ channelOperationUnsupported: {
17
+ 'zh-CN': '当前 ICQQ 频道接入不支持此消息操作',
18
+ en: 'The current ICQQ channel integration does not support this message operation',
19
+ },
20
+ ownMessageRequired: {
21
+ 'zh-CN': '该工具只能撤回机器人自己发送的消息',
22
+ en: 'This tool can only recall messages sent by the bot itself',
23
+ },
24
+ recallForbidden: {
25
+ 'zh-CN': '机器人或目标成员的当前平台角色不允许撤回该消息',
26
+ en: 'Current bot or target member roles do not permit recalling this message',
27
+ },
28
+ messageRecalled: {
29
+ 'zh-CN': '平台已确认撤回消息',
30
+ en: 'The platform confirmed the message was recalled',
31
+ },
32
+ recallUnconfirmed: {
33
+ 'zh-CN': '平台未确认撤回成功,可能已超过时限或被平台拒绝,请勿自动重试',
34
+ en: 'Recall was not confirmed; the time window may have expired or the platform may have rejected it. Do not retry automatically',
35
+ },
36
+ leaveForbidden: {
37
+ 'zh-CN': '目标必须是当前群,且机器人必须是普通成员或管理员;群主退群会解散群,不能执行',
38
+ en: 'The target must be the current group and the bot must be a member or admin. An owner cannot leave because the SDK would dissolve the group',
39
+ },
40
+ groupLeft: {
41
+ 'zh-CN': '平台已确认退群',
42
+ en: 'The platform confirmed the bot left the group',
43
+ },
44
+ leaveUnconfirmed: {
45
+ 'zh-CN': '平台未确认退群成功,请勿自动重试',
46
+ en: 'The platform did not confirm leaving the group; do not retry automatically',
47
+ },
48
+ "userInfo": {
49
+ "zh-CN": "已获取 {p0} 的用户信息:{p1}",
50
+ "en": "User information for {p0}:{p1}"
51
+ },
52
+ "announcementSent": {
53
+ "zh-CN": "群公告已发送",
54
+ "en": "Group announcement sent"
55
+ },
56
+ "essenceRemoved": {
57
+ "zh-CN": "已移除精华",
58
+ "en": "Featured status removed"
59
+ },
60
+ "essenceAdded": {
61
+ "zh-CN": "已设为精华",
62
+ "en": "Message marked as featured"
63
+ },
64
+ "inviteAttempted": {
65
+ "zh-CN": "已尝试邀请 {p0} 加入群 {p1}",
66
+ "en": "Attempted to invite {p0} to group {p1}"
67
+ },
68
+ "memberExists": {
69
+ "zh-CN": "目标成员已加入群",
70
+ "en": "The member has already joined the group"
71
+ },
72
+ "groupMissing": {
73
+ "zh-CN": "目标群不存在",
74
+ "en": "The target group does not exist"
75
+ },
76
+ "memberKicked": {
77
+ "zh-CN": "已踢出 {p0} 成员",
78
+ "en": "Member {p0} removed"
79
+ },
80
+ "kickUnconfirmed": {
81
+ "zh-CN": "平台未确认踢出成功,请勿自动重试",
82
+ "en": "The platform did not confirm removal; do not retry automatically"
83
+ },
84
+ "kickForbidden": {
85
+ "zh-CN": "当前权限或成员状态不允许踢出",
86
+ "en": "Current permissions or member status do not allow removal"
87
+ },
88
+ "anonymousDisabled": {
89
+ "zh-CN": "已关闭匿名聊天",
90
+ "en": "Anonymous chat disabled"
91
+ },
92
+ "anonymousEnabled": {
93
+ "zh-CN": "已开启匿名聊天",
94
+ "en": "Anonymous chat enabled"
95
+ },
96
+ "anonymousForbidden": {
97
+ "zh-CN": "机器人不是群主或管理员,无法开启或关闭匿名聊天",
98
+ "en": "The bot must be a group owner or administrator to change anonymous chat"
99
+ },
100
+ "muted": {
101
+ "zh-CN": "已禁言 {user} 成员({seconds}秒)",
102
+ "en": "Muted member {user} ({seconds} seconds)"
103
+ },
104
+ "unmuted": {
105
+ "zh-CN": "已解除禁言 {user} 成员",
106
+ "en": "Unmuted member {user}"
107
+ },
108
+ "memberMissing": {
109
+ "zh-CN": "目标成员不存在",
110
+ "en": "The target member does not exist"
111
+ },
112
+ "muteForbidden": {
113
+ "zh-CN": "机器人不是群主或管理员,无法禁言/解除禁言成员",
114
+ "en": "The bot must be a group owner or administrator to mute or unmute members"
115
+ },
116
+ "titleChanged": {
117
+ "zh-CN": "已将 {p0} 的头衔设为 \"{p1}\"",
118
+ "en": "Title for {p0} set to \"{p1}\""
119
+ },
120
+ "titleForbidden": {
121
+ "zh-CN": "机器人不是群主或管理员,无法设置头衔",
122
+ "en": "The bot must be a group owner or administrator to set member titles"
123
+ },
124
+ "checkedIn": {
125
+ "zh-CN": "群签到成功",
126
+ "en": "Checked in to the group"
127
+ },
128
+ "poked": {
129
+ "zh-CN": "已戳了戳 {p0}",
130
+ "en": "Poked {p0}"
131
+ },
132
+ "filesEmpty": {
133
+ "zh-CN": "群文件为空",
134
+ "en": "The group has no files"
135
+ },
136
+ "likesSent": {
137
+ "zh-CN": "已给 {p0} 点赞 {p1} 次",
138
+ "en": "Sent {p1} likes to {p0}"
139
+ },
140
+ "likeFailed": {
141
+ "zh-CN": "点赞失败",
142
+ "en": "Could not send a like"
143
+ }
144
+ });
@@ -0,0 +1,144 @@
1
+ import { createMessages } from 'zhin.js/i18n';
2
+
3
+ export const messages = createMessages({
4
+ conversationRequired: {
5
+ 'zh-CN': '此操作需要当前 ICQQ 会话',
6
+ en: 'This operation requires the current ICQQ conversation',
7
+ },
8
+ groupRequired: {
9
+ 'zh-CN': '此操作仅支持当前 QQ 群',
10
+ en: 'This operation requires the current QQ group',
11
+ },
12
+ messageOutsideConversation: {
13
+ 'zh-CN': '无法在当前会话中核实该原消息,请勿猜测消息 ID',
14
+ en: 'The original message could not be verified in this conversation; do not guess message IDs',
15
+ },
16
+ channelOperationUnsupported: {
17
+ 'zh-CN': '当前 ICQQ 频道接入不支持此消息操作',
18
+ en: 'The current ICQQ channel integration does not support this message operation',
19
+ },
20
+ ownMessageRequired: {
21
+ 'zh-CN': '该工具只能撤回机器人自己发送的消息',
22
+ en: 'This tool can only recall messages sent by the bot itself',
23
+ },
24
+ recallForbidden: {
25
+ 'zh-CN': '机器人或目标成员的当前平台角色不允许撤回该消息',
26
+ en: 'Current bot or target member roles do not permit recalling this message',
27
+ },
28
+ messageRecalled: {
29
+ 'zh-CN': '平台已确认撤回消息',
30
+ en: 'The platform confirmed the message was recalled',
31
+ },
32
+ recallUnconfirmed: {
33
+ 'zh-CN': '平台未确认撤回成功,可能已超过时限或被平台拒绝,请勿自动重试',
34
+ en: 'Recall was not confirmed; the time window may have expired or the platform may have rejected it. Do not retry automatically',
35
+ },
36
+ leaveForbidden: {
37
+ 'zh-CN': '目标必须是当前群,且机器人必须是普通成员或管理员;群主退群会解散群,不能执行',
38
+ en: 'The target must be the current group and the bot must be a member or admin. An owner cannot leave because the SDK would dissolve the group',
39
+ },
40
+ groupLeft: {
41
+ 'zh-CN': '平台已确认退群',
42
+ en: 'The platform confirmed the bot left the group',
43
+ },
44
+ leaveUnconfirmed: {
45
+ 'zh-CN': '平台未确认退群成功,请勿自动重试',
46
+ en: 'The platform did not confirm leaving the group; do not retry automatically',
47
+ },
48
+ "userInfo": {
49
+ "zh-CN": "已获取 {p0} 的用户信息:{p1}",
50
+ "en": "User information for {p0}:{p1}"
51
+ },
52
+ "announcementSent": {
53
+ "zh-CN": "群公告已发送",
54
+ "en": "Group announcement sent"
55
+ },
56
+ "essenceRemoved": {
57
+ "zh-CN": "已移除精华",
58
+ "en": "Featured status removed"
59
+ },
60
+ "essenceAdded": {
61
+ "zh-CN": "已设为精华",
62
+ "en": "Message marked as featured"
63
+ },
64
+ "inviteAttempted": {
65
+ "zh-CN": "已尝试邀请 {p0} 加入群 {p1}",
66
+ "en": "Attempted to invite {p0} to group {p1}"
67
+ },
68
+ "memberExists": {
69
+ "zh-CN": "目标成员已加入群",
70
+ "en": "The member has already joined the group"
71
+ },
72
+ "groupMissing": {
73
+ "zh-CN": "目标群不存在",
74
+ "en": "The target group does not exist"
75
+ },
76
+ "memberKicked": {
77
+ "zh-CN": "已踢出 {p0} 成员",
78
+ "en": "Member {p0} removed"
79
+ },
80
+ "kickUnconfirmed": {
81
+ "zh-CN": "平台未确认踢出成功,请勿自动重试",
82
+ "en": "The platform did not confirm removal; do not retry automatically"
83
+ },
84
+ "kickForbidden": {
85
+ "zh-CN": "当前权限或成员状态不允许踢出",
86
+ "en": "Current permissions or member status do not allow removal"
87
+ },
88
+ "anonymousDisabled": {
89
+ "zh-CN": "已关闭匿名聊天",
90
+ "en": "Anonymous chat disabled"
91
+ },
92
+ "anonymousEnabled": {
93
+ "zh-CN": "已开启匿名聊天",
94
+ "en": "Anonymous chat enabled"
95
+ },
96
+ "anonymousForbidden": {
97
+ "zh-CN": "机器人不是群主或管理员,无法开启或关闭匿名聊天",
98
+ "en": "The bot must be a group owner or administrator to change anonymous chat"
99
+ },
100
+ "muted": {
101
+ "zh-CN": "已禁言 {user} 成员({seconds}秒)",
102
+ "en": "Muted member {user} ({seconds} seconds)"
103
+ },
104
+ "unmuted": {
105
+ "zh-CN": "已解除禁言 {user} 成员",
106
+ "en": "Unmuted member {user}"
107
+ },
108
+ "memberMissing": {
109
+ "zh-CN": "目标成员不存在",
110
+ "en": "The target member does not exist"
111
+ },
112
+ "muteForbidden": {
113
+ "zh-CN": "机器人不是群主或管理员,无法禁言/解除禁言成员",
114
+ "en": "The bot must be a group owner or administrator to mute or unmute members"
115
+ },
116
+ "titleChanged": {
117
+ "zh-CN": "已将 {p0} 的头衔设为 \"{p1}\"",
118
+ "en": "Title for {p0} set to \"{p1}\""
119
+ },
120
+ "titleForbidden": {
121
+ "zh-CN": "机器人不是群主或管理员,无法设置头衔",
122
+ "en": "The bot must be a group owner or administrator to set member titles"
123
+ },
124
+ "checkedIn": {
125
+ "zh-CN": "群签到成功",
126
+ "en": "Checked in to the group"
127
+ },
128
+ "poked": {
129
+ "zh-CN": "已戳了戳 {p0}",
130
+ "en": "Poked {p0}"
131
+ },
132
+ "filesEmpty": {
133
+ "zh-CN": "群文件为空",
134
+ "en": "The group has no files"
135
+ },
136
+ "likesSent": {
137
+ "zh-CN": "已给 {p0} 点赞 {p1} 次",
138
+ "en": "Sent {p1} likes to {p0}"
139
+ },
140
+ "likeFailed": {
141
+ "zh-CN": "点赞失败",
142
+ "en": "Could not send a like"
143
+ }
144
+ });
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: icqq-conversation
3
+ platforms:
4
+ - icqq
5
+ description: 查询当前群或频道信息、机器人自身身份(群主/管理员/成员)以及当前会话支持的操作和权限边界。
6
+ keywords:
7
+ - 当前群
8
+ - 当前频道
9
+ - 你是管理员吗
10
+ - 群主
11
+ - 机器人权限
12
+ - 身份
13
+ tools:
14
+ - conversation_info
15
+ ---
16
+ # icqq-conversation
17
+
18
+ Use `conversation_info` before claiming the bot's role or available platform permissions. It uses the current endpoint account, not the sender's role. Group roles are refreshed from the platform on demand; failure is not evidence that the bot is an ordinary member.
19
+
20
+ Caller roles authorize a request; bot platform roles determine whether QQ can execute it. They are independent. A successful inspection does not replace the live permission check before a management action.
21
+
22
+ Guild member lookup can be incomplete. Missing self membership means `unknown`. A `channel_admin` record does not identify which child channels it governs, so it cannot authorize an operation in the current channel.
23
+
24
+ Load `icqq-messages` for quote/forward/own-message recall, or `icqq-group-admin` for member-message recall and leaving the current group. These operations are unavailable in the current guild-channel integration. Do not infer capability from a group role or substitute a group API for a channel API.