@zhin.js/adapter-icqq 1.1.17 → 1.1.19

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 +83 -0
  2. package/README.md +55 -10
  3. package/adapters/icqq/index.js +3 -19
  4. package/adapters/icqq/index.ts +2 -25
  5. package/commands/icqq/endpoint/definition.js +3 -25
  6. package/commands/icqq/endpoint/definition.ts +3 -24
  7. package/commands/icqq/login/code/index.js +28 -0
  8. package/commands/icqq/login/code/index.ts +30 -0
  9. package/commands/icqq/login/confirm/index.js +20 -0
  10. package/commands/icqq/login/confirm/index.ts +21 -0
  11. package/commands/icqq/login/definition.js +65 -0
  12. package/commands/icqq/login/definition.ts +72 -0
  13. package/commands/icqq/login/qr/index.js +53 -0
  14. package/commands/icqq/login/qr/index.ts +55 -0
  15. package/commands/icqq/login/retry/index.js +21 -0
  16. package/commands/icqq/login/retry/index.ts +23 -0
  17. package/commands/icqq/login/status/index.js +31 -0
  18. package/commands/icqq/login/status/index.ts +34 -0
  19. package/lib/adapter.d.ts +13 -0
  20. package/lib/adapter.js +27 -0
  21. package/lib/client.d.ts +6 -9
  22. package/lib/client.js +1 -3
  23. package/lib/console-spec.d.ts +17 -0
  24. package/lib/console-spec.js +21 -0
  25. package/lib/console.d.ts +2 -0
  26. package/lib/console.js +13 -0
  27. package/lib/endpoint.d.ts +12 -15
  28. package/lib/endpoint.js +373 -164
  29. package/lib/group-moderation.d.ts +6 -3
  30. package/lib/group-moderation.js +9 -8
  31. package/lib/group-scene.d.ts +3 -3
  32. package/lib/group-scene.js +8 -7
  33. package/lib/icqq-login-assist.d.ts +3 -1
  34. package/lib/icqq-login-assist.js +3 -1
  35. package/lib/icqq-login-control.d.ts +24 -0
  36. package/lib/icqq-login-control.js +25 -0
  37. package/lib/index.d.ts +3 -2
  38. package/lib/index.js +2 -2
  39. package/lib/protocol.d.ts +13 -0
  40. package/lib/protocol.js +24 -9
  41. package/package.json +25 -17
  42. package/pages/index/index.js +15 -0
  43. package/pages/index/index.ts +16 -0
  44. package/plugin.js +3 -5
  45. package/schema.json +174 -0
  46. package/src/adapter.ts +29 -0
  47. package/src/client.ts +6 -11
  48. package/src/console-spec.ts +26 -0
  49. package/src/console.ts +17 -0
  50. package/src/endpoint.ts +381 -178
  51. package/src/group-moderation.ts +10 -10
  52. package/src/group-scene.ts +8 -8
  53. package/src/icqq-login-assist.ts +6 -2
  54. package/src/icqq-login-control.ts +42 -0
  55. package/src/index.ts +4 -1
  56. package/src/protocol.ts +35 -8
  57. package/lib/icqq-runtime-state.d.ts +0 -1
  58. package/lib/icqq-runtime-state.js +0 -6
  59. package/src/icqq-runtime-state.ts +0 -7
@@ -1,5 +1,8 @@
1
1
  import type { Client } from '@icqqjs/icqq';
2
- import type { GroupModerationAction, GroupModerationMember } from 'zhin.js/adapter';
2
+ import { type EndpointMemberInspectionRequest, type EndpointModerationRequest, type GroupModerationMember } from 'zhin.js/adapter';
3
3
  /** Native role/join facts stay in the adapter; the moderation plugin owns policy. */
4
- export declare function inspectGroupMember(client: Client, groupId: string, userId: string, signal: AbortSignal): Promise<GroupModerationMember>;
5
- export declare function moderateGroupMember(client: Client, groupId: string, userId: string, action: GroupModerationAction, signal: AbortSignal, isActive: () => boolean): Promise<void>;
4
+ export declare function inspectGroupMember(client: Client, { groupId, userId, signal }: EndpointMemberInspectionRequest): Promise<GroupModerationMember>;
5
+ export declare function moderateGroupMember({ client, isActive }: {
6
+ readonly client: Client;
7
+ readonly isActive: () => boolean;
8
+ }, { groupId, userId, action, signal }: EndpointModerationRequest): Promise<void>;
@@ -1,12 +1,13 @@
1
+ import { createEndpointOperation } from 'zhin.js/adapter';
1
2
  /** Native role/join facts stay in the adapter; the moderation plugin owns policy. */
2
- export async function inspectGroupMember(client, groupId, userId, signal) {
3
- signal.throwIfAborted();
3
+ export async function inspectGroupMember(client, { groupId, userId, signal }) {
4
+ const operation = createEndpointOperation({ signal });
4
5
  const group = numericId(groupId);
5
6
  const user = numericId(userId);
6
- const selfLookup = client.getGroupMemberInfo(group, client.uin, true);
7
+ const selfLookup = operation.run(() => client.getGroupMemberInfo(group, client.uin, true));
7
8
  const [self, member] = await Promise.all([
8
9
  selfLookup,
9
- user === client.uin ? selfLookup : client.getGroupMemberInfo(group, user, true),
10
+ user === client.uin ? selfLookup : operation.run(() => client.getGroupMemberInfo(group, user, true)),
10
11
  ]);
11
12
  signal.throwIfAborted();
12
13
  const joinedAt = member.join_time * 1000;
@@ -22,8 +23,8 @@ export async function inspectGroupMember(client, groupId, userId, signal) {
22
23
  ...(Number.isSafeInteger(joinedAt) && joinedAt > 0 && joinedAt <= Date.now() ? { joinedAt } : {}),
23
24
  };
24
25
  }
25
- export async function moderateGroupMember(client, groupId, userId, action, signal, isActive) {
26
- const member = await inspectGroupMember(client, groupId, userId, signal);
26
+ export async function moderateGroupMember({ client, isActive }, { groupId, userId, action, signal }) {
27
+ const member = await inspectGroupMember(client, { groupId: groupId, userId: userId, signal: signal });
27
28
  signal.throwIfAborted();
28
29
  if (!isActive() || !member.canModerate)
29
30
  throw new Error('Group moderation permission is unavailable');
@@ -38,8 +39,8 @@ export async function moderateGroupMember(client, groupId, userId, action, signa
38
39
  throw new Error('Invalid group mute duration');
39
40
  }
40
41
  const ok = action.type === 'kick'
41
- ? await client.setGroupKick(numericId(groupId), numericId(userId))
42
- : await client.setGroupBan(numericId(groupId), numericId(userId), action.durationSeconds);
42
+ ? await createEndpointOperation({ signal }).run(() => client.setGroupKick(numericId(groupId), numericId(userId)))
43
+ : await createEndpointOperation({ signal }).run(() => client.setGroupBan(numericId(groupId), numericId(userId), action.durationSeconds));
43
44
  if (!ok)
44
45
  throw new Error('Platform did not confirm the moderation action');
45
46
  }
@@ -1,11 +1,11 @@
1
1
  import type { Client } from '@icqqjs/icqq';
2
- import type { GroupMemberProfile, GroupSceneFacts } from 'zhin.js/adapter';
2
+ import { type EndpointGroupInspectionRequest, type EndpointMemberInspectionRequest, type GroupMemberProfile, type GroupSceneFacts } from 'zhin.js/adapter';
3
3
  /** Listing a very large roster only to find admins is not worth the packet; adminIds stay unknown. */
4
4
  export declare const ADMIN_SCAN_MAX_MEMBERS = 1000;
5
5
  /**
6
6
  * ADR 0041 scene facts: one uncached group-info read, plus (small groups only) one roster read for
7
7
  * admins. A roster failure leaves adminIds unknown instead of failing the other facts.
8
8
  */
9
- export declare function inspectGroupScene(client: Client, groupId: string, signal: AbortSignal): Promise<GroupSceneFacts>;
9
+ export declare function inspectGroupScene(client: Client, { groupId, signal }: EndpointGroupInspectionRequest): Promise<GroupSceneFacts>;
10
10
  /** ADR 0041 member facts from one uncached lookup (same rationale as the agent `freshMember` helper). */
11
- export declare function inspectGroupMemberProfile(client: Client, groupId: string, userId: string, signal: AbortSignal): Promise<GroupMemberProfile>;
11
+ export declare function inspectGroupMemberProfile(client: Client, { groupId, userId, signal }: EndpointMemberInspectionRequest): Promise<GroupMemberProfile>;
@@ -1,3 +1,4 @@
1
+ import { createEndpointOperation } from 'zhin.js/adapter';
1
2
  /** Listing a very large roster only to find admins is not worth the packet; adminIds stay unknown. */
2
3
  export const ADMIN_SCAN_MAX_MEMBERS = 1000;
3
4
  const MAX_ADMIN_IDS = 20;
@@ -14,10 +15,10 @@ function numericId(value) {
14
15
  * ADR 0041 scene facts: one uncached group-info read, plus (small groups only) one roster read for
15
16
  * admins. A roster failure leaves adminIds unknown instead of failing the other facts.
16
17
  */
17
- export async function inspectGroupScene(client, groupId, signal) {
18
- signal.throwIfAborted();
18
+ export async function inspectGroupScene(client, { groupId, signal }) {
19
+ const operation = createEndpointOperation({ signal });
19
20
  const group = numericId(groupId);
20
- const info = await client.getGroupInfo(group, true);
21
+ const info = await operation.run(() => client.getGroupInfo(group, true));
21
22
  signal.throwIfAborted();
22
23
  if (info.group_id !== group)
23
24
  return {};
@@ -26,7 +27,7 @@ export async function inspectGroupScene(client, groupId, signal) {
26
27
  let adminIds;
27
28
  if (memberCount === undefined || memberCount <= ADMIN_SCAN_MAX_MEMBERS) {
28
29
  try {
29
- const roster = await client.getGroupMemberList(group);
30
+ const roster = await operation.run(() => client.getGroupMemberList(group));
30
31
  signal.throwIfAborted();
31
32
  adminIds = [...roster.values()]
32
33
  .filter(member => member.role === 'admin' && member.group_id === group)
@@ -50,11 +51,11 @@ export async function inspectGroupScene(client, groupId, signal) {
50
51
  };
51
52
  }
52
53
  /** ADR 0041 member facts from one uncached lookup (same rationale as the agent `freshMember` helper). */
53
- export async function inspectGroupMemberProfile(client, groupId, userId, signal) {
54
- signal.throwIfAborted();
54
+ export async function inspectGroupMemberProfile(client, { groupId, userId, signal }) {
55
+ const operation = createEndpointOperation({ signal });
55
56
  const group = numericId(groupId);
56
57
  const user = numericId(userId);
57
- const member = await client.getGroupMemberInfo(group, user, true);
58
+ const member = await operation.run(() => client.getGroupMemberInfo(group, user, true));
58
59
  signal.throwIfAborted();
59
60
  if (member.user_id !== user || member.group_id !== group)
60
61
  return {};
@@ -1,5 +1,5 @@
1
1
  import type { Locale } from 'zhin.js/i18n';
2
- import type { LoginAssist, LoginAssistType } from '@zhin.js/core';
2
+ import type { LoginAssist, LoginAssistEndpointIdentity, LoginAssistType } from '@zhin.js/core';
3
3
  type IcqqLoginClient = {
4
4
  login(password?: string): Promise<unknown>;
5
5
  submitSlider(ticket: string): Promise<unknown>;
@@ -21,6 +21,8 @@ export declare function runIcqqLoginAssistStep(input: {
21
21
  adapter: string;
22
22
  endpointKey: string;
23
23
  owner: object;
24
+ signal?: AbortSignal;
25
+ endpointIdentity?: LoginAssistEndpointIdentity;
24
26
  logger: Logger;
25
27
  step: LoginAssistType;
26
28
  event: unknown;
@@ -17,6 +17,7 @@ export async function runIcqqLoginAssistStep(input) {
17
17
  ...(typeof payload.url === 'string' ? { url: payload.url } : {}),
18
18
  }));
19
19
  try {
20
+ input.signal?.throwIfAborted();
20
21
  if (step === 'device') {
21
22
  try {
22
23
  await client.sendSmsCode();
@@ -29,7 +30,8 @@ export async function runIcqqLoginAssistStep(input) {
29
30
  }));
30
31
  }
31
32
  }
32
- const value = await assist.waitForInput(adapter, endpointKey, step, payload, { owner });
33
+ const value = await assist.waitForInput(adapter, endpointKey, step, payload, { owner, signal: input.signal, endpointIdentity: input.endpointIdentity });
34
+ input.signal?.throwIfAborted();
33
35
  const text = normalizeSubmitValue(value);
34
36
  switch (step) {
35
37
  case 'qrcode':
@@ -0,0 +1,24 @@
1
+ import type { FeatureId, PluginId } from 'zhin.js';
2
+ import type { PendingLoginTask } from '@zhin.js/core';
3
+ export interface IcqqLoginControlState {
4
+ readonly endpointId: string;
5
+ readonly transportState: string;
6
+ readonly online: boolean;
7
+ readonly pending: readonly PendingLoginTask[];
8
+ }
9
+ export interface IcqqLoginControl {
10
+ readonly endpointId: string;
11
+ state(): IcqqLoginControlState;
12
+ pending(): readonly PendingLoginTask[];
13
+ /** Force a login attempt; rejects when the endpoint is stopped or the SDK login fails. */
14
+ relogin(): Promise<void>;
15
+ submit(taskId: string, value: string | Record<string, unknown>): boolean;
16
+ cancel(taskId: string, reason?: string): boolean;
17
+ }
18
+ /** Read the owning Adapter's actual incarnations; this projection retains no independent directory. */
19
+ export declare function icqqLoginControls(context: Readonly<{
20
+ owner: PluginId | Readonly<{
21
+ id: PluginId;
22
+ }>;
23
+ project<T>(id: FeatureId): T;
24
+ }>): ReadonlyMap<string, IcqqLoginControl>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * ICQQ login control surface exposed to the Plugin Runtime, Console and the
3
+ * recovery command. Controls are projected from actual current incarnations in
4
+ * the owning IcqqAdapter; no plugin resource keeps a second Endpoint directory.
5
+ */
6
+ import { adapterFeatureId, isAdapterIndex } from 'zhin.js/adapter';
7
+ import { IcqqAdapter } from './adapter.js';
8
+ /** Read the owning Adapter's actual incarnations; this projection retains no independent directory. */
9
+ export function icqqLoginControls(context) {
10
+ const index = context.project(adapterFeatureId);
11
+ if (!isAdapterIndex(index))
12
+ throw new Error('ICQQ Adapter projection is unavailable');
13
+ const owner = typeof context.owner === 'string' ? context.owner : context.owner.id;
14
+ const controls = new Map();
15
+ for (const adapter of index.forOwner(owner).adapters()) {
16
+ if (!(adapter instanceof IcqqAdapter))
17
+ continue;
18
+ for (const row of adapter.list()) {
19
+ const endpoint = adapter.getEndpoint(row.instanceId);
20
+ if (endpoint)
21
+ controls.set(row.endpointId, endpoint.loginControl);
22
+ }
23
+ }
24
+ return controls;
25
+ }
package/lib/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export { Actions, formatInboundContent, formatOutboundBody, formatOutboundMessages, icqqInboundConversation, icqqOutboundTarget, type ActionResult, type IcqqEndpointConfig, type IcqqInboundMessage, type IcqqWireSegment, type ParsedIcqqSendTarget, type ResolvedIcqqConfig, } from './protocol.js';
1
+ export { Actions, formatInboundContent, formatOutboundBody, formatOutboundMessages, formatOutboundMessageParts, icqqInboundConversation, icqqOutboundTarget, type ActionResult, type IcqqEndpointConfig, type IcqqInboundMessage, type IcqqWireSegment, type IcqqOutboundMessagePart, type ParsedIcqqSendTarget, type ResolvedIcqqConfig, } from './protocol.js';
2
2
  export { IcqqEndpoint, type IcqqEndpointOptions, } from './endpoint.js';
3
3
  export * from './types.js';
4
- export { icqqClient, type IcqqClient, type IcqqClientEventMap, } from './client.js';
4
+ export { type IcqqClient, type IcqqClientEventMap, } from './client.js';
5
5
  export { resolveIcqqInboundMessageId, } from './icqq-inbound.js';
6
+ export { IcqqAdapter } from './adapter.js';
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { Actions, formatInboundContent, formatOutboundBody, formatOutboundMessages, icqqInboundConversation, icqqOutboundTarget, } from './protocol.js';
1
+ export { Actions, formatInboundContent, formatOutboundBody, formatOutboundMessages, formatOutboundMessageParts, icqqInboundConversation, icqqOutboundTarget, } from './protocol.js';
2
2
  export { IcqqEndpoint, } from './endpoint.js';
3
3
  export * from './types.js';
4
- export { icqqClient, } from './client.js';
5
4
  export { resolveIcqqInboundMessageId, } from './icqq-inbound.js';
5
+ export { IcqqAdapter } from './adapter.js';
package/lib/protocol.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Sendable } from '@icqqjs/icqq';
2
2
  import { type ConversationRef } from '@zhin.js/im-contract';
3
+ import { type AdapterNotifyTarget, type ResolvedAdapterNotifyTarget } from 'zhin.js/adapter';
3
4
  import type { MessageSegment, SendContent } from "zhin.js";
4
5
  export type ActionResult = {
5
6
  id: string;
@@ -177,6 +178,8 @@ export interface IcqqEndpointConfig {
177
178
  readonly autoServer?: boolean;
178
179
  readonly qqnt?: boolean;
179
180
  readonly ntLogin?: boolean;
181
+ /** Optional out-of-band outage notification target (usually a QQ adapter instance). */
182
+ readonly notify?: AdapterNotifyTarget;
180
183
  }
181
184
  export interface ResolvedIcqqConfig {
182
185
  readonly context: 'icqq';
@@ -194,6 +197,7 @@ export interface ResolvedIcqqConfig {
194
197
  readonly autoServer?: boolean;
195
198
  readonly qqnt?: boolean;
196
199
  readonly ntLogin?: boolean;
200
+ readonly notify?: ResolvedAdapterNotifyTarget;
197
201
  }
198
202
  export interface IcqqWireSegment {
199
203
  readonly type: string;
@@ -201,6 +205,9 @@ export interface IcqqWireSegment {
201
205
  readonly platform?: Readonly<Record<string, unknown>>;
202
206
  }
203
207
  export interface IcqqInboundMessage {
208
+ /** Exact SDK callback object, operation-local and never a persisted projection. */
209
+ readonly event?: unknown;
210
+ readonly platformSentAt?: number;
204
211
  readonly id: string;
205
212
  readonly conversation: ConversationRef;
206
213
  readonly content: string;
@@ -269,4 +276,10 @@ export declare function formatOutboundBody(payload: unknown): Sendable;
269
276
  * quote or whitespace are dropped: a standalone element cannot carry the quote.
270
277
  */
271
278
  export declare function formatOutboundMessages(payload: unknown): Sendable[];
279
+ /** Native bubble and the exact source segment positions which produced it. */
280
+ export interface IcqqOutboundMessagePart {
281
+ readonly message: Sendable;
282
+ readonly sourceIndices?: readonly number[];
283
+ }
284
+ export declare function formatOutboundMessageParts(payload: unknown): IcqqOutboundMessagePart[];
272
285
  export declare function formatInboundContent(rawMessage: string): string;
package/lib/protocol.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { isMediaRef } from '@zhin.js/im-contract';
2
+ import { resolveAdapterNotifyTarget } from 'zhin.js/adapter';
2
3
  import { resolveIcqqMediaFile } from "./cq-message.js";
3
4
  export { resolveIcqqInboundMessageId } from "./icqq-inbound.js";
4
5
  /**
@@ -190,6 +191,7 @@ export function resolveIcqqConfig(config) {
190
191
  const autoServer = config.autoServer ?? true;
191
192
  const qqnt = config.qqnt;
192
193
  const ntLogin = config.ntLogin;
194
+ const notify = resolveAdapterNotifyTarget(config.notify);
193
195
  return {
194
196
  context: 'icqq',
195
197
  id,
@@ -206,6 +208,7 @@ export function resolveIcqqConfig(config) {
206
208
  ...(autoServer != null ? { autoServer } : {}),
207
209
  ...(qqnt != null ? { qqnt } : {}),
208
210
  ...(ntLogin != null ? { ntLogin } : {}),
211
+ ...(notify ? { notify } : {}),
209
212
  };
210
213
  }
211
214
  /**
@@ -326,22 +329,34 @@ export function formatOutboundBody(payload) {
326
329
  * quote or whitespace are dropped: a standalone element cannot carry the quote.
327
330
  */
328
331
  export function formatOutboundMessages(payload) {
332
+ return formatOutboundMessageParts(payload).map(part => part.message);
333
+ }
334
+ export function formatOutboundMessageParts(payload) {
329
335
  if (!Array.isArray(payload)) {
330
- return isWireSegment(payload) ? formatOutboundMessages([payload]) : [formatOutboundBody(payload)];
336
+ if (isWireSegment(payload))
337
+ return formatOutboundMessageParts([payload]);
338
+ const message = formatOutboundBody(payload);
339
+ return [{ message, ...(typeof payload === 'string' && message !== payload ? {} : { sourceIndices: [0] }) }];
331
340
  }
332
341
  const groups = [[]];
333
- for (const element of formatOutboundElements(payload)) {
334
- if (isStandaloneElement(element))
335
- groups.push([element], []);
336
- else
337
- groups.at(-1).push(element);
342
+ for (const [sourceIndex, source] of payload.entries()) {
343
+ for (const element of formatOutboundElements([source])) {
344
+ const item = { element, sourceIndex };
345
+ if (isStandaloneElement(element))
346
+ groups.push([item], []);
347
+ else
348
+ groups.at(-1).push(item);
349
+ }
338
350
  }
339
- const messages = groups.filter(group => group.some(element => typeof element === 'string'
351
+ const messages = groups.filter(group => group.some(({ element }) => typeof element === 'string'
340
352
  ? element.trim() !== ''
341
353
  : element.type !== 'reply' && element.type !== 'quote'));
342
354
  if (messages.length === 0)
343
- return ['\u200b'];
344
- return messages.map(group => group.length === 1 ? group[0] : group);
355
+ return [{ message: '\u200b', sourceIndices: [] }];
356
+ return messages.map(group => ({
357
+ message: group.length === 1 ? group[0].element : group.map(item => item.element),
358
+ sourceIndices: [...new Set(group.map(item => item.sourceIndex))],
359
+ }));
345
360
  }
346
361
  function isStandaloneElement(element) {
347
362
  return typeof element !== 'string' && ICQQ_STANDALONE_ELEMENTS.has(element.type);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-icqq",
3
- "version": "1.1.17",
3
+ "version": "1.1.19",
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,27 @@
33
33
  "directory": "plugins/adapters/icqq"
34
34
  },
35
35
  "dependencies": {
36
- "@zhin.js/adapter": "1.1.24",
37
- "@zhin.js/agent-feature": "1.1.10",
38
- "@zhin.js/core": "1.1.52",
39
- "@zhin.js/feature-kit": "1.1.9",
40
- "@zhin.js/im-contract": "1.1.8",
36
+ "@zhin.js/adapter": "1.1.26",
37
+ "@zhin.js/agent-feature": "1.1.11",
38
+ "@zhin.js/client": "1.1.22",
39
+ "@zhin.js/console-contract": "1.1.3",
40
+ "@zhin.js/core": "1.1.54",
41
+ "@zhin.js/feature-kit": "1.1.10",
42
+ "@zhin.js/im-contract": "1.1.9",
41
43
  "@zhin.js/logger": "1.1.2",
42
- "@zhin.js/permission": "1.1.9",
43
- "@zhin.js/skill": "1.1.10",
44
- "@zhin.js/tool": "1.1.11"
44
+ "@zhin.js/page": "1.1.11",
45
+ "@zhin.js/permission": "1.1.10",
46
+ "@zhin.js/skill": "1.1.11",
47
+ "@zhin.js/tool": "1.1.13"
45
48
  },
46
49
  "peerDependencies": {
47
50
  "@icqqjs/icqq": "^1.12.2",
48
51
  "zod": "^4.0.0",
49
- "@zhin.js/adapter": "^1.1.24",
50
- "@zhin.js/command": "^1.1.11",
51
- "@zhin.js/core": "^1.1.52",
52
- "@zhin.js/prompt-section": "^1.1.9",
53
- "zhin.js": "^1.1.17"
52
+ "@zhin.js/adapter": "^1.1.26",
53
+ "@zhin.js/command": "^1.1.12",
54
+ "@zhin.js/core": "^1.1.54",
55
+ "@zhin.js/prompt-section": "^1.1.10",
56
+ "zhin.js": "^1.1.19"
54
57
  },
55
58
  "peerDependenciesMeta": {
56
59
  "@icqqjs/icqq": {
@@ -75,12 +78,13 @@
75
78
  "typescript": "^6.0.3",
76
79
  "vitest": "^4.1.10",
77
80
  "zod": "^4.4.3",
78
- "@zhin.js/prompt-section": "1.1.9",
79
- "zhin.js": "1.1.17"
81
+ "@zhin.js/prompt-section": "1.1.10",
82
+ "zhin.js": "1.1.19"
80
83
  },
81
84
  "files": [
82
85
  "adapters",
83
86
  "commands",
87
+ "pages",
84
88
  "plugin.js",
85
89
  "schema.json",
86
90
  "src",
@@ -107,7 +111,7 @@
107
111
  "features": [
108
112
  {
109
113
  "package": "@zhin.js/adapter",
110
- "api": "^1.0.0"
114
+ "api": "^2.0.0"
111
115
  },
112
116
  {
113
117
  "package": "@zhin.js/command",
@@ -124,6 +128,10 @@
124
128
  {
125
129
  "package": "@zhin.js/agent-feature",
126
130
  "api": "^1.0.0"
131
+ },
132
+ {
133
+ "package": "@zhin.js/page",
134
+ "api": "^1.0.0"
127
135
  }
128
136
  ],
129
137
  "plugins": []
@@ -0,0 +1,15 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { definePage } from '@zhin.js/console-contract';
3
+ import { createAdapterConsolePage } from '@zhin.js/client';
4
+ export const meta = definePage({
5
+ title: 'ICQQ',
6
+ titleI18n: { en: 'ICQQ' },
7
+ icon: 'MessageCircle',
8
+ order: 64,
9
+ requiredPermissions: ['icqq:read'],
10
+ });
11
+ /**
12
+ * Convention page: `pages/index/index.ts` → `/icqq`(插件路径)。
13
+ * 连接状态、连接测试、endpoint 增删;登录待办来自 LoginAssist(ICQQ 等)。
14
+ */
15
+ export default createAdapterConsolePage('icqq');
@@ -0,0 +1,16 @@
1
+ import { definePage } from '@zhin.js/console-contract';
2
+ import { createAdapterConsolePage } from '@zhin.js/client';
3
+
4
+ export const meta = definePage({
5
+ title: 'ICQQ',
6
+ titleI18n: { en: 'ICQQ' },
7
+ icon: 'MessageCircle',
8
+ order: 64,
9
+ requiredPermissions: ['icqq:read'],
10
+ });
11
+
12
+ /**
13
+ * Convention page: `pages/index/index.ts` → `/icqq`(插件路径)。
14
+ * 连接状态、连接测试、endpoint 增删;登录待办来自 LoginAssist(ICQQ 等)。
15
+ */
16
+ export default createAdapterConsolePage('icqq');
package/plugin.js CHANGED
@@ -1,16 +1,14 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { createEndpointRuntimeState } from 'zhin.js/adapter';
3
- import { definePlugin } from 'zhin.js';
2
+ import { definePlugin, pluginConsoleRpcToken } from 'zhin.js';
4
3
  import { permissionHostToken, createSceneRolePlatformChecker } from '@zhin.js/permission';
5
- import { icqqRuntimeStateToken } from "./lib/icqq-runtime-state.js";
4
+ import { createIcqqConsoleRpc } from "./lib/console.js";
6
5
  export default definePlugin({
7
6
  name: 'icqq',
8
7
  metadata: {
9
8
  displayName: 'ICQQ Adapter',
10
9
  },
11
10
  setup(context) {
12
- // 运行中 endpoint 注册表(icqq endpoint list 的"运行中"数据源)
13
- context.resources.provide(icqqRuntimeStateToken, createEndpointRuntimeState());
11
+ context.resources.provide(pluginConsoleRpcToken, createIcqqConsoleRpc(() => context.config.get()));
14
12
  const disposePlatform = context.resources.has(permissionHostToken)
15
13
  ? context.resources.use(permissionHostToken).registerPlatform('icqq', createSceneRolePlatformChecker())
16
14
  : undefined;
package/schema.json CHANGED
@@ -346,6 +346,93 @@
346
346
  "description": "Command prefix for this endpoint."
347
347
  }
348
348
  }
349
+ },
350
+ "notify": {
351
+ "type": "object",
352
+ "additionalProperties": false,
353
+ "description": "掉线/登录失败时通过另一个适配器实例给 master 发通知(adapter/endpoint/kind/id 指向实际会话)",
354
+ "x-i18n": {
355
+ "en": {
356
+ "description": "Send outage/login notices to the master through another adapter instance (adapter/endpoint/kind/id identify the actual conversation)."
357
+ }
358
+ },
359
+ "properties": {
360
+ "adapter": {
361
+ "type": "string",
362
+ "description": "通知使用的适配器类型,如 qq / icqq",
363
+ "x-i18n": {
364
+ "en": {
365
+ "description": "Adapter type used for the notice, such as qq or icqq."
366
+ }
367
+ }
368
+ },
369
+ "endpoint": {
370
+ "type": "string",
371
+ "description": "该适配器的 endpoint id",
372
+ "x-i18n": {
373
+ "en": {
374
+ "description": "Endpoint id of that adapter."
375
+ }
376
+ }
377
+ },
378
+ "kind": {
379
+ "type": "string",
380
+ "enum": [
381
+ "private",
382
+ "group",
383
+ "channel"
384
+ ],
385
+ "default": "private",
386
+ "description": "会话类型,默认 private",
387
+ "x-i18n": {
388
+ "en": {
389
+ "description": "Conversation kind; defaults to private."
390
+ }
391
+ }
392
+ },
393
+ "id": {
394
+ "type": [
395
+ "string",
396
+ "number"
397
+ ],
398
+ "description": "master 在该平台上的会话 id(QQ 官方为 openid,ICQQ 为 QQ 号)",
399
+ "x-i18n": {
400
+ "en": {
401
+ "description": "Master conversation id on that platform (openid for QQ official, QQ number for ICQQ)."
402
+ }
403
+ }
404
+ },
405
+ "parent": {
406
+ "type": "object",
407
+ "additionalProperties": false,
408
+ "properties": {
409
+ "kind": {
410
+ "type": "string",
411
+ "enum": [
412
+ "private",
413
+ "group",
414
+ "channel"
415
+ ]
416
+ },
417
+ "id": {
418
+ "type": [
419
+ "string",
420
+ "number"
421
+ ]
422
+ }
423
+ },
424
+ "required": [
425
+ "kind",
426
+ "id"
427
+ ],
428
+ "description": "可选的父频道/群容器"
429
+ }
430
+ },
431
+ "required": [
432
+ "adapter",
433
+ "endpoint",
434
+ "id"
435
+ ]
349
436
  }
350
437
  },
351
438
  "required": [
@@ -367,6 +454,93 @@
367
454
  "description": "Command prefix, empty by default. Each endpoints[i] can override it."
368
455
  }
369
456
  }
457
+ },
458
+ "notify": {
459
+ "type": "object",
460
+ "additionalProperties": false,
461
+ "description": "掉线/登录失败时通过另一个适配器实例给 master 发通知(adapter/endpoint/kind/id 指向实际会话)",
462
+ "x-i18n": {
463
+ "en": {
464
+ "description": "Send outage/login notices to the master through another adapter instance (adapter/endpoint/kind/id identify the actual conversation)."
465
+ }
466
+ },
467
+ "properties": {
468
+ "adapter": {
469
+ "type": "string",
470
+ "description": "通知使用的适配器类型,如 qq / icqq",
471
+ "x-i18n": {
472
+ "en": {
473
+ "description": "Adapter type used for the notice, such as qq or icqq."
474
+ }
475
+ }
476
+ },
477
+ "endpoint": {
478
+ "type": "string",
479
+ "description": "该适配器的 endpoint id",
480
+ "x-i18n": {
481
+ "en": {
482
+ "description": "Endpoint id of that adapter."
483
+ }
484
+ }
485
+ },
486
+ "kind": {
487
+ "type": "string",
488
+ "enum": [
489
+ "private",
490
+ "group",
491
+ "channel"
492
+ ],
493
+ "default": "private",
494
+ "description": "会话类型,默认 private",
495
+ "x-i18n": {
496
+ "en": {
497
+ "description": "Conversation kind; defaults to private."
498
+ }
499
+ }
500
+ },
501
+ "id": {
502
+ "type": [
503
+ "string",
504
+ "number"
505
+ ],
506
+ "description": "master 在该平台上的会话 id(QQ 官方为 openid,ICQQ 为 QQ 号)",
507
+ "x-i18n": {
508
+ "en": {
509
+ "description": "Master conversation id on that platform (openid for QQ official, QQ number for ICQQ)."
510
+ }
511
+ }
512
+ },
513
+ "parent": {
514
+ "type": "object",
515
+ "additionalProperties": false,
516
+ "properties": {
517
+ "kind": {
518
+ "type": "string",
519
+ "enum": [
520
+ "private",
521
+ "group",
522
+ "channel"
523
+ ]
524
+ },
525
+ "id": {
526
+ "type": [
527
+ "string",
528
+ "number"
529
+ ]
530
+ }
531
+ },
532
+ "required": [
533
+ "kind",
534
+ "id"
535
+ ],
536
+ "description": "可选的父频道/群容器"
537
+ }
538
+ },
539
+ "required": [
540
+ "adapter",
541
+ "endpoint",
542
+ "id"
543
+ ]
370
544
  }
371
545
  },
372
546
  "required": [