@zhin.js/adapter-icqq 1.1.16 → 1.1.18

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 (75) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +44 -3
  3. package/adapters/icqq/index.js +10 -2
  4. package/adapters/icqq/index.ts +10 -2
  5. package/agents/icqq/members.js +16 -0
  6. package/agents/icqq/members.ts +17 -0
  7. package/agents/icqq/skills/icqq-group-admin/tools/essence/index.js +1 -1
  8. package/agents/icqq/skills/icqq-group-admin/tools/essence/index.ts +1 -1
  9. package/agents/icqq/skills/icqq-group-admin/tools/group_invite/index.js +2 -2
  10. package/agents/icqq/skills/icqq-group-admin/tools/group_invite/index.ts +2 -2
  11. package/agents/icqq/skills/icqq-group-admin/tools/group_leave/index.js +1 -0
  12. package/agents/icqq/skills/icqq-group-admin/tools/group_leave/index.ts +1 -0
  13. package/agents/icqq/skills/icqq-group-admin/tools/recall_member_message/index.js +2 -1
  14. package/agents/icqq/skills/icqq-group-admin/tools/recall_member_message/index.ts +2 -1
  15. package/agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.js +2 -2
  16. package/agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.ts +2 -2
  17. package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.js +4 -4
  18. package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.ts +4 -4
  19. package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.js +4 -4
  20. package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.ts +4 -4
  21. package/agents/icqq/skills/icqq-group-directory/tools/group_member_info/index.js +16 -6
  22. package/agents/icqq/skills/icqq-group-directory/tools/group_member_info/index.ts +16 -5
  23. package/agents/icqq/skills/icqq-messages/tools/forward_messages/index.js +1 -1
  24. package/agents/icqq/skills/icqq-messages/tools/forward_messages/index.ts +1 -1
  25. package/agents/icqq/skills/icqq-messages/tools/quote_reply/index.js +1 -1
  26. package/agents/icqq/skills/icqq-messages/tools/quote_reply/index.ts +1 -1
  27. package/agents/icqq/skills/icqq-messages/tools/recall_own_message/index.js +1 -1
  28. package/agents/icqq/skills/icqq-messages/tools/recall_own_message/index.ts +1 -1
  29. package/agents/icqq/system.md +3 -3
  30. package/commands/icqq/login/code/index.js +28 -0
  31. package/commands/icqq/login/code/index.ts +30 -0
  32. package/commands/icqq/login/confirm/index.js +20 -0
  33. package/commands/icqq/login/confirm/index.ts +21 -0
  34. package/commands/icqq/login/definition.js +65 -0
  35. package/commands/icqq/login/definition.ts +72 -0
  36. package/commands/icqq/login/qr/index.js +53 -0
  37. package/commands/icqq/login/qr/index.ts +55 -0
  38. package/commands/icqq/login/retry/index.js +21 -0
  39. package/commands/icqq/login/retry/index.ts +23 -0
  40. package/commands/icqq/login/status/index.js +31 -0
  41. package/commands/icqq/login/status/index.ts +34 -0
  42. package/lib/console-spec.d.ts +17 -0
  43. package/lib/console-spec.js +11 -0
  44. package/lib/console.d.ts +8 -0
  45. package/lib/console.js +16 -0
  46. package/lib/endpoint.d.ts +9 -2
  47. package/lib/endpoint.js +200 -45
  48. package/lib/group-moderation.js +7 -2
  49. package/lib/group-scene.d.ts +11 -0
  50. package/lib/group-scene.js +71 -0
  51. package/lib/icqq-inbound.d.ts +2 -0
  52. package/lib/icqq-inbound.js +3 -0
  53. package/lib/icqq-inbox.d.ts +5 -0
  54. package/lib/icqq-inbox.js +14 -0
  55. package/lib/icqq-login-control.d.ts +18 -0
  56. package/lib/icqq-login-control.js +8 -0
  57. package/lib/index.d.ts +1 -1
  58. package/lib/index.js +1 -1
  59. package/lib/protocol.d.ts +12 -0
  60. package/lib/protocol.js +28 -11
  61. package/package.json +17 -9
  62. package/pages/index/index.js +15 -0
  63. package/pages/index/index.ts +16 -0
  64. package/plugin.js +6 -1
  65. package/schema.json +174 -0
  66. package/src/console-spec.ts +17 -0
  67. package/src/console.ts +21 -0
  68. package/src/endpoint.ts +199 -49
  69. package/src/group-moderation.ts +7 -2
  70. package/src/group-scene.ts +76 -0
  71. package/src/icqq-inbound.ts +5 -0
  72. package/src/icqq-inbox.ts +15 -0
  73. package/src/icqq-login-control.ts +30 -0
  74. package/src/index.ts +2 -0
  75. package/src/protocol.ts +38 -10
@@ -1,7 +1,7 @@
1
1
  # ICQQ Platform Agent
2
2
 
3
- You are the icqq platform specialist. Interpret requests in the current icqq conversation and load only the narrowest private Skill needed for the task.
3
+ You are the icqq platform specialist. Interpret requests in the current icqq conversation and load only the narrowest available Skill needed for the task.
4
4
 
5
- For your own group/guild role, current scene information or platform permissions, load `icqq-conversation` and query `conversation_info`. Sender roles describe the requester, not you. Do not infer your role from old session summaries or another endpoint account.
5
+ Use the current Bot snapshot for your platform role. Load `icqq-conversation` and query `conversation_info` when the role is unknown or you need more scene or permission details. Sender roles describe the requester, not you. Do not infer your role from old session summaries or another endpoint account.
6
6
 
7
- For quoting, forwarding or recalling your own speech, load `icqq-messages`. Your own-message recall needs no group management role or per-call approval. For recalling another member's message or leaving a group, load `icqq-group-admin` and follow the tool-specific rules; only leaving requires fresh approval. All these message tools are restricted to the current conversation. Guild-channel limitations are explicit in `conversation_info`.
7
+ Discover message and management operations from the current scene's Skill catalog; follow each tool's target, platform and approval rules. Recalling your own messages needs no group management role or per-call approval. Leaving a group requires fresh approval and is forbidden when you own the group. Do not load a Skill that is absent from the current catalog.
@@ -0,0 +1,28 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineCommand } from 'zhin.js/command';
3
+ import { icqqLoginControlToken, icqqLoginForbidden, isIcqqLoginOperator, pendingTaskLabel, pickIcqqLoginControl, } from '../definition.js';
4
+ export default defineCommand({
5
+ description: '提交 ICQQ 滑块/短信/设备验证内容',
6
+ descriptionI18n: { en: 'Submit an ICQQ slider, SMS or device verification value' },
7
+ async execute({ args, config, input, use }) {
8
+ if (!isIcqqLoginOperator(config, input))
9
+ return icqqLoginForbidden();
10
+ const controls = use(icqqLoginControlToken);
11
+ // One endpoint: the whole argument is the code. Multiple: first arg selects the UIN.
12
+ const requested = controls.size > 1 ? args[0] : undefined;
13
+ const value = (controls.size > 1 ? args.slice(1) : args).join(' ').trim();
14
+ const picked = pickIcqqLoginControl(controls, requested);
15
+ if (!picked.control)
16
+ return picked.text ?? 'ICQQ 适配器还没有启动';
17
+ if (!value) {
18
+ return '请把验证内容放在命令后面,例如 `icqq login code 123456` 或 `icqq login code <ticket>`。';
19
+ }
20
+ const task = picked.control.pending()
21
+ .find((item) => item.type === 'slider' || item.type === 'device' || item.type === 'sms' || item.type === 'other');
22
+ if (!task)
23
+ return '当前没有滑块/短信/设备验证待办;发送 `icqq login qr` 查看二维码。';
24
+ if (!picked.control.submit(task.id, value))
25
+ return '登录待办已失效,请重新触发 `icqq login qr`。';
26
+ return `已提交 ICQQ ${task.endpointKey} 的${pendingTaskLabel(task.type)}内容,正在验证。`;
27
+ },
28
+ });
@@ -0,0 +1,30 @@
1
+ import { defineCommand } from 'zhin.js/command';
2
+ import {
3
+ icqqLoginControlToken,
4
+ icqqLoginForbidden,
5
+ isIcqqLoginOperator,
6
+ pendingTaskLabel,
7
+ pickIcqqLoginControl,
8
+ } from '../definition.js';
9
+
10
+ export default defineCommand({
11
+ description: '提交 ICQQ 滑块/短信/设备验证内容',
12
+ descriptionI18n: { en: 'Submit an ICQQ slider, SMS or device verification value' },
13
+ async execute({ args, config, input, use }) {
14
+ if (!isIcqqLoginOperator(config, input)) return icqqLoginForbidden();
15
+ const controls = use(icqqLoginControlToken);
16
+ // One endpoint: the whole argument is the code. Multiple: first arg selects the UIN.
17
+ const requested = controls.size > 1 ? args[0] : undefined;
18
+ const value = (controls.size > 1 ? args.slice(1) : args).join(' ').trim();
19
+ const picked = pickIcqqLoginControl(controls, requested);
20
+ if (!picked.control) return picked.text ?? 'ICQQ 适配器还没有启动';
21
+ if (!value) {
22
+ return '请把验证内容放在命令后面,例如 `icqq login code 123456` 或 `icqq login code <ticket>`。';
23
+ }
24
+ const task = picked.control.pending()
25
+ .find((item) => item.type === 'slider' || item.type === 'device' || item.type === 'sms' || item.type === 'other');
26
+ if (!task) return '当前没有滑块/短信/设备验证待办;发送 `icqq login qr` 查看二维码。';
27
+ if (!picked.control.submit(task.id, value)) return '登录待办已失效,请重新触发 `icqq login qr`。';
28
+ return `已提交 ICQQ ${task.endpointKey} 的${pendingTaskLabel(task.type)}内容,正在验证。`;
29
+ },
30
+ });
@@ -0,0 +1,20 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineCommand } from 'zhin.js/command';
3
+ import { icqqLoginControlToken, icqqLoginForbidden, isIcqqLoginOperator, pickIcqqLoginControl, } from '../definition.js';
4
+ export default defineCommand({
5
+ description: '确认已完成 ICQQ 扫码登录',
6
+ descriptionI18n: { en: 'Confirm the ICQQ QR login was scanned' },
7
+ async execute({ args, config, input, use }) {
8
+ if (!isIcqqLoginOperator(config, input))
9
+ return icqqLoginForbidden();
10
+ const picked = pickIcqqLoginControl(use(icqqLoginControlToken), args[0]);
11
+ if (!picked.control)
12
+ return picked.text ?? 'ICQQ 适配器还没有启动';
13
+ const task = picked.control.pending().find((item) => item.type === 'qrcode' || item.type === 'auth');
14
+ if (!task)
15
+ return '当前没有等待确认的扫码登录;发送 `icqq login qr` 获取二维码。';
16
+ if (!picked.control.submit(task.id, 'ok'))
17
+ return '登录待办已失效,请重新发送 `icqq login qr`。';
18
+ return `已提交 ICQQ ${task.endpointKey} 的扫码确认,正在登录。`;
19
+ },
20
+ });
@@ -0,0 +1,21 @@
1
+ import { defineCommand } from 'zhin.js/command';
2
+ import {
3
+ icqqLoginControlToken,
4
+ icqqLoginForbidden,
5
+ isIcqqLoginOperator,
6
+ pickIcqqLoginControl,
7
+ } from '../definition.js';
8
+
9
+ export default defineCommand({
10
+ description: '确认已完成 ICQQ 扫码登录',
11
+ descriptionI18n: { en: 'Confirm the ICQQ QR login was scanned' },
12
+ async execute({ args, config, input, use }) {
13
+ if (!isIcqqLoginOperator(config, input)) return icqqLoginForbidden();
14
+ const picked = pickIcqqLoginControl(use(icqqLoginControlToken), args[0]);
15
+ if (!picked.control) return picked.text ?? 'ICQQ 适配器还没有启动';
16
+ const task = picked.control.pending().find((item) => item.type === 'qrcode' || item.type === 'auth');
17
+ if (!task) return '当前没有等待确认的扫码登录;发送 `icqq login qr` 获取二维码。';
18
+ if (!picked.control.submit(task.id, 'ok')) return '登录待办已失效,请重新发送 `icqq login qr`。';
19
+ return `已提交 ICQQ ${task.endpointKey} 的扫码确认,正在登录。`;
20
+ },
21
+ });
@@ -0,0 +1,65 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ /**
3
+ * `icqq login` recovery commands.
4
+ *
5
+ * They are registered by the ICQQ plugin but can be invoked from any connected
6
+ * adapter (typically the QQ instance that delivered the outage notice), so the
7
+ * master can fetch the QR image and confirm/send verification codes without the
8
+ * Console or terminal.
9
+ */
10
+ import { icqqLoginControlToken } from "../../../lib/icqq-login-control.js";
11
+ export { icqqLoginControlToken };
12
+ /** Master = framework master(s), or the configured outage-notify conversation id(s). */
13
+ export function isIcqqLoginOperator(config, input) {
14
+ const cfg = (config ?? {});
15
+ const masters = new Set();
16
+ const collect = (value) => {
17
+ if (value === undefined || value === null)
18
+ return;
19
+ const text = String(value).trim();
20
+ if (text)
21
+ masters.add(text);
22
+ };
23
+ collect(cfg.master);
24
+ collect(cfg.notify?.id);
25
+ if (Array.isArray(cfg.endpoints)) {
26
+ for (const endpoint of cfg.endpoints) {
27
+ collect(endpoint.master);
28
+ collect(endpoint.notify?.id);
29
+ }
30
+ }
31
+ // Login control is account-sensitive: without an explicit master/notify target,
32
+ // refuse chat recovery and leave it to the authenticated Console page.
33
+ if (masters.size === 0)
34
+ return false;
35
+ const senderRaw = input?.sender;
36
+ const senderId = (typeof senderRaw === 'object' && senderRaw?.id) ? String(senderRaw.id).trim() : '';
37
+ return senderId !== '' && masters.has(senderId);
38
+ }
39
+ export function pickIcqqLoginControl(controls, requested) {
40
+ const wanted = requested?.trim();
41
+ if (wanted) {
42
+ const control = controls.get(wanted);
43
+ return control ? { control } : { text: `找不到 ICQQ endpoint「${wanted}」` };
44
+ }
45
+ if (controls.size === 0)
46
+ return { text: 'ICQQ 适配器还没有启动' };
47
+ if (controls.size === 1) {
48
+ const control = [...controls.values()][0];
49
+ return control ? { control } : { text: 'ICQQ 适配器还没有启动' };
50
+ }
51
+ return { text: `有多个 ICQQ endpoint,请指定 QQ 号:${[...controls.keys()].join('、')}` };
52
+ }
53
+ export function icqqLoginForbidden() {
54
+ return '仅 master 或 notify 目标账号可以操作 ICQQ 登录';
55
+ }
56
+ export function pendingTaskLabel(type) {
57
+ switch (type) {
58
+ case 'qrcode': return '二维码';
59
+ case 'slider': return '滑块验证';
60
+ case 'device': return '设备确认';
61
+ case 'sms': return '短信验证';
62
+ case 'auth': return '登录确认';
63
+ default: return '登录验证';
64
+ }
65
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * `icqq login` recovery commands.
3
+ *
4
+ * They are registered by the ICQQ plugin but can be invoked from any connected
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
7
+ * Console or terminal.
8
+ */
9
+ import { icqqLoginControlToken, type IcqqLoginControl } from '../../../src/icqq-login-control.js';
10
+
11
+ export { icqqLoginControlToken };
12
+
13
+ /** Master = framework master(s), or the configured outage-notify conversation id(s). */
14
+ export function isIcqqLoginOperator(config: unknown, input: unknown): boolean {
15
+ const cfg = (config ?? {}) as {
16
+ master?: unknown;
17
+ notify?: { id?: unknown };
18
+ endpoints?: readonly { master?: unknown; notify?: { id?: unknown } }[];
19
+ };
20
+ const masters = new Set<string>();
21
+ const collect = (value: unknown) => {
22
+ if (value === undefined || value === null) return;
23
+ const text = String(value).trim();
24
+ if (text) masters.add(text);
25
+ };
26
+ collect(cfg.master);
27
+ collect(cfg.notify?.id);
28
+ if (Array.isArray(cfg.endpoints)) {
29
+ for (const endpoint of cfg.endpoints) {
30
+ collect(endpoint.master);
31
+ collect(endpoint.notify?.id);
32
+ }
33
+ }
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);
40
+ }
41
+
42
+ export function pickIcqqLoginControl(
43
+ controls: ReadonlyMap<string, IcqqLoginControl>,
44
+ requested?: string,
45
+ ): { control?: IcqqLoginControl; text?: string } {
46
+ const wanted = requested?.trim();
47
+ if (wanted) {
48
+ const control = controls.get(wanted);
49
+ return control ? { control } : { text: `找不到 ICQQ endpoint「${wanted}」` };
50
+ }
51
+ if (controls.size === 0) return { text: 'ICQQ 适配器还没有启动' };
52
+ if (controls.size === 1) {
53
+ const control = [...controls.values()][0];
54
+ return control ? { control } : { text: 'ICQQ 适配器还没有启动' };
55
+ }
56
+ return { text: `有多个 ICQQ endpoint,请指定 QQ 号:${[...controls.keys()].join('、')}` };
57
+ }
58
+
59
+ export function icqqLoginForbidden(): string {
60
+ return '仅 master 或 notify 目标账号可以操作 ICQQ 登录';
61
+ }
62
+
63
+ export function pendingTaskLabel(type: string): string {
64
+ switch (type) {
65
+ case 'qrcode': return '二维码';
66
+ case 'slider': return '滑块验证';
67
+ case 'device': return '设备确认';
68
+ case 'sms': return '短信验证';
69
+ case 'auth': return '登录确认';
70
+ default: return '登录验证';
71
+ }
72
+ }
@@ -0,0 +1,53 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineCommand } from 'zhin.js/command';
3
+ import { icqqLoginControlToken, icqqLoginForbidden, isIcqqLoginOperator, pickIcqqLoginControl, } from '../definition.js';
4
+ const QR_IMAGE = /^data:image\/[a-z0-9.+-]+;base64,(.+)$/iu;
5
+ export default defineCommand({
6
+ description: '获取 ICQQ 登录二维码(发给当前会话)',
7
+ descriptionI18n: { en: 'Fetch the ICQQ login QR code into this conversation' },
8
+ async execute({ args, config, input, use }) {
9
+ if (!isIcqqLoginOperator(config, input))
10
+ return icqqLoginForbidden();
11
+ const picked = pickIcqqLoginControl(use(icqqLoginControlToken), args[0]);
12
+ if (!picked.control)
13
+ return picked.text ?? 'ICQQ 适配器还没有启动';
14
+ const control = picked.control;
15
+ const findQr = () => control.pending().find((task) => task.type === 'qrcode' || task.type === 'auth');
16
+ let task = findQr();
17
+ if (!task) {
18
+ const other = control.pending().find((item) => item.type !== 'qrcode' && item.type !== 'auth');
19
+ if (other) {
20
+ const url = typeof other.payload.url === 'string' ? other.payload.url : '';
21
+ return `ICQQ ${other.endpointKey} 当前是${other.type === 'slider' ? '滑块验证' : '登录验证'},请打开 ${url || '验证页面'},`
22
+ + '完成后回复 `icqq login code <内容>`。';
23
+ }
24
+ try {
25
+ await control.relogin();
26
+ }
27
+ catch {
28
+ // The SDK may still deliver a QR challenge asynchronously.
29
+ }
30
+ task = findQr();
31
+ if (!task)
32
+ return '已触发重新登录,请 5 秒后再发送 `icqq login qr` 获取二维码。';
33
+ }
34
+ const raw = typeof task.payload.image === 'string' ? task.payload.image : '';
35
+ const base64 = QR_IMAGE.exec(raw)?.[1];
36
+ if (!base64) {
37
+ return `ICQQ ${task.endpointKey} 当前是 ${task.type === 'qrcode' ? '二维码' : '登录确认'}待办,但没有可发送的图片;请打开 Console 的 ICQQ 页面完成。`;
38
+ }
39
+ return [
40
+ {
41
+ type: 'text',
42
+ data: {
43
+ text: `ICQQ ${task.endpointKey} 登录二维码(约 3 分钟有效)。\n`
44
+ + '用手机 QQ 扫码,完成后回复 `icqq login confirm`。',
45
+ },
46
+ },
47
+ {
48
+ type: 'image',
49
+ data: { media: { kind: 'base64', value: base64, mime_type: 'image/png' } },
50
+ },
51
+ ];
52
+ },
53
+ });
@@ -0,0 +1,55 @@
1
+ import { defineCommand } from 'zhin.js/command';
2
+ import {
3
+ icqqLoginControlToken,
4
+ icqqLoginForbidden,
5
+ isIcqqLoginOperator,
6
+ pickIcqqLoginControl,
7
+ } from '../definition.js';
8
+
9
+ const QR_IMAGE = /^data:image\/[a-z0-9.+-]+;base64,(.+)$/iu;
10
+
11
+ export default defineCommand({
12
+ description: '获取 ICQQ 登录二维码(发给当前会话)',
13
+ descriptionI18n: { en: 'Fetch the ICQQ login QR code into this conversation' },
14
+ async execute({ args, config, input, use }) {
15
+ if (!isIcqqLoginOperator(config, input)) return icqqLoginForbidden();
16
+ const picked = pickIcqqLoginControl(use(icqqLoginControlToken), args[0]);
17
+ if (!picked.control) return picked.text ?? 'ICQQ 适配器还没有启动';
18
+ const control = picked.control;
19
+ const findQr = () => control.pending().find((task) => task.type === 'qrcode' || task.type === 'auth');
20
+ let task = findQr();
21
+ if (!task) {
22
+ const other = control.pending().find((item) => item.type !== 'qrcode' && item.type !== 'auth');
23
+ if (other) {
24
+ const url = typeof other.payload.url === 'string' ? other.payload.url : '';
25
+ return `ICQQ ${other.endpointKey} 当前是${other.type === 'slider' ? '滑块验证' : '登录验证'},请打开 ${url || '验证页面'},`
26
+ + '完成后回复 `icqq login code <内容>`。';
27
+ }
28
+ try {
29
+ await control.relogin();
30
+ } catch {
31
+ // The SDK may still deliver a QR challenge asynchronously.
32
+ }
33
+ task = findQr();
34
+ if (!task) return '已触发重新登录,请 5 秒后再发送 `icqq login qr` 获取二维码。';
35
+ }
36
+ const raw = typeof task.payload.image === 'string' ? task.payload.image : '';
37
+ const base64 = QR_IMAGE.exec(raw)?.[1];
38
+ if (!base64) {
39
+ return `ICQQ ${task.endpointKey} 当前是 ${task.type === 'qrcode' ? '二维码' : '登录确认'}待办,但没有可发送的图片;请打开 Console 的 ICQQ 页面完成。`;
40
+ }
41
+ return [
42
+ {
43
+ type: 'text',
44
+ data: {
45
+ text: `ICQQ ${task.endpointKey} 登录二维码(约 3 分钟有效)。\n`
46
+ + '用手机 QQ 扫码,完成后回复 `icqq login confirm`。',
47
+ },
48
+ },
49
+ {
50
+ type: 'image',
51
+ data: { media: { kind: 'base64', value: base64, mime_type: 'image/png' } },
52
+ },
53
+ ];
54
+ },
55
+ });
@@ -0,0 +1,21 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineCommand } from 'zhin.js/command';
3
+ import { icqqLoginControlToken, icqqLoginForbidden, isIcqqLoginOperator, pickIcqqLoginControl, } from '../definition.js';
4
+ export default defineCommand({
5
+ description: '重新触发 ICQQ 登录',
6
+ descriptionI18n: { en: 'Trigger an ICQQ login attempt again' },
7
+ async execute({ args, config, input, use }) {
8
+ if (!isIcqqLoginOperator(config, input))
9
+ return icqqLoginForbidden();
10
+ const picked = pickIcqqLoginControl(use(icqqLoginControlToken), args[0]);
11
+ if (!picked.control)
12
+ return picked.text ?? 'ICQQ 适配器还没有启动';
13
+ try {
14
+ await picked.control.relogin();
15
+ return `已触发 ICQQ ${picked.control.endpointId} 重新登录;需要扫码时发送 \`icqq login qr\`。`;
16
+ }
17
+ catch {
18
+ return `ICQQ ${picked.control.endpointId} 重新登录失败,请稍后重试或查看日志。`;
19
+ }
20
+ },
21
+ });
@@ -0,0 +1,23 @@
1
+ import { defineCommand } from 'zhin.js/command';
2
+ import {
3
+ icqqLoginControlToken,
4
+ icqqLoginForbidden,
5
+ isIcqqLoginOperator,
6
+ pickIcqqLoginControl,
7
+ } from '../definition.js';
8
+
9
+ export default defineCommand({
10
+ description: '重新触发 ICQQ 登录',
11
+ descriptionI18n: { en: 'Trigger an ICQQ login attempt again' },
12
+ async execute({ args, config, input, use }) {
13
+ if (!isIcqqLoginOperator(config, input)) return icqqLoginForbidden();
14
+ const picked = pickIcqqLoginControl(use(icqqLoginControlToken), args[0]);
15
+ if (!picked.control) return picked.text ?? 'ICQQ 适配器还没有启动';
16
+ try {
17
+ await picked.control.relogin();
18
+ return `已触发 ICQQ ${picked.control.endpointId} 重新登录;需要扫码时发送 \`icqq login qr\`。`;
19
+ } catch {
20
+ return `ICQQ ${picked.control.endpointId} 重新登录失败,请稍后重试或查看日志。`;
21
+ }
22
+ },
23
+ });
@@ -0,0 +1,31 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineCommand } from 'zhin.js/command';
3
+ import { icqqLoginControlToken, icqqLoginForbidden, isIcqqLoginOperator, pendingTaskLabel, pickIcqqLoginControl, } from '../definition.js';
4
+ export default defineCommand({
5
+ description: '查看 ICQQ 掉线状态与登录待办',
6
+ descriptionI18n: { en: 'Show ICQQ offline status and pending login tasks' },
7
+ async execute({ args, config, input, use }) {
8
+ if (!isIcqqLoginOperator(config, input))
9
+ return icqqLoginForbidden();
10
+ const picked = pickIcqqLoginControl(use(icqqLoginControlToken), args[0]);
11
+ if (!picked.control)
12
+ return picked.text ?? 'ICQQ 适配器还没有启动';
13
+ const state = picked.control.state();
14
+ const lines = [
15
+ `ICQQ ${state.endpointId}: ${state.online ? '在线' : '离线'}(${state.transportState})`,
16
+ ];
17
+ if (state.pending.length === 0) {
18
+ lines.push('没有待处理的登录验证。');
19
+ }
20
+ else {
21
+ lines.push('待处理:');
22
+ for (const task of state.pending) {
23
+ const url = typeof task.payload.url === 'string' ? task.payload.url : undefined;
24
+ lines.push(`- ${pendingTaskLabel(task.type)}(${task.id},endpoint ${task.endpointKey})`
25
+ + (url ? `:${url}` : ''));
26
+ }
27
+ lines.push('二维码:`icqq login qr`;完成扫码后:`icqq login confirm`;滑块/短信:`icqq login code <内容>`。');
28
+ }
29
+ return lines.join('\n');
30
+ },
31
+ });
@@ -0,0 +1,34 @@
1
+ import { defineCommand } from 'zhin.js/command';
2
+ import {
3
+ icqqLoginControlToken,
4
+ icqqLoginForbidden,
5
+ isIcqqLoginOperator,
6
+ pendingTaskLabel,
7
+ pickIcqqLoginControl,
8
+ } from '../definition.js';
9
+
10
+ export default defineCommand({
11
+ description: '查看 ICQQ 掉线状态与登录待办',
12
+ descriptionI18n: { en: 'Show ICQQ offline status and pending login tasks' },
13
+ async execute({ args, config, input, use }) {
14
+ if (!isIcqqLoginOperator(config, input)) return icqqLoginForbidden();
15
+ const picked = pickIcqqLoginControl(use(icqqLoginControlToken), args[0]);
16
+ if (!picked.control) return picked.text ?? 'ICQQ 适配器还没有启动';
17
+ const state = picked.control.state();
18
+ const lines = [
19
+ `ICQQ ${state.endpointId}: ${state.online ? '在线' : '离线'}(${state.transportState})`,
20
+ ];
21
+ if (state.pending.length === 0) {
22
+ lines.push('没有待处理的登录验证。');
23
+ } else {
24
+ lines.push('待处理:');
25
+ for (const task of state.pending) {
26
+ const url = typeof task.payload.url === 'string' ? task.payload.url : undefined;
27
+ lines.push(`- ${pendingTaskLabel(task.type)}(${task.id},endpoint ${task.endpointKey})`
28
+ + (url ? `:${url}` : ''));
29
+ }
30
+ lines.push('二维码:`icqq login qr`;完成扫码后:`icqq login confirm`;滑块/短信:`icqq login code <内容>`。');
31
+ }
32
+ return lines.join('\n');
33
+ },
34
+ });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * ICQQ Console 页面元数据(浏览器与服务端共享;不要在这里 import 服务端代码)。
3
+ */
4
+ import type { AdapterConsoleField } from 'zhin.js/adapter';
5
+ export declare const icqqConsoleSpec: {
6
+ readonly adapterKey: "icqq";
7
+ readonly adapterDisplayName: "ICQQ";
8
+ readonly fields: readonly AdapterConsoleField[];
9
+ readonly docsUrl: "https://zhin.js.org/adapters/icqq";
10
+ readonly readPermission: "icqq:read";
11
+ readonly writePermission: "icqq:write";
12
+ readonly idPattern: RegExp;
13
+ readonly idHint: "ICQQ endpoint id 是 QQ 号(纯数字)";
14
+ readonly idHintI18n: {
15
+ readonly en: "ICQQ endpoint ID is the QQ number (digits only)";
16
+ };
17
+ };
@@ -0,0 +1,11 @@
1
+ export const icqqConsoleSpec = {
2
+ adapterKey: 'icqq',
3
+ adapterDisplayName: 'ICQQ',
4
+ fields: [],
5
+ docsUrl: 'https://zhin.js.org/adapters/icqq',
6
+ readPermission: 'icqq:read',
7
+ writePermission: 'icqq:write',
8
+ idPattern: /^\d+$/,
9
+ idHint: 'ICQQ endpoint id 是 QQ 号(纯数字)',
10
+ idHintI18n: { en: 'ICQQ endpoint ID is the QQ number (digits only)' },
11
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ICQQ Console RPC:运行状态 + endpoint 增删(原 `icqq endpoint` 聊天命令)。
3
+ *
4
+ * 页面绑定 `pages/index`,读取 / 写入分别要求 `<adapter>:read` / `<adapter>:write`。
5
+ */
6
+ import { type AdapterConsoleResources } from 'zhin.js/adapter';
7
+ import type { PluginConsoleRpc } from 'zhin.js';
8
+ export declare function createIcqqConsoleRpc(resources: AdapterConsoleResources, getConfig: () => unknown): PluginConsoleRpc;
package/lib/console.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * ICQQ Console RPC:运行状态 + endpoint 增删(原 `icqq endpoint` 聊天命令)。
3
+ *
4
+ * 页面绑定 `pages/index`,读取 / 写入分别要求 `<adapter>:read` / `<adapter>:write`。
5
+ */
6
+ import { createAdapterConsoleRpc } from 'zhin.js/adapter';
7
+ import { icqqRuntimeStateToken } from './icqq-runtime-state.js';
8
+ import { icqqConsoleSpec } from './console-spec.js';
9
+ export function createIcqqConsoleRpc(resources, getConfig) {
10
+ const state = resources.use(icqqRuntimeStateToken);
11
+ return createAdapterConsoleRpc({
12
+ ...icqqConsoleSpec,
13
+ running: () => state.endpoints.values(),
14
+ getConfig,
15
+ }, resources);
16
+ }
package/lib/endpoint.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { Client } from '@icqqjs/icqq';
2
- import { Endpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest, type EndpointTransportState } from 'zhin.js/adapter';
2
+ import { Endpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest, type EndpointTransportState, type AdapterOutageNotifier } from 'zhin.js/adapter';
3
3
  import type { EndpointContentPort } from '@zhin.js/adapter';
4
4
  import { type LoginAssist } from '@zhin.js/core';
5
+ import { type EndpointSendResult } from '@zhin.js/im-contract';
5
6
  import type { CapabilityId } from 'zhin.js';
7
+ import type { IcqqLoginControl } from './icqq-login-control.js';
6
8
  import { type IcqqInboundMessage, type ResolvedIcqqConfig } from './protocol.js';
7
9
  import { ManagedIcqqClient } from './managed-client.js';
8
10
  export interface IcqqEndpointOptions {
@@ -10,20 +12,25 @@ export interface IcqqEndpointOptions {
10
12
  readonly config: ResolvedIcqqConfig;
11
13
  readonly loginAssist: LoginAssist;
12
14
  readonly locale?: import('zhin.js/i18n').Locale;
15
+ /** Out-of-band outage notifier; absent in tests/hosts without outbound. */
16
+ readonly notifier?: AdapterOutageNotifier;
13
17
  }
14
18
  export declare class IcqqEndpoint extends Endpoint<Client> {
15
19
  #private;
16
20
  readonly client: ManagedIcqqClient;
17
21
  readonly endpointName: string;
18
22
  readonly content: EndpointContentPort;
23
+ readonly loginControl: IcqqLoginControl;
19
24
  readonly management: EndpointManagement;
20
25
  readonly control: EndpointControl;
21
26
  constructor(options: IcqqEndpointOptions);
27
+ /** Force a login attempt (manual recovery command). */
28
+ relogin(): Promise<void>;
22
29
  get transportState(): EndpointTransportState;
23
30
  start(signal: AbortSignal): Promise<void>;
24
31
  open(): void;
25
32
  close(): Promise<void>;
26
33
  stop(): Promise<void>;
27
- send({ conversation, payload }: EndpointSendRequest): Promise<string>;
34
+ send({ conversation, payload }: EndpointSendRequest): Promise<EndpointSendResult>;
28
35
  admit(msg: IcqqInboundMessage): Promise<void>;
29
36
  }