@zhin.js/adapter-qq 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 (122) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +75 -21
  3. package/adapters/qq/index.js +4 -33
  4. package/adapters/qq/index.ts +3 -41
  5. package/agents/qq/boundaries.md +2 -0
  6. package/agents/qq/conversation.js +92 -0
  7. package/agents/qq/conversation.ts +99 -0
  8. package/agents/qq/messages.js +60 -0
  9. package/agents/qq/messages.ts +60 -0
  10. package/agents/qq/skills/qq/SKILL.md +8 -2
  11. package/agents/qq/skills/qq/tools/add_role/index.js +2 -1
  12. package/agents/qq/skills/qq/tools/add_role/index.ts +2 -1
  13. package/agents/qq/skills/qq/tools/channel_info/index.js +1 -1
  14. package/agents/qq/skills/qq/tools/channel_info/index.ts +1 -1
  15. package/agents/qq/skills/qq/tools/create_role/index.js +1 -1
  16. package/agents/qq/skills/qq/tools/create_role/index.ts +1 -1
  17. package/agents/qq/skills/qq/tools/guild_kick_member/index.js +30 -0
  18. package/agents/qq/skills/qq/tools/guild_kick_member/index.ts +29 -0
  19. package/agents/qq/skills/qq/tools/guild_mute_member/index.js +31 -0
  20. package/agents/qq/skills/qq/tools/guild_mute_member/index.ts +30 -0
  21. package/agents/qq/skills/qq/tools/list_channels/index.js +1 -1
  22. package/agents/qq/skills/qq/tools/list_channels/index.ts +1 -1
  23. package/agents/qq/skills/qq/tools/list_roles/index.js +1 -1
  24. package/agents/qq/skills/qq/tools/list_roles/index.ts +1 -1
  25. package/agents/qq/skills/qq/tools/member_detail/index.js +1 -1
  26. package/agents/qq/skills/qq/tools/member_detail/index.ts +1 -1
  27. package/agents/qq/skills/qq/tools/remove_role/index.js +2 -1
  28. package/agents/qq/skills/qq/tools/remove_role/index.ts +2 -1
  29. package/agents/qq/skills/qq-conversation/SKILL.md +24 -0
  30. package/agents/qq/skills/qq-conversation/tools/conversation_info/index.js +78 -0
  31. package/agents/qq/skills/qq-conversation/tools/conversation_info/index.ts +76 -0
  32. package/agents/qq/skills/qq-directory/tools/list_guilds/index.js +1 -1
  33. package/agents/qq/skills/qq-directory/tools/list_guilds/index.ts +1 -1
  34. package/agents/qq/skills/qq-group-admin/SKILL.md +34 -0
  35. package/agents/qq/skills/qq-group-admin/tools/group_kick/index.js +39 -0
  36. package/agents/qq/skills/qq-group-admin/tools/group_kick/index.ts +38 -0
  37. package/agents/qq/skills/qq-group-admin/tools/handle_join_request/index.js +33 -0
  38. package/agents/qq/skills/qq-group-admin/tools/handle_join_request/index.ts +32 -0
  39. package/agents/qq/skills/qq-group-admin/tools/recall_member_message/index.js +25 -0
  40. package/agents/qq/skills/qq-group-admin/tools/recall_member_message/index.ts +25 -0
  41. package/agents/qq/skills/qq-group-admin/tools/set_mute/index.js +37 -0
  42. package/agents/qq/skills/qq-group-admin/tools/set_mute/index.ts +36 -0
  43. package/agents/qq/skills/qq-group-directory/SKILL.md +24 -0
  44. package/agents/qq/skills/qq-group-directory/tools/group_info/index.js +16 -0
  45. package/agents/qq/skills/qq-group-directory/tools/group_info/index.ts +16 -0
  46. package/agents/qq/skills/qq-group-directory/tools/group_member_info/index.js +19 -0
  47. package/agents/qq/skills/qq-group-directory/tools/group_member_info/index.ts +19 -0
  48. package/agents/qq/skills/qq-group-directory/tools/group_members/index.js +19 -0
  49. package/agents/qq/skills/qq-group-directory/tools/group_members/index.ts +19 -0
  50. package/agents/qq/skills/qq-group-state/SKILL.md +21 -0
  51. package/agents/qq/skills/qq-group-state/tools/group_blacklist/index.js +19 -0
  52. package/agents/qq/skills/qq-group-state/tools/group_blacklist/index.ts +19 -0
  53. package/agents/qq/skills/qq-group-state/tools/list_join_requests/index.js +19 -0
  54. package/agents/qq/skills/qq-group-state/tools/list_join_requests/index.ts +19 -0
  55. package/agents/qq/skills/qq-group-state/tools/list_muted/index.js +21 -0
  56. package/agents/qq/skills/qq-group-state/tools/list_muted/index.ts +21 -0
  57. package/agents/qq/skills/qq-messages/SKILL.md +27 -0
  58. package/agents/qq/skills/qq-messages/tools/quote_reply/index.js +21 -0
  59. package/agents/qq/skills/qq-messages/tools/quote_reply/index.ts +21 -0
  60. package/agents/qq/skills/qq-messages/tools/recall_own_message/index.js +18 -0
  61. package/agents/qq/skills/qq-messages/tools/recall_own_message/index.ts +17 -0
  62. package/commands/qq/endpoint/definition.js +73 -55
  63. package/commands/qq/endpoint/definition.ts +75 -86
  64. package/lib/adapter.d.ts +13 -0
  65. package/lib/adapter.js +27 -0
  66. package/lib/client.d.ts +7 -9
  67. package/lib/client.js +1 -2
  68. package/lib/console.d.ts +3 -0
  69. package/lib/console.js +93 -0
  70. package/lib/endpoint.d.ts +17 -27
  71. package/lib/endpoint.js +325 -146
  72. package/lib/group-admin.d.ts +41 -0
  73. package/lib/group-admin.js +114 -0
  74. package/lib/i18n.js +4 -4
  75. package/lib/image-size.d.ts +6 -1
  76. package/lib/image-size.js +2 -3
  77. package/lib/index.d.ts +5 -4
  78. package/lib/index.js +3 -3
  79. package/lib/interaction.js +1 -1
  80. package/lib/outbound.d.ts +4 -1
  81. package/lib/outbound.js +3 -2
  82. package/lib/protocol.d.ts +7 -0
  83. package/lib/protocol.js +4 -0
  84. package/lib/qq-console-spec.d.ts +17 -0
  85. package/lib/qq-console-spec.js +21 -0
  86. package/lib/qq-console.d.ts +10 -0
  87. package/lib/qq-console.js +126 -0
  88. package/lib/qq-endpoint-write.d.ts +18 -0
  89. package/lib/qq-endpoint-write.js +7 -0
  90. package/lib/qq-runtime-state.d.ts +29 -9
  91. package/lib/qq-runtime-state.js +5 -5
  92. package/lib/qq-send.d.ts +7 -14
  93. package/lib/qq-send.js +20 -6
  94. package/lib/webhook.d.ts +3 -6
  95. package/lib/webhook.js +6 -49
  96. package/lib/ws.d.ts +10 -33
  97. package/lib/ws.js +21 -70
  98. package/middlewares/bot-kind-reply/index.js +1 -2
  99. package/middlewares/bot-kind-reply/index.ts +0 -2
  100. package/package.json +26 -18
  101. package/pages/index/index.js +15 -0
  102. package/pages/index/index.ts +16 -0
  103. package/plugin.js +9 -6
  104. package/schema.json +174 -0
  105. package/src/adapter.ts +23 -0
  106. package/src/client.ts +4 -12
  107. package/src/console.ts +103 -0
  108. package/src/endpoint.ts +328 -141
  109. package/src/group-admin.ts +155 -0
  110. package/src/i18n.ts +4 -4
  111. package/src/image-size.ts +7 -3
  112. package/src/index.ts +5 -5
  113. package/src/interaction.ts +1 -1
  114. package/src/outbound.ts +3 -2
  115. package/src/protocol.ts +10 -0
  116. package/src/qq-console-spec.ts +29 -0
  117. package/src/qq-console.ts +130 -0
  118. package/src/qq-endpoint-write.ts +24 -0
  119. package/src/qq-runtime-state.ts +31 -15
  120. package/src/qq-send.ts +28 -12
  121. package/src/webhook.ts +7 -57
  122. package/src/ws.ts +27 -105
package/schema.json CHANGED
@@ -197,6 +197,93 @@
197
197
  }
198
198
  },
199
199
  "description": "Optional fixed loopback TCP fault proxy for WSS only; original gateway TLS identity must match serverName."
200
+ },
201
+ "notify": {
202
+ "type": "object",
203
+ "additionalProperties": false,
204
+ "description": "掉线/登录失败时通过另一个适配器实例给 master 发通知(adapter/endpoint/kind/id 指向实际会话)",
205
+ "x-i18n": {
206
+ "en": {
207
+ "description": "Send outage/login notices to the master through another adapter instance (adapter/endpoint/kind/id identify the actual conversation)."
208
+ }
209
+ },
210
+ "properties": {
211
+ "adapter": {
212
+ "type": "string",
213
+ "description": "通知使用的适配器类型,如 qq / icqq",
214
+ "x-i18n": {
215
+ "en": {
216
+ "description": "Adapter type used for the notice, such as qq or icqq."
217
+ }
218
+ }
219
+ },
220
+ "endpoint": {
221
+ "type": "string",
222
+ "description": "该适配器的 endpoint id",
223
+ "x-i18n": {
224
+ "en": {
225
+ "description": "Endpoint id of that adapter."
226
+ }
227
+ }
228
+ },
229
+ "kind": {
230
+ "type": "string",
231
+ "enum": [
232
+ "private",
233
+ "group",
234
+ "channel"
235
+ ],
236
+ "default": "private",
237
+ "description": "会话类型,默认 private",
238
+ "x-i18n": {
239
+ "en": {
240
+ "description": "Conversation kind; defaults to private."
241
+ }
242
+ }
243
+ },
244
+ "id": {
245
+ "type": [
246
+ "string",
247
+ "number"
248
+ ],
249
+ "description": "master 在该平台上的会话 id(QQ 官方为 openid,ICQQ 为 QQ 号)",
250
+ "x-i18n": {
251
+ "en": {
252
+ "description": "Master conversation id on that platform (openid for QQ official, QQ number for ICQQ)."
253
+ }
254
+ }
255
+ },
256
+ "parent": {
257
+ "type": "object",
258
+ "additionalProperties": false,
259
+ "properties": {
260
+ "kind": {
261
+ "type": "string",
262
+ "enum": [
263
+ "private",
264
+ "group",
265
+ "channel"
266
+ ]
267
+ },
268
+ "id": {
269
+ "type": [
270
+ "string",
271
+ "number"
272
+ ]
273
+ }
274
+ },
275
+ "required": [
276
+ "kind",
277
+ "id"
278
+ ],
279
+ "description": "可选的父频道/群容器"
280
+ }
281
+ },
282
+ "required": [
283
+ "adapter",
284
+ "endpoint",
285
+ "id"
286
+ ]
200
287
  }
201
288
  },
202
289
  "required": [
@@ -240,6 +327,93 @@
240
327
  }
241
328
  },
242
329
  "description": "Optional fixed loopback TCP fault proxy for WSS only; original gateway TLS identity must match serverName."
330
+ },
331
+ "notify": {
332
+ "type": "object",
333
+ "additionalProperties": false,
334
+ "description": "掉线/登录失败时通过另一个适配器实例给 master 发通知(adapter/endpoint/kind/id 指向实际会话)",
335
+ "x-i18n": {
336
+ "en": {
337
+ "description": "Send outage/login notices to the master through another adapter instance (adapter/endpoint/kind/id identify the actual conversation)."
338
+ }
339
+ },
340
+ "properties": {
341
+ "adapter": {
342
+ "type": "string",
343
+ "description": "通知使用的适配器类型,如 qq / icqq",
344
+ "x-i18n": {
345
+ "en": {
346
+ "description": "Adapter type used for the notice, such as qq or icqq."
347
+ }
348
+ }
349
+ },
350
+ "endpoint": {
351
+ "type": "string",
352
+ "description": "该适配器的 endpoint id",
353
+ "x-i18n": {
354
+ "en": {
355
+ "description": "Endpoint id of that adapter."
356
+ }
357
+ }
358
+ },
359
+ "kind": {
360
+ "type": "string",
361
+ "enum": [
362
+ "private",
363
+ "group",
364
+ "channel"
365
+ ],
366
+ "default": "private",
367
+ "description": "会话类型,默认 private",
368
+ "x-i18n": {
369
+ "en": {
370
+ "description": "Conversation kind; defaults to private."
371
+ }
372
+ }
373
+ },
374
+ "id": {
375
+ "type": [
376
+ "string",
377
+ "number"
378
+ ],
379
+ "description": "master 在该平台上的会话 id(QQ 官方为 openid,ICQQ 为 QQ 号)",
380
+ "x-i18n": {
381
+ "en": {
382
+ "description": "Master conversation id on that platform (openid for QQ official, QQ number for ICQQ)."
383
+ }
384
+ }
385
+ },
386
+ "parent": {
387
+ "type": "object",
388
+ "additionalProperties": false,
389
+ "properties": {
390
+ "kind": {
391
+ "type": "string",
392
+ "enum": [
393
+ "private",
394
+ "group",
395
+ "channel"
396
+ ]
397
+ },
398
+ "id": {
399
+ "type": [
400
+ "string",
401
+ "number"
402
+ ]
403
+ }
404
+ },
405
+ "required": [
406
+ "kind",
407
+ "id"
408
+ ],
409
+ "description": "可选的父频道/群容器"
410
+ }
411
+ },
412
+ "required": [
413
+ "adapter",
414
+ "endpoint",
415
+ "id"
416
+ ]
243
417
  }
244
418
  },
245
419
  "required": [
package/src/adapter.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { Adapter, createAdapterOutageNotifier, type AdapterContext, type EndpointCreationContext } from 'zhin.js/adapter';
2
+ import { outboundHostToken } from 'zhin.js';
3
+ import { httpHostToken } from '@zhin.js/host-http';
4
+ import { QqHttpEndpoint, QqWebsocketEndpoint } from './endpoint.js';
5
+ import { resolveQqConfig, type QqEndpointConfig } from './protocol.js';
6
+ import type { QqClientEventMap } from './client.js';
7
+
8
+ export class QqAdapter extends Adapter<QqHttpEndpoint | QqWebsocketEndpoint, QqEndpointConfig, QqClientEventMap> {
9
+ constructor(private readonly context: AdapterContext<QqEndpointConfig>) { super(); }
10
+ override get configurationIdentityFields(): readonly string[] { return ['id', 'appid']; }
11
+ override get configurationIdentityProbeFields(): readonly string[] { return ['accessTokenUrl', 'gatewayUrl']; }
12
+ override get configurationUpdateFields(): readonly string[] { return ['secret', 'mode', 'sandbox', 'intents', 'botKind',
13
+ 'accessTokenUrl', 'gatewayUrl', 'streamProxy', 'streamProxy.port', 'streamProxy.serverName', 'webhookPath']; }
14
+ protected override validateConfiguration(input: QqEndpointConfig): void { resolveQqConfig(input); }
15
+ protected createEndpoint(input: QqEndpointConfig, execution: EndpointCreationContext): QqHttpEndpoint | QqWebsocketEndpoint {
16
+ const config = resolveQqConfig(input);
17
+ const outbound = this.context.has?.(outboundHostToken) ? this.context.use(outboundHostToken) : undefined;
18
+ const notifier = createAdapterOutageNotifier(outbound, config.notify);
19
+ return config.mode === 'websocket'
20
+ ? new QqWebsocketEndpoint({ id: execution.canonicalId, config, notifier })
21
+ : new QqHttpEndpoint({ id: execution.canonicalId, config, notifier, http: this.context.use(httpHostToken) });
22
+ }
23
+ }
package/src/client.ts CHANGED
@@ -1,16 +1,8 @@
1
- import { defineEndpointClient } from 'zhin.js/adapter';
2
- import type { QqInboundMessage } from './protocol.js';
3
- import type { QqBotTransport } from './ws.js';
1
+ import type { EventMap } from 'qq-official-bot';
2
+ import type { QqAdapter } from './adapter.js';
4
3
 
5
- export type QqClientEventMap = Record<string, QqInboundMessage | unknown>;
4
+ export type QqClientEventMap = { readonly [K in keyof EventMap]: EventMap[K] extends (event: infer E, ...args: never[]) => unknown ? E : never };
6
5
 
7
6
  declare module '@zhin.js/feature-kit' {
8
- interface AdapterClientRegistry {
9
- readonly qq: {
10
- readonly client: QqBotTransport;
11
- readonly events: QqClientEventMap;
12
- };
13
- }
7
+ interface AdapterRegistry { readonly qq: QqAdapter; }
14
8
  }
15
-
16
- export const qqClient = defineEndpointClient<QqBotTransport, QqClientEventMap>('qq');
package/src/console.ts ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * QQ Console RPC:除通用 endpoint 管理外,把 `qq endpoint add` 的扫码绑定
3
+ * (以及公域 / 私域选择、取消)搬到 Console 页面。
4
+ */
5
+ import {
6
+ prepareConsoleAdapterMutation,
7
+ ownedConsoleAdapter,
8
+ createAdapterConsoleRpc,
9
+ type AdapterConsoleSpec,
10
+ } from 'zhin.js/adapter';
11
+ import { PluginConsoleRpcError, type PluginConsoleRpc } from 'zhin.js';
12
+ import { qqConsoleSpec } from './qq-console-spec.js';
13
+ import {
14
+ cancelQqConsoleBind,
15
+ completeQqConsoleBind,
16
+ startQqConsoleBind,
17
+ } from './qq-console.js';
18
+ import type { QqRuntimeState } from './qq-runtime-state.js';
19
+
20
+ export function createQqConsoleRpc(
21
+ state: QqRuntimeState,
22
+ getConfig: () => unknown,
23
+ ): PluginConsoleRpc {
24
+ const spec: AdapterConsoleSpec = {
25
+ ...qqConsoleSpec,
26
+ getConfig,
27
+ extraStatus: () => {
28
+ const bind = state.consoleBind;
29
+ if (!bind) {
30
+ return { bindState: state.bindFlow ? 'binding' : 'idle' };
31
+ }
32
+ return {
33
+ bindState: bind.state,
34
+ ...(bind.url ? { scanUrl: bind.url } : {}),
35
+ ...(bind.message ? { bindMessage: bind.message } : {}),
36
+ ...(bind.pending ? { bindEndpoint: bind.pending.endpointId } : {}),
37
+ ...(bind.error ? { bindError: bind.error } : {}),
38
+ };
39
+ },
40
+ availableActions: () => {
41
+ if (state.consoleBind?.pending) return ['qq-bind-kind', 'qq-bind-cancel'];
42
+ if (state.bindFlow || state.consoleBind?.state === 'waiting') return ['qq-bind-cancel'];
43
+ return ['qq-bind-start'];
44
+ },
45
+ actions: {
46
+ 'qq-bind-start': {
47
+ label: '扫码绑定新机器人',
48
+ labelI18n: { en: 'Bind a bot with QR' },
49
+ description: '用手机 QQ 扫码,选择公域 / 私域后保存到账号库;保存后需手动启动。',
50
+ descriptionI18n: { en: 'Scan with mobile QQ, choose public/private and save the account; start it explicitly afterwards.' },
51
+ handler: async (input, call) => {
52
+ if (!call.runtime) throw new PluginConsoleRpcError('unsupported', '账号运行目录不可用');
53
+ const adapter = ownedConsoleAdapter(spec, call);
54
+ if (!adapter.canManage) throw new PluginConsoleRpcError('unsupported', '此环境未提供持久化账号管理');
55
+ if (input.expectedGeneration !== call.runtime.generation || input.expectedRevision !== adapter.revision) {
56
+ throw new PluginConsoleRpcError('conflict', '账号清单已变化,请刷新核实');
57
+ }
58
+ const starting = startQqConsoleBind(state);
59
+ state.consoleAdmission = { adapterId: adapter.descriptor.id, generation: call.runtime.generation, revision: adapter.revision };
60
+ const { url } = await starting;
61
+ return { message: '请用手机 QQ 扫码;扫码成功后选择公域或私域', url };
62
+ },
63
+ },
64
+ 'qq-bind-kind': {
65
+ accountMutation: true,
66
+ label: '选择公域 / 私域',
67
+ labelI18n: { en: 'Choose public/private' },
68
+ description: '公域订阅 PUBLIC_GUILD_MESSAGES;私域订阅 GUILD_MESSAGES。',
69
+ descriptionI18n: { en: 'Public subscribes PUBLIC_GUILD_MESSAGES; private subscribes GUILD_MESSAGES.' },
70
+ options: [
71
+ { value: 'public', label: '公域', labelI18n: { en: 'Public' } },
72
+ { value: 'private', label: '私域', labelI18n: { en: 'Private' } },
73
+ ],
74
+ handler: async (input, call) => {
75
+ const captured = state.consoleAdmission;
76
+ if (!captured || captured.generation !== input.expectedGeneration || captured.revision !== input.expectedRevision) {
77
+ throw new PluginConsoleRpcError('conflict', '扫码期间账号清单已变化,请刷新核实');
78
+ }
79
+ const admission = prepareConsoleAdapterMutation(spec, call, input);
80
+ if (admission.adapter.descriptor.id !== captured.adapterId) throw new PluginConsoleRpcError('conflict', '扫码所属 Adapter 已变化');
81
+ const result = await completeQqConsoleBind(state, config => admission.adapter.add(admission.revision,
82
+ { endpointId: config.id, config: { ...config } }, admission.options), input.value);
83
+ return {
84
+ message: `已保存账号「${result.endpointId}」(${result.botKind}),尚未启动`,
85
+ saved: true, restartRequired: false, execution: { ...result.mutation.execution },
86
+ details: { endpoint: result.endpointId, botKind: result.botKind, intents: result.intents.join(', ') },
87
+ };
88
+ },
89
+ },
90
+ 'qq-bind-cancel': {
91
+ label: '取消扫码绑定',
92
+ labelI18n: { en: 'Cancel QR binding' },
93
+ description: '中止正在进行的扫码绑定;已写入的配置不会删除。',
94
+ descriptionI18n: { en: 'Stop the current QR binding flow; already-saved configuration is untouched.' },
95
+ handler: async () => {
96
+ const cancelled = cancelQqConsoleBind(state);
97
+ return { message: cancelled ? '已取消绑定' : '当前没有进行中的绑定流程' };
98
+ },
99
+ },
100
+ },
101
+ };
102
+ return createAdapterConsoleRpc(spec);
103
+ }