@zhin.js/adapter-icqq 1.1.25 → 1.1.27

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 (59) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/LICENSE +21 -0
  3. package/PERMITS.md +5 -1
  4. package/README.md +6 -2
  5. package/adapters/icqq/index.js +4 -0
  6. package/adapters/icqq/index.ts +4 -0
  7. package/agents/icqq/conversation.js +3 -2
  8. package/agents/icqq/conversation.ts +3 -2
  9. package/agents/icqq/skills/icqq-conversation/tools/conversation_info/index.js +2 -2
  10. package/agents/icqq/skills/icqq-conversation/tools/conversation_info/index.ts +2 -2
  11. package/agents/icqq/skills/icqq-directory/tools/friend_list/index.js +1 -1
  12. package/agents/icqq/skills/icqq-directory/tools/friend_list/index.ts +1 -1
  13. package/agents/icqq/skills/icqq-directory/tools/group_list/index.js +1 -1
  14. package/agents/icqq/skills/icqq-directory/tools/group_list/index.ts +1 -1
  15. package/agents/icqq/skills/icqq-group-admin/tools/announce/index.js +6 -5
  16. package/agents/icqq/skills/icqq-group-admin/tools/announce/index.ts +7 -6
  17. package/agents/icqq/skills/icqq-group-admin/tools/essence/index.js +7 -3
  18. package/agents/icqq/skills/icqq-group-admin/tools/essence/index.ts +7 -3
  19. package/agents/icqq/skills/icqq-group-admin/tools/group_invite/index.js +4 -3
  20. package/agents/icqq/skills/icqq-group-admin/tools/group_invite/index.ts +5 -4
  21. package/agents/icqq/skills/icqq-group-admin/tools/group_kick/index.js +6 -5
  22. package/agents/icqq/skills/icqq-group-admin/tools/group_kick/index.ts +7 -6
  23. package/agents/icqq/skills/icqq-group-admin/tools/group_leave/index.js +1 -1
  24. package/agents/icqq/skills/icqq-group-admin/tools/group_leave/index.ts +1 -1
  25. package/agents/icqq/skills/icqq-group-admin/tools/recall_member_message/index.js +1 -2
  26. package/agents/icqq/skills/icqq-group-admin/tools/recall_member_message/index.ts +1 -2
  27. package/agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.js +5 -4
  28. package/agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.ts +6 -5
  29. package/agents/icqq/skills/icqq-group-admin/tools/set_group_admin/index.js +6 -6
  30. package/agents/icqq/skills/icqq-group-admin/tools/set_group_admin/index.ts +7 -7
  31. package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.js +5 -3
  32. package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.ts +6 -4
  33. package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.js +5 -3
  34. package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.ts +6 -4
  35. package/agents/icqq/skills/icqq-group-directory/tools/group_member_info/index.js +5 -4
  36. package/agents/icqq/skills/icqq-group-directory/tools/group_member_info/index.ts +5 -5
  37. package/agents/icqq/skills/icqq-group-interaction/tools/group_sign/index.js +5 -5
  38. package/agents/icqq/skills/icqq-group-interaction/tools/group_sign/index.ts +6 -6
  39. package/agents/icqq/skills/icqq-group-interaction/tools/poke/index.js +4 -4
  40. package/agents/icqq/skills/icqq-group-interaction/tools/poke/index.ts +5 -5
  41. package/agents/icqq/skills/icqq-group-state/SKILL.md +1 -1
  42. package/agents/icqq/skills/icqq-group-state/tools/group_files/index.js +6 -7
  43. package/agents/icqq/skills/icqq-group-state/tools/group_files/index.ts +7 -8
  44. package/agents/icqq/skills/icqq-group-state/tools/list_muted/index.js +5 -6
  45. package/agents/icqq/skills/icqq-group-state/tools/list_muted/index.ts +6 -7
  46. package/commands/icqq/login/definition.js +19 -16
  47. package/commands/icqq/login/definition.ts +18 -18
  48. package/lib/console-spec.js +103 -9
  49. package/lib/endpoint.js +3 -2
  50. package/lib/group-scene.d.ts +3 -0
  51. package/lib/group-scene.js +11 -0
  52. package/lib/protocol.d.ts +3 -1
  53. package/package.json +29 -28
  54. package/plugin.js +0 -5
  55. package/schema.json +0 -55
  56. package/src/console-spec.ts +103 -9
  57. package/src/endpoint.ts +3 -2
  58. package/src/group-scene.ts +12 -0
  59. package/src/protocol.ts +3 -2
@@ -3,40 +3,40 @@
3
3
  *
4
4
  * They are registered by the ICQQ plugin but can be invoked from any connected
5
5
  * adapter (typically the QQ instance that delivered the outage notice), so the
6
- * master can fetch the QR image and confirm/send verification codes without the
6
+ * owner can fetch the QR image and confirm/send verification codes without the
7
7
  * Console or terminal.
8
8
  */
9
9
  import { icqqLoginControls, type IcqqLoginControl } from '../../../src/icqq-login-control.js';
10
10
 
11
11
  export { icqqLoginControls };
12
12
 
13
- /** Master = framework master(s), or the configured outage-notify conversation id(s). */
13
+ /**
14
+ * Operator = a **global** chat-role `owner` (ADR 0056 §2: the command reaches an ICQQ account from
15
+ * any adapter, so a conversation / account owner elsewhere does not qualify), or the configured
16
+ * outage-notify conversation id(s).
17
+ */
14
18
  export function isIcqqLoginOperator(config: unknown, input: unknown): boolean {
15
19
  const cfg = (config ?? {}) as {
16
- master?: unknown;
17
20
  notify?: { id?: unknown };
18
- endpoints?: readonly { master?: unknown; notify?: { id?: unknown } }[];
21
+ endpoints?: readonly { notify?: { id?: unknown } }[];
19
22
  };
20
- const masters = new Set<string>();
23
+ const sender = (input as { sender?: { id?: unknown; chatRole?: unknown; chatRoleLevels?: { owner?: unknown } } } | null | undefined)?.sender;
24
+ const senderId = (typeof sender === 'object' && sender?.id) ? String(sender.id).trim() : '';
25
+ if (!senderId) return false;
26
+ if (sender?.chatRole === 'owner' && sender.chatRoleLevels?.owner === 'global') return true;
27
+ const targets = new Set<string>();
21
28
  const collect = (value: unknown) => {
22
29
  if (value === undefined || value === null) return;
23
30
  const text = String(value).trim();
24
- if (text) masters.add(text);
31
+ if (text) targets.add(text);
25
32
  };
26
- collect(cfg.master);
27
33
  collect(cfg.notify?.id);
28
34
  if (Array.isArray(cfg.endpoints)) {
29
- for (const endpoint of cfg.endpoints) {
30
- collect(endpoint.master);
31
- collect(endpoint.notify?.id);
32
- }
35
+ for (const endpoint of cfg.endpoints) collect(endpoint.notify?.id);
33
36
  }
34
- // Login control is account-sensitive: without an explicit master/notify target,
35
- // refuse chat recovery and leave it to the authenticated Console page.
36
- if (masters.size === 0) return false;
37
- const senderRaw = (input as { sender?: { id?: unknown } } | null | undefined)?.sender;
38
- const senderId = (typeof senderRaw === 'object' && senderRaw?.id) ? String(senderRaw.id).trim() : '';
39
- return senderId !== '' && masters.has(senderId);
37
+ // Login control is account-sensitive: without an owner or a notify target, chat recovery is
38
+ // refused and left to the authenticated Console page.
39
+ return targets.has(senderId);
40
40
  }
41
41
 
42
42
  export function pickIcqqLoginControl(
@@ -57,7 +57,7 @@ export function pickIcqqLoginControl(
57
57
  }
58
58
 
59
59
  export function icqqLoginForbidden(): string {
60
- return '仅 master 或 notify 目标账号可以操作 ICQQ 登录';
60
+ return '仅 owner(聊天角色)或 notify 目标账号可以操作 ICQQ 登录';
61
61
  }
62
62
 
63
63
  export function pendingTaskLabel(type: string): string {
@@ -2,15 +2,109 @@ export const icqqConsoleSpec = {
2
2
  adapterKey: 'icqq',
3
3
  adapterDisplayName: 'ICQQ',
4
4
  fields: [
5
- { key: 'password', env: true, description: 'QQ password; leave blank to retain the saved credential' },
6
- { key: 'platform', type: 'number', description: 'ICQQ platform number' },
7
- { key: 'ver', description: 'ICQQ protocol version' },
8
- { key: 'dataDir', description: 'ICQQ device and session directory' },
9
- { key: 'signApiAddr', description: 'ICQQ signing service URL' },
10
- { key: 'autoReconnect', type: 'boolean', description: 'ICQQ reconnect setting' },
11
- { key: 'autoServer', type: 'boolean', description: 'ICQQ automatic server selection' },
12
- { key: 'qqnt', type: 'boolean', description: 'QQNT protocol' },
13
- { key: 'ntLogin', type: 'boolean', description: 'QQNT login' },
5
+ {
6
+ key: 'password',
7
+ env: true,
8
+ label: 'QQ 密码',
9
+ labelI18n: { en: 'QQ Password' },
10
+ group: '基础认证',
11
+ groupI18n: { en: 'Basic Auth' },
12
+ placeholder: '留空可使用扫码登录或保留已存密码',
13
+ placeholderI18n: { en: 'Leave blank for QR login or to retain saved password' },
14
+ description: 'QQ 登录密码;留空则保留已保存的凭据或使用扫码登录',
15
+ descriptionI18n: { en: 'QQ password; leave blank to retain the saved credential' },
16
+ },
17
+ {
18
+ key: 'platform',
19
+ type: 'number',
20
+ label: '登录协议',
21
+ labelI18n: { en: 'Login Platform' },
22
+ group: '协议与签名',
23
+ groupI18n: { en: 'Protocol & Signing' },
24
+ placeholder: '5 (推荐 iPad 协议)',
25
+ placeholderI18n: { en: '5 (iPad recommended)' },
26
+ description: '登录设备协议(1:安卓手机 2:aPad 3:安卓手表 4:MacOS 5:iPad 6:安卓Pad)',
27
+ descriptionI18n: { en: 'ICQQ platform number (1:Android Phone 2:aPad 3:Watch 4:macOS 5:iPad 6:Android Pad)' },
28
+ options: [
29
+ { value: '1', label: '1 - 安卓手机 (Android Phone)', labelI18n: { en: '1 - Android Phone' } },
30
+ { value: '2', label: '2 - aPad 平板 (aPad)', labelI18n: { en: '2 - aPad' } },
31
+ { value: '3', label: '3 - 安卓手表 (Android Watch)', labelI18n: { en: '3 - Android Watch' } },
32
+ { value: '4', label: '4 - MacOS 桌面 (macOS)', labelI18n: { en: '4 - macOS' } },
33
+ { value: '5', label: '5 - iPad 平板(推荐)', labelI18n: { en: '5 - iPad (Recommended)' } },
34
+ { value: '6', label: '6 - 安卓平板 (Android Pad)', labelI18n: { en: '6 - Android Pad' } },
35
+ ],
36
+ },
37
+ {
38
+ key: 'signApiAddr',
39
+ label: '签名服务地址',
40
+ labelI18n: { en: 'Sign API URL' },
41
+ group: '协议与签名',
42
+ groupI18n: { en: 'Protocol & Signing' },
43
+ placeholder: 'http://127.0.0.1:8080/sign',
44
+ description: 'ICQQ 第三方签名服务地址(qsign)',
45
+ descriptionI18n: { en: 'ICQQ signing service URL' },
46
+ },
47
+ {
48
+ key: 'ver',
49
+ label: '协议版本',
50
+ labelI18n: { en: 'Protocol Version' },
51
+ group: '协议与签名',
52
+ groupI18n: { en: 'Protocol & Signing' },
53
+ placeholder: '例如 9.0.50(留空使用默认)',
54
+ placeholderI18n: { en: 'e.g. 9.0.50 (blank for default)' },
55
+ description: '指定客户端协议版本号',
56
+ descriptionI18n: { en: 'ICQQ protocol version' },
57
+ },
58
+ {
59
+ key: 'dataDir',
60
+ label: '会话数据目录',
61
+ labelI18n: { en: 'Session Data Directory' },
62
+ group: '连接与高可用',
63
+ groupI18n: { en: 'Connection & Storage' },
64
+ placeholder: './data/icqq',
65
+ description: '设备锁与登录会话缓存目录',
66
+ descriptionI18n: { en: 'ICQQ device and session directory' },
67
+ },
68
+ {
69
+ key: 'autoReconnect',
70
+ type: 'boolean',
71
+ label: '自动重连',
72
+ labelI18n: { en: 'Auto Reconnect' },
73
+ group: '连接与高可用',
74
+ groupI18n: { en: 'Connection & Storage' },
75
+ description: '掉线后是否自动重新连接',
76
+ descriptionI18n: { en: 'ICQQ reconnect setting' },
77
+ },
78
+ {
79
+ key: 'autoServer',
80
+ type: 'boolean',
81
+ label: '自动优选服务器',
82
+ labelI18n: { en: 'Auto Server Selection' },
83
+ group: '连接与高可用',
84
+ groupI18n: { en: 'Connection & Storage' },
85
+ description: '自动选择最优连接服务器节点',
86
+ descriptionI18n: { en: 'ICQQ automatic server selection' },
87
+ },
88
+ {
89
+ key: 'qqnt',
90
+ type: 'boolean',
91
+ label: 'QQNT 协议',
92
+ labelI18n: { en: 'QQNT Protocol' },
93
+ group: '协议与签名',
94
+ groupI18n: { en: 'Protocol & Signing' },
95
+ description: '启用新版 QQNT 协议栈',
96
+ descriptionI18n: { en: 'QQNT protocol' },
97
+ },
98
+ {
99
+ key: 'ntLogin',
100
+ type: 'boolean',
101
+ label: 'QQNT 登录流',
102
+ labelI18n: { en: 'QQNT Login' },
103
+ group: '协议与签名',
104
+ groupI18n: { en: 'Protocol & Signing' },
105
+ description: '启用 QQNT 登录验证模式',
106
+ descriptionI18n: { en: 'QQNT login' },
107
+ },
14
108
  ],
15
109
  docsUrl: 'https://zhin.js.org/adapters/icqq',
16
110
  readPermission: 'icqq:read',
package/lib/endpoint.js CHANGED
@@ -16,7 +16,7 @@ import { formatInboundContent, formatOutboundMessageParts, normalizeIcqqImageMed
16
16
  import { ManagedIcqqClient } from './managed-client.js';
17
17
  import { createIcqqActionPorts } from './group-actions.js';
18
18
  import { inspectGroupMember, moderateGroupMember } from './group-moderation.js';
19
- import { inspectGroupMemberProfile, inspectGroupScene } from './group-scene.js';
19
+ import { inspectGroupMemberProfile, inspectGroupScene, inspectSelfStanding } from './group-scene.js';
20
20
  import { createIcqqSelfProfile } from './self-profile.js';
21
21
  const BOUND_EVENTS = [
22
22
  'message.private',
@@ -69,6 +69,7 @@ export class IcqqEndpoint extends ManagedIcqqClient {
69
69
  inspectGroupMember: request => inspectGroupMember(this, request),
70
70
  inspectGroupScene: request => inspectGroupScene(this, request),
71
71
  inspectGroupMemberProfile: request => inspectGroupMemberProfile(this, request),
72
+ inspectSelfStanding: request => inspectSelfStanding(this, request),
72
73
  moderateGroupMember: request => moderateGroupMember({ client: this, isActive: () => this.#open && !this.#retiring && !this.#stopped }, request),
73
74
  listFriends: async (request = {}) => createEndpointOperation(request).run(() => Array.from(this.fl.values()).map((f) => ({
74
75
  user_id: f.user_id,
@@ -977,7 +978,7 @@ export class IcqqEndpoint extends ManagedIcqqClient {
977
978
  sender: {
978
979
  id: normalized.userId,
979
980
  name: normalized.nickname || undefined,
980
- ...(normalized.senderRole ? { roles: [normalized.senderRole] } : {}),
981
+ ...(normalized.senderRole ? { platform: { standing: normalized.senderRole } } : {}),
981
982
  },
982
983
  channelType: normalized.channelType,
983
984
  ...(quoteId ? { replyTo: { id: quoteId } } : {}),
@@ -1,4 +1,5 @@
1
1
  import type { Client } from '@icqqjs/icqq';
2
+ import { type PlatformStanding, type SelfStandingRequest } from '@zhin.js/im-contract';
2
3
  import { type EndpointGroupInspectionRequest, type EndpointMemberInspectionRequest, type GroupMemberProfile, type GroupSceneFacts } from 'zhin.js/adapter';
3
4
  /** Listing a very large roster only to find admins is not worth the packet; adminIds stay unknown. */
4
5
  export declare const ADMIN_SCAN_MAX_MEMBERS = 1000;
@@ -9,3 +10,5 @@ export declare const ADMIN_SCAN_MAX_MEMBERS = 1000;
9
10
  export declare function inspectGroupScene(client: Client, { groupId, signal }: EndpointGroupInspectionRequest): Promise<GroupSceneFacts>;
10
11
  /** ADR 0041 member facts from one uncached lookup (same rationale as the agent `freshMember` helper). */
11
12
  export declare function inspectGroupMemberProfile(client: Client, { groupId, userId, signal }: EndpointMemberInspectionRequest): Promise<GroupMemberProfile>;
13
+ /** ADR 0056 §1: the Bot's own standing in a group (`scene.selfStanding`); undefined outside groups. */
14
+ export declare function inspectSelfStanding(client: Client, { conversation, signal }: SelfStandingRequest): Promise<PlatformStanding | undefined>;
@@ -1,3 +1,4 @@
1
+ import { standingFromMemberRole } from '@zhin.js/im-contract';
1
2
  import { createEndpointOperation } from 'zhin.js/adapter';
2
3
  /** Listing a very large roster only to find admins is not worth the packet; adminIds stay unknown. */
3
4
  export const ADMIN_SCAN_MAX_MEMBERS = 1000;
@@ -70,3 +71,13 @@ export async function inspectGroupMemberProfile(client, { groupId, userId, signa
70
71
  ? { mutedUntil: shutupSeconds * 1000 > Date.now() ? shutupSeconds * 1000 : 0 } : {}),
71
72
  };
72
73
  }
74
+ /** ADR 0056 §1: the Bot's own standing in a group (`scene.selfStanding`); undefined outside groups. */
75
+ export async function inspectSelfStanding(client, { conversation, signal }) {
76
+ if (conversation.kind !== 'group')
77
+ return undefined;
78
+ const self = client.uin;
79
+ if (!self)
80
+ return undefined;
81
+ const profile = await inspectGroupMemberProfile(client, { groupId: conversation.id, userId: String(self), signal });
82
+ return standingFromMemberRole(profile.platformRole);
83
+ }
package/lib/protocol.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { EndpointProfileConfiguration } from 'zhin.js/adapter';
2
2
  import type { Sendable } from '@icqqjs/icqq';
3
- import { type ConversationRef } from '@zhin.js/im-contract';
3
+ import { type ConversationRef, type PlatformStanding } from '@zhin.js/im-contract';
4
4
  import { type AdapterNotifyTarget, type ResolvedAdapterNotifyTarget } from 'zhin.js/adapter';
5
5
  import type { MessageSegment, SendContent } from "zhin.js";
6
6
  export type ActionResult = {
@@ -217,9 +217,11 @@ export interface IcqqInboundMessage {
217
217
  * 与 content(CQ 原文纯文本视图)同源,随 gateway.receive 上送。
218
218
  */
219
219
  readonly segments?: readonly MessageSegment[];
220
+ /** `platform`: ADR 0056 §1 normalized standing (group messages only). */
220
221
  readonly sender: {
221
222
  readonly id: string;
222
223
  readonly name?: string;
224
+ readonly platform?: PlatformStanding;
223
225
  };
224
226
  readonly channelType: 'private' | 'group' | 'channel';
225
227
  readonly metadata?: Record<string, unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-icqq",
3
- "version": "1.1.25",
3
+ "version": "1.1.27",
4
4
  "description": "Zhin.js ICQQ adapter — direct connection to @icqqjs/icqq",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -33,28 +33,28 @@
33
33
  "directory": "plugins/adapters/icqq"
34
34
  },
35
35
  "dependencies": {
36
- "@zhin.js/adapter-onebot-events": "0.0.4",
37
- "@zhin.js/adapter": "1.1.31",
38
- "@zhin.js/agent-feature": "1.1.16",
39
- "@zhin.js/console-contract": "1.1.5",
40
- "@zhin.js/core": "1.1.59",
41
- "@zhin.js/feature-kit": "1.1.15",
42
- "@zhin.js/im-contract": "1.1.13",
43
- "@zhin.js/logger": "1.1.2",
44
- "@zhin.js/page": "1.1.17",
45
- "@zhin.js/permission": "1.1.15",
46
- "@zhin.js/skill": "1.1.16",
47
- "@zhin.js/tool": "1.1.18"
36
+ "zod": "^4.6.7",
37
+ "@zhin.js/adapter": "1.1.33",
38
+ "@zhin.js/adapter-onebot-events": "0.0.6",
39
+ "@zhin.js/agent-feature": "1.1.17",
40
+ "@zhin.js/console-contract": "1.1.6",
41
+ "@zhin.js/core": "1.2.0",
42
+ "@zhin.js/feature-kit": "1.1.16",
43
+ "@zhin.js/im-contract": "1.1.14",
44
+ "@zhin.js/logger": "1.1.3",
45
+ "@zhin.js/page": "1.1.18",
46
+ "@zhin.js/permission": "1.2.0",
47
+ "@zhin.js/skill": "1.1.17",
48
+ "@zhin.js/tool": "1.1.19"
48
49
  },
49
50
  "peerDependencies": {
50
51
  "@icqqjs/icqq": "^1.12.2",
51
- "zod": "^4.0.0",
52
- "@zhin.js/adapter": "^1.1.31",
53
- "@zhin.js/agent": "^1.1.50",
54
- "@zhin.js/command": "^1.1.17",
55
- "@zhin.js/core": "^1.1.59",
56
- "@zhin.js/prompt-section": "^1.1.15",
57
- "zhin.js": "^1.1.24"
52
+ "@zhin.js/adapter": "^1.1.33",
53
+ "@zhin.js/agent": "^1.2.0",
54
+ "@zhin.js/command": "^1.2.0",
55
+ "@zhin.js/core": "^1.2.0",
56
+ "@zhin.js/prompt-section": "^1.1.16",
57
+ "zhin.js": "^1.1.26"
58
58
  },
59
59
  "peerDependenciesMeta": {
60
60
  "@icqqjs/icqq": {
@@ -71,9 +71,6 @@
71
71
  },
72
72
  "zhin.js": {
73
73
  "optional": true
74
- },
75
- "zod": {
76
- "optional": true
77
74
  }
78
75
  },
79
76
  "devDependencies": {
@@ -81,10 +78,9 @@
81
78
  "@types/node": "^26.1.2",
82
79
  "typescript": "^6.0.3",
83
80
  "vitest": "^4.1.10",
84
- "zod": "^4.4.3",
85
- "@zhin.js/agent": "1.1.50",
86
- "@zhin.js/prompt-section": "1.1.15",
87
- "zhin.js": "1.1.24"
81
+ "@zhin.js/agent": "1.2.0",
82
+ "@zhin.js/prompt-section": "1.1.16",
83
+ "zhin.js": "1.1.26"
88
84
  },
89
85
  "files": [
90
86
  "adapters",
@@ -137,6 +133,11 @@
137
133
  {
138
134
  "package": "@zhin.js/page",
139
135
  "api": "^1.0.0"
136
+ },
137
+ {
138
+ "package": "@zhin.js/prompt-section",
139
+ "api": "^1.0.0",
140
+ "optional": true
140
141
  }
141
142
  ],
142
143
  "plugins": []
@@ -144,7 +145,7 @@
144
145
  "scripts": {
145
146
  "build": "tsc",
146
147
  "clean": "rimraf lib",
147
- "test": "pnpm run test:types && NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/adapters/icqq/tests",
148
+ "test": "pnpm run test:types && vitest run --root ../../.. plugins/adapters/icqq/tests",
148
149
  "test:types": "tsc -p tsconfig.type-tests.json"
149
150
  }
150
151
  }
package/plugin.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
2
  import { definePlugin } from 'zhin.js';
3
- import { permissionHostToken, createSceneRolePlatformChecker } from '@zhin.js/permission';
4
3
  import { registerSensitiveExecCommand } from '@zhin.js/agent';
5
4
  import { createIcqqConsoleRpc } from "./lib/console.js";
6
5
  import { icqqSensitiveExecRule } from "./lib/sensitive-commands.js";
@@ -11,15 +10,11 @@ export default definePlugin({
11
10
  displayName: 'ICQQ Adapter',
12
11
  },
13
12
  setup(context) {
14
- const disposePlatform = context.resources.has(permissionHostToken)
15
- ? context.resources.use(permissionHostToken).registerPlatform('icqq', createSceneRolePlatformChecker())
16
- : undefined;
17
13
  const disposeExecRule = registerSensitiveExecCommand(icqqSensitiveExecRule);
18
14
  return {
19
15
  consoleRpc: createIcqqConsoleRpc(() => context.config.get()),
20
16
  dispose: () => {
21
17
  disposeExecRule();
22
- disposePlatform?.();
23
18
  },
24
19
  };
25
20
  },
package/schema.json CHANGED
@@ -3,33 +3,6 @@
3
3
  "type": "object",
4
4
  "additionalProperties": false,
5
5
  "properties": {
6
- "master": {
7
- "type": [
8
- "string",
9
- "number"
10
- ],
11
- "description": "框架 master QQ uin(/approve、AI/工具权限);endpoints[i].master 可逐项覆盖",
12
- "x-i18n": {
13
- "en": {
14
- "description": "Framework master QQ uin list for approval and AI/tool permissions. endpoints[i].master can override this value."
15
- }
16
- }
17
- },
18
- "trusted": {
19
- "type": "array",
20
- "items": {
21
- "type": [
22
- "string",
23
- "number"
24
- ]
25
- },
26
- "description": "框架 trusted QQ uin 列表(弱于 master)",
27
- "x-i18n": {
28
- "en": {
29
- "description": "Framework trusted QQ uin list, below master authority."
30
- }
31
- }
32
- },
33
6
  "password": {
34
7
  "type": "string",
35
8
  "description": "QQ 密码(可选,不填则扫码登录)",
@@ -181,33 +154,6 @@
181
154
  "pattern": "^[0-9]+$",
182
155
  "description": "QQ uin"
183
156
  },
184
- "master": {
185
- "type": [
186
- "string",
187
- "number"
188
- ],
189
- "description": "本 endpoint 的 master QQ uin;覆盖顶层 master",
190
- "x-i18n": {
191
- "en": {
192
- "description": "Master QQ uin list for this endpoint, overriding the top-level master list."
193
- }
194
- }
195
- },
196
- "trusted": {
197
- "type": "array",
198
- "items": {
199
- "type": [
200
- "string",
201
- "number"
202
- ]
203
- },
204
- "description": "本 endpoint 的 trusted 列表",
205
- "x-i18n": {
206
- "en": {
207
- "description": "Trusted users for this endpoint."
208
- }
209
- }
210
- },
211
157
  "password": {
212
158
  "type": "string",
213
159
  "description": "本 endpoint 的 QQ 密码",
@@ -586,7 +532,6 @@
586
532
  }
587
533
  },
588
534
  "required": [
589
- "master",
590
535
  "endpoints"
591
536
  ],
592
537
  "$defs": {
@@ -7,15 +7,109 @@ export const icqqConsoleSpec = {
7
7
  adapterKey: 'icqq',
8
8
  adapterDisplayName: 'ICQQ',
9
9
  fields: [
10
- { key: 'password', env: true, description: 'QQ password; leave blank to retain the saved credential' },
11
- { key: 'platform', type: 'number', description: 'ICQQ platform number' },
12
- { key: 'ver', description: 'ICQQ protocol version' },
13
- { key: 'dataDir', description: 'ICQQ device and session directory' },
14
- { key: 'signApiAddr', description: 'ICQQ signing service URL' },
15
- { key: 'autoReconnect', type: 'boolean', description: 'ICQQ reconnect setting' },
16
- { key: 'autoServer', type: 'boolean', description: 'ICQQ automatic server selection' },
17
- { key: 'qqnt', type: 'boolean', description: 'QQNT protocol' },
18
- { key: 'ntLogin', type: 'boolean', description: 'QQNT login' },
10
+ {
11
+ key: 'password',
12
+ env: true,
13
+ label: 'QQ 密码',
14
+ labelI18n: { en: 'QQ Password' },
15
+ group: '基础认证',
16
+ groupI18n: { en: 'Basic Auth' },
17
+ placeholder: '留空可使用扫码登录或保留已存密码',
18
+ placeholderI18n: { en: 'Leave blank for QR login or to retain saved password' },
19
+ description: 'QQ 登录密码;留空则保留已保存的凭据或使用扫码登录',
20
+ descriptionI18n: { en: 'QQ password; leave blank to retain the saved credential' },
21
+ },
22
+ {
23
+ key: 'platform',
24
+ type: 'number',
25
+ label: '登录协议',
26
+ labelI18n: { en: 'Login Platform' },
27
+ group: '协议与签名',
28
+ groupI18n: { en: 'Protocol & Signing' },
29
+ placeholder: '5 (推荐 iPad 协议)',
30
+ placeholderI18n: { en: '5 (iPad recommended)' },
31
+ description: '登录设备协议(1:安卓手机 2:aPad 3:安卓手表 4:MacOS 5:iPad 6:安卓Pad)',
32
+ descriptionI18n: { en: 'ICQQ platform number (1:Android Phone 2:aPad 3:Watch 4:macOS 5:iPad 6:Android Pad)' },
33
+ options: [
34
+ { value: '1', label: '1 - 安卓手机 (Android Phone)', labelI18n: { en: '1 - Android Phone' } },
35
+ { value: '2', label: '2 - aPad 平板 (aPad)', labelI18n: { en: '2 - aPad' } },
36
+ { value: '3', label: '3 - 安卓手表 (Android Watch)', labelI18n: { en: '3 - Android Watch' } },
37
+ { value: '4', label: '4 - MacOS 桌面 (macOS)', labelI18n: { en: '4 - macOS' } },
38
+ { value: '5', label: '5 - iPad 平板(推荐)', labelI18n: { en: '5 - iPad (Recommended)' } },
39
+ { value: '6', label: '6 - 安卓平板 (Android Pad)', labelI18n: { en: '6 - Android Pad' } },
40
+ ],
41
+ },
42
+ {
43
+ key: 'signApiAddr',
44
+ label: '签名服务地址',
45
+ labelI18n: { en: 'Sign API URL' },
46
+ group: '协议与签名',
47
+ groupI18n: { en: 'Protocol & Signing' },
48
+ placeholder: 'http://127.0.0.1:8080/sign',
49
+ description: 'ICQQ 第三方签名服务地址(qsign)',
50
+ descriptionI18n: { en: 'ICQQ signing service URL' },
51
+ },
52
+ {
53
+ key: 'ver',
54
+ label: '协议版本',
55
+ labelI18n: { en: 'Protocol Version' },
56
+ group: '协议与签名',
57
+ groupI18n: { en: 'Protocol & Signing' },
58
+ placeholder: '例如 9.0.50(留空使用默认)',
59
+ placeholderI18n: { en: 'e.g. 9.0.50 (blank for default)' },
60
+ description: '指定客户端协议版本号',
61
+ descriptionI18n: { en: 'ICQQ protocol version' },
62
+ },
63
+ {
64
+ key: 'dataDir',
65
+ label: '会话数据目录',
66
+ labelI18n: { en: 'Session Data Directory' },
67
+ group: '连接与高可用',
68
+ groupI18n: { en: 'Connection & Storage' },
69
+ placeholder: './data/icqq',
70
+ description: '设备锁与登录会话缓存目录',
71
+ descriptionI18n: { en: 'ICQQ device and session directory' },
72
+ },
73
+ {
74
+ key: 'autoReconnect',
75
+ type: 'boolean',
76
+ label: '自动重连',
77
+ labelI18n: { en: 'Auto Reconnect' },
78
+ group: '连接与高可用',
79
+ groupI18n: { en: 'Connection & Storage' },
80
+ description: '掉线后是否自动重新连接',
81
+ descriptionI18n: { en: 'ICQQ reconnect setting' },
82
+ },
83
+ {
84
+ key: 'autoServer',
85
+ type: 'boolean',
86
+ label: '自动优选服务器',
87
+ labelI18n: { en: 'Auto Server Selection' },
88
+ group: '连接与高可用',
89
+ groupI18n: { en: 'Connection & Storage' },
90
+ description: '自动选择最优连接服务器节点',
91
+ descriptionI18n: { en: 'ICQQ automatic server selection' },
92
+ },
93
+ {
94
+ key: 'qqnt',
95
+ type: 'boolean',
96
+ label: 'QQNT 协议',
97
+ labelI18n: { en: 'QQNT Protocol' },
98
+ group: '协议与签名',
99
+ groupI18n: { en: 'Protocol & Signing' },
100
+ description: '启用新版 QQNT 协议栈',
101
+ descriptionI18n: { en: 'QQNT protocol' },
102
+ },
103
+ {
104
+ key: 'ntLogin',
105
+ type: 'boolean',
106
+ label: 'QQNT 登录流',
107
+ labelI18n: { en: 'QQNT Login' },
108
+ group: '协议与签名',
109
+ groupI18n: { en: 'Protocol & Signing' },
110
+ description: '启用 QQNT 登录验证模式',
111
+ descriptionI18n: { en: 'QQNT login' },
112
+ },
19
113
  ] as readonly AdapterConsoleField[],
20
114
  docsUrl: 'https://zhin.js.org/adapters/icqq',
21
115
  readPermission: 'icqq:read',
package/src/endpoint.ts CHANGED
@@ -80,7 +80,7 @@ import type { SystemMessage as IcqqSystemMessage } from './types.js';
80
80
  import { ManagedIcqqClient, type IcqqBackgroundOperation } from './managed-client.js';
81
81
  import { createIcqqActionPorts } from './group-actions.js';
82
82
  import { inspectGroupMember, moderateGroupMember } from './group-moderation.js';
83
- import { inspectGroupMemberProfile, inspectGroupScene } from './group-scene.js';
83
+ import { inspectGroupMemberProfile, inspectGroupScene, inspectSelfStanding } from './group-scene.js';
84
84
  import { createIcqqSelfProfile } from './self-profile.js';
85
85
 
86
86
  export interface IcqqEndpointOptions {
@@ -147,6 +147,7 @@ export class IcqqEndpoint extends ManagedIcqqClient implements Endpoint {
147
147
  inspectGroupMember: request => inspectGroupMember(this, request),
148
148
  inspectGroupScene: request => inspectGroupScene(this, request),
149
149
  inspectGroupMemberProfile: request => inspectGroupMemberProfile(this, request),
150
+ inspectSelfStanding: request => inspectSelfStanding(this, request),
150
151
  moderateGroupMember: request => moderateGroupMember({ client: this, isActive: () => this.#open && !this.#retiring && !this.#stopped }, request),
151
152
  listFriends: async (request: EndpointManagementRequest = {}) =>
152
153
  createEndpointOperation(request).run(() => Array.from(this.fl.values()).map((f) => ({
@@ -1054,7 +1055,7 @@ export class IcqqEndpoint extends ManagedIcqqClient implements Endpoint {
1054
1055
  sender: {
1055
1056
  id: normalized.userId,
1056
1057
  name: normalized.nickname || undefined,
1057
- ...(normalized.senderRole ? { roles: [normalized.senderRole] } : {}),
1058
+ ...(normalized.senderRole ? { platform: { standing: normalized.senderRole } } : {}),
1058
1059
  },
1059
1060
  channelType: normalized.channelType,
1060
1061
  ...(quoteId ? { replyTo: { id: quoteId } } : {}),
@@ -1,4 +1,5 @@
1
1
  import type { Client } from '@icqqjs/icqq';
2
+ import { standingFromMemberRole, type PlatformStanding, type SelfStandingRequest } from '@zhin.js/im-contract';
2
3
  import { createEndpointOperation, type EndpointGroupInspectionRequest, type EndpointMemberInspectionRequest, type GroupMemberProfile, type GroupSceneFacts } from 'zhin.js/adapter';
3
4
 
4
5
  /** Listing a very large roster only to find admins is not worth the packet; adminIds stay unknown. */
@@ -74,3 +75,14 @@ export async function inspectGroupMemberProfile(
74
75
  ? { mutedUntil: shutupSeconds * 1000 > Date.now() ? shutupSeconds * 1000 : 0 } : {}),
75
76
  };
76
77
  }
78
+
79
+ /** ADR 0056 §1: the Bot's own standing in a group (`scene.selfStanding`); undefined outside groups. */
80
+ export async function inspectSelfStanding(
81
+ client: Client, { conversation, signal }: SelfStandingRequest,
82
+ ): Promise<PlatformStanding | undefined> {
83
+ if (conversation.kind !== 'group') return undefined;
84
+ const self = client.uin;
85
+ if (!self) return undefined;
86
+ const profile = await inspectGroupMemberProfile(client, { groupId: conversation.id, userId: String(self), signal });
87
+ return standingFromMemberRole(profile.platformRole);
88
+ }
package/src/protocol.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { EndpointProfileConfiguration } from 'zhin.js/adapter';
2
2
  import type { MessageElem, Sendable, ShareElem } from '@icqqjs/icqq';
3
- import { isMediaRef, type ConversationRef } from '@zhin.js/im-contract';
3
+ import { isMediaRef, type ConversationRef, type PlatformStanding } from '@zhin.js/im-contract';
4
4
  import { resolveAdapterNotifyTarget, type AdapterNotifyTarget, type ResolvedAdapterNotifyTarget } from 'zhin.js/adapter';
5
5
  import type { MessageSegment, SendContent } from "zhin.js";
6
6
  import { resolveIcqqMediaFile } from "./cq-message.js";
@@ -289,7 +289,8 @@ export interface IcqqInboundMessage {
289
289
  * 与 content(CQ 原文纯文本视图)同源,随 gateway.receive 上送。
290
290
  */
291
291
  readonly segments?: readonly MessageSegment[];
292
- readonly sender: { readonly id: string; readonly name?: string };
292
+ /** `platform`: ADR 0056 §1 normalized standing (group messages only). */
293
+ readonly sender: { readonly id: string; readonly name?: string; readonly platform?: PlatformStanding };
293
294
  readonly channelType: 'private' | 'group' | 'channel';
294
295
  readonly metadata?: Record<string, unknown>;
295
296
  readonly replyTo?: { readonly id: string };