@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/lib/qq-send.d.ts CHANGED
@@ -1,16 +1,4 @@
1
- /**
2
- * One QQ outbound send: passive reply when a usable source exists, active message otherwise, with
3
- * the fallbacks the platform's error codes allow. Shared by the websocket and webhook Endpoints.
4
- *
5
- * 1. Each message claims a passive source (explicit reply segment first, else the conversation's
6
- * newest usable msg_id / event_id) with its own msg_seq.
7
- * 2. Source unusable (expired, over the limit, invalid id): mark it and send that message once as
8
- * an active message. Never after a timeout: the first request may have been delivered.
9
- * 3. Markdown not allowed: resend the same message as plain text.
10
- * 4. Active message refused (no permission, quota, user switched it off): remember the conversation
11
- * for a while and fail fast instead of calling the platform again.
12
- */
13
- import type { ConversationRef } from '@zhin.js/im-contract';
1
+ import { type ConversationRef } from '@zhin.js/im-contract';
14
2
  import type { QqOutboundMessage } from './outbound.js';
15
3
  import { type QqActiveMessageGate, type QqPassiveReplyLedger, type QqReplySource } from './passive-reply.js';
16
4
  import type { QqBotTransport } from './ws.js';
@@ -20,9 +8,14 @@ export interface QqSendContext {
20
8
  readonly activeGate: QqActiveMessageGate;
21
9
  readonly log: (fields: Record<string, string | number | undefined>) => void;
22
10
  }
11
+ export interface QqMessagesRequest {
12
+ readonly conversation: ConversationRef;
13
+ readonly messages: readonly QqOutboundMessage[];
14
+ readonly signal?: AbortSignal;
15
+ }
23
16
  /** The reply segment the payload already names, as a passive source. */
24
17
  export declare function explicitReplySource(message: QqOutboundMessage): QqReplySource | undefined;
25
18
  /** Plain-text version of a Markdown message (buttons dropped); undefined when it has no Markdown. */
26
19
  export declare function markdownAsText(message: QqOutboundMessage): QqOutboundMessage | undefined;
27
20
  /** Sends every message of one delivery in order; returns the last platform result. */
28
- export declare function sendQqMessages(context: QqSendContext, conversation: ConversationRef, messages: readonly QqOutboundMessage[]): Promise<unknown>;
21
+ export declare function sendQqMessages(context: QqSendContext, { conversation, messages, signal }: QqMessagesRequest): Promise<unknown>;
package/lib/qq-send.js CHANGED
@@ -1,3 +1,16 @@
1
+ /**
2
+ * One QQ outbound send: passive reply when a usable source exists, active message otherwise, with
3
+ * the fallbacks the platform's error codes allow. Shared by the websocket and webhook Endpoints.
4
+ *
5
+ * 1. Each message claims a passive source (explicit reply segment first, else the conversation's
6
+ * newest usable msg_id / event_id) with its own msg_seq.
7
+ * 2. Source unusable (expired, over the limit, invalid id): mark it and send that message once as
8
+ * an active message. Never after a timeout: the first request may have been delivered.
9
+ * 3. Markdown not allowed: resend the same message as plain text.
10
+ * 4. Active message refused (no permission, quota, user switched it off): remember the conversation
11
+ * for a while and fail fast instead of calling the platform again.
12
+ */
13
+ import { createEndpointOperation } from 'zhin.js/adapter';
1
14
  import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
15
  import { QQ_ACTIVE_UNAVAILABLE_CODES, QQ_MARKDOWN_DENIED_CODES, QQ_PASSIVE_SOURCE_CODES, } from './passive-reply.js';
3
16
  import { qqDeliveryFailure } from './delivery-error.js';
@@ -39,7 +52,7 @@ export function markdownAsText(message) {
39
52
  const kept = parts.filter((part) => typeof part !== 'string' && (part.type === 'reply' || part.type === 'at'));
40
53
  return text ? [...kept, text] : undefined;
41
54
  }
42
- function dispatch(bot, kind, id, message, source, options) {
55
+ function dispatch(bot, { kind, id, message, source, options }) {
43
56
  switch (kind) {
44
57
  case 'private': return bot.sendPrivateMessage(id, message, source, options);
45
58
  case 'group': return bot.sendGroupMessage(id, message, source, options);
@@ -54,14 +67,14 @@ function platformCode(error) {
54
67
  return qqDeliveryFailure(error).diagnostic.platformCode;
55
68
  }
56
69
  /** Sends one message, passive if possible; returns the platform result. */
57
- async function sendOne(context, kind, id, message) {
58
- const passive = (body, grant) => dispatch(context.bot, kind, id, body, grant.source, { msgSeq: grant.msgSeq });
70
+ async function sendOne(context, { kind, id, message }) {
71
+ const passive = (body, grant) => context.operation.run(() => dispatch(context.bot, { kind, id, message: body, source: grant.source, options: { msgSeq: grant.msgSeq } }));
59
72
  const active = async (body) => {
60
73
  if (context.activeGate.blocked(kind, id)) {
61
74
  throw new EndpointDeliveryError('platform_rejected', 'QQ active messages are unavailable for this conversation', 'not_sent');
62
75
  }
63
76
  try {
64
- return await dispatch(context.bot, kind, id, body);
77
+ return await context.operation.run(() => dispatch(context.bot, { kind, id, message: body }));
65
78
  }
66
79
  catch (error) {
67
80
  const code = platformCode(error);
@@ -100,10 +113,11 @@ async function sendOne(context, kind, id, message) {
100
113
  }
101
114
  }
102
115
  /** Sends every message of one delivery in order; returns the last platform result. */
103
- export async function sendQqMessages(context, conversation, messages) {
116
+ export async function sendQqMessages(context, { conversation, messages, signal }) {
104
117
  const kind = qqOutboundKind(conversation);
118
+ const execution = { ...context, operation: createEndpointOperation({ signal }) };
105
119
  let result;
106
120
  for (const message of messages)
107
- result = await sendOne(context, kind, conversation.id, message);
121
+ result = await sendOne(execution, { kind, id: conversation.id, message });
108
122
  return result;
109
123
  }
package/lib/webhook.d.ts CHANGED
@@ -1,11 +1,8 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { Bot, ReceiverMode } from 'qq-official-bot';
2
3
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
3
4
  import type { ResolvedQqHttpConfig } from './protocol.js';
4
- import type { QqBotTransport } from './ws.js';
5
- export type QqHttpBotTransport = QqBotTransport & {
6
- middleware: (ctx: unknown, next: () => Promise<void>) => Promise<unknown>;
7
- };
8
- export type CreateQqHttpBot = (config: ResolvedQqHttpConfig) => QqHttpBotTransport;
5
+ export type QqHttpBotTransport = Bot<ReceiverMode.MIDDLEWARE, 'koa'>;
9
6
  export interface QqWebhookHandler {
10
7
  readonly config: ResolvedQqHttpConfig;
11
8
  getBot(): (QqHttpBotTransport & {
@@ -15,4 +12,4 @@ export interface QqWebhookHandler {
15
12
  export declare function registerQqWebhookRoutes(http: HttpHost, handler: QqWebhookHandler): HttpRouteRegistration[];
16
13
  export declare function handleQqWebhookRequest(request: IncomingMessage, response: ServerResponse, handler: QqWebhookHandler): Promise<void>;
17
14
  export declare function readRequestBodyText(request: IncomingMessage): Promise<string>;
18
- export declare function defaultCreateHttpBot(config: ResolvedQqHttpConfig): QqHttpBotTransport;
15
+ export declare function qqHttpBotOptions(config: ResolvedQqHttpConfig): ConstructorParameters<typeof Bot<ReceiverMode.MIDDLEWARE, 'koa'>>[0];
package/lib/webhook.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * QQ HTTP webhook / middleware inbound via httpHostToken POST.
3
3
  */
4
4
  import path from 'node:path';
5
- import { Bot, ReceiverMode } from 'qq-official-bot';
5
+ import { ReceiverMode } from 'qq-official-bot';
6
6
  import { getLogger } from '@zhin.js/logger';
7
7
  const logger = getLogger('qq');
8
8
  export function registerQqWebhookRoutes(http, handler) {
@@ -28,6 +28,7 @@ export async function handleQqWebhookRequest(request, response, handler) {
28
28
  req: request,
29
29
  res: response,
30
30
  request: { body: undefined },
31
+ response: {},
31
32
  }, async () => undefined);
32
33
  }
33
34
  catch (error) {
@@ -52,52 +53,8 @@ export async function readRequestBodyText(request) {
52
53
  }
53
54
  return Buffer.concat(chunks).toString('utf8');
54
55
  }
55
- export function defaultCreateHttpBot(config) {
56
- const bot = new Bot({
57
- appid: config.appid,
58
- secret: config.secret,
59
- mode: ReceiverMode.MIDDLEWARE,
60
- platform: 'koa',
61
- sandbox: config.sandbox,
62
- dataDir: path.join(process.cwd(), 'data', 'qq'),
63
- // 关闭内置 log4js,与 WebSocket 模式一致走 zhin adapter logger
64
- logLevel: 'off',
65
- });
66
- return {
67
- api: bot,
68
- start: () => bot.start().then(() => undefined),
69
- stop: () => bot.stop().then(() => undefined),
70
- on: (event, listener) => {
71
- bot.on(event, listener);
72
- },
73
- removeAllListeners: () => {
74
- bot.removeAllListeners(undefined);
75
- },
76
- sendPrivateMessage: (userId, message, source, options) => bot.sendPrivateMessage(userId, message, source, options),
77
- sendGroupMessage: (groupId, message, source, options) => bot.sendGroupMessage(groupId, message, source, options),
78
- sendGuildMessage: (channelId, message, source, options) => bot.sendGuildMessage(channelId, message, source, options),
79
- sendDirectMessage: (guildId, message, source, options) => bot.sendDirectMessage(guildId, message, source, options),
80
- recallPrivateMessage: (userId, messageId) => bot.recallPrivateMessage(userId, messageId),
81
- recallGroupMessage: (groupId, messageId) => bot.recallGroupMessage(groupId, messageId),
82
- recallGuildMessage: (channelId, messageId) => bot.recallGuildMessage(channelId, messageId),
83
- recallDirectMessage: (guildId, messageId) => bot.recallDirectMessage(guildId, messageId),
84
- getGuilds: () => bot.guildService.getList(),
85
- getChannels: (guildId) => bot.channelService.getList(guildId),
86
- getChannelInfo: (channelId) => bot.channelService.getInfo(channelId),
87
- getGuildMember: (guildId, userId) => bot.memberService.getGuildMemberInfo(guildId, userId),
88
- getGuildRoles: (guildId) => bot.guildService.getRoles(guildId),
89
- createGuildRole: (guildId, name, color) => bot.guildService.createRole(guildId, { name, color: color || 0, hoist: 0 }),
90
- addMemberRole: async (guildId, channelId, userId, roleId) => {
91
- await bot.memberService.addMemberRole(guildId, channelId, userId, roleId);
92
- return true;
93
- },
94
- removeMemberRole: async (guildId, channelId, userId, roleId) => {
95
- await bot.memberService.removeMemberRole(guildId, channelId, userId, roleId);
96
- return true;
97
- },
98
- approveGroupJoinRequest: (groupId, userId, options) => bot
99
- .approveGroupJoinRequest?.(groupId, userId, options)
100
- ?? Promise.reject(new Error('approveGroupJoinRequest is not available')),
101
- middleware: (ctx, next) => bot.middleware(ctx, next),
102
- };
56
+ export function qqHttpBotOptions(config) {
57
+ return { handleProcessErrors: false, appid: config.appid, secret: config.secret,
58
+ mode: ReceiverMode.MIDDLEWARE, platform: 'koa', sandbox: config.sandbox,
59
+ dataDir: path.join(process.cwd(), 'data', 'qq'), logLevel: 'off' };
103
60
  }
package/lib/ws.d.ts CHANGED
@@ -1,7 +1,5 @@
1
- import { Bot, type ApproveJoinRequestOptions } from 'qq-official-bot';
1
+ import { Bot, ReceiverMode } from 'qq-official-bot';
2
2
  import type { EndpointTransportState } from 'zhin.js/adapter';
3
- import type { QqOutboundMessage } from './outbound.js';
4
- import type { QqReplySource } from './passive-reply.js';
5
3
  /** Per-send options. `msgSeq` numbers replies to one passive source (qq-official-bot ≥ 1.3.3). */
6
4
  export interface QqSendOptions {
7
5
  readonly msgSeq?: number;
@@ -9,35 +7,7 @@ export interface QqSendOptions {
9
7
  import type { QqInboundMessage, ResolvedQqWebsocketConfig } from './protocol.js';
10
8
  /** SDK business surface exposed to plugins without bypassing Endpoint lifecycle ownership. */
11
9
  export type QqOfficialApi = Pick<Bot, 'messageService' | 'fileProcessor' | 'guildService' | 'channelService' | 'memberService' | 'permissionService' | 'reactionService' | 'scheduleService' | 'threadService' | 'audioService' | 'botService' | 'groupService' | 'menuPanelService' | 'group' | 'user' | 'channel' | 'direct' | 'guild'>;
12
- /** Minimal bot surface used by the endpoint (real qq-official-bot or test mock). */
13
- export interface QqBotTransport {
14
- readonly transportState?: EndpointTransportState;
15
- /** Full qq-official-bot business API; lifecycle remains owned by the Endpoint. */
16
- readonly api: QqOfficialApi;
17
- start(): Promise<void>;
18
- stop(): Promise<void>;
19
- on(event: string, listener: (...args: unknown[]) => void): void;
20
- removeAllListeners(): void;
21
- /** `source` makes it a passive reply; without it the platform treats the message as active. */
22
- sendPrivateMessage(userId: string, message: QqOutboundMessage, source?: QqReplySource, options?: QqSendOptions): Promise<unknown>;
23
- sendGroupMessage(groupId: string, message: QqOutboundMessage, source?: QqReplySource, options?: QqSendOptions): Promise<unknown>;
24
- sendGuildMessage(channelId: string, message: QqOutboundMessage, source?: QqReplySource, options?: QqSendOptions): Promise<unknown>;
25
- sendDirectMessage?(guildId: string, message: QqOutboundMessage, source?: QqReplySource, options?: QqSendOptions): Promise<unknown>;
26
- recallPrivateMessage?(userId: string, messageId: string): Promise<unknown>;
27
- recallGroupMessage?(groupId: string, messageId: string): Promise<unknown>;
28
- recallGuildMessage?(channelId: string, messageId: string): Promise<unknown>;
29
- recallDirectMessage?(guildId: string, messageId: string): Promise<unknown>;
30
- getGuilds(): Promise<unknown[]>;
31
- getChannels(guildId: string): Promise<unknown[]>;
32
- getChannelInfo(channelId: string): Promise<unknown>;
33
- getGuildMember(guildId: string, userId: string): Promise<unknown>;
34
- getGuildRoles(guildId: string): Promise<unknown[]>;
35
- createGuildRole(guildId: string, name: string, color?: number): Promise<unknown>;
36
- addMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean>;
37
- removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean>;
38
- approveGroupJoinRequest?(groupId: string, userId: string, options: ApproveJoinRequestOptions): Promise<unknown>;
39
- }
40
- export type CreateQqBot = (config: ResolvedQqWebsocketConfig) => QqBotTransport;
10
+ export type QqBotTransport = Bot;
41
11
  interface QqOfficialBotLifecycle {
42
12
  stop(): Promise<unknown>;
43
13
  readonly sessionManager: {
@@ -59,5 +29,12 @@ interface QqOfficialBotLifecycle {
59
29
  export declare function stopQqOfficialBot(bot: QqOfficialBotLifecycle): Promise<void>;
60
30
  export declare function bindQqBotInboundEvents(bot: QqBotTransport, admitRaw: (raw: unknown) => void): void;
61
31
  export declare function normalizeQqMessage(raw: unknown): QqInboundMessage | null;
62
- export declare function defaultCreateBot(config: ResolvedQqWebsocketConfig): QqBotTransport;
32
+ export declare function qqWebsocketBotOptions(config: ResolvedQqWebsocketConfig): ConstructorParameters<typeof Bot<ReceiverMode.WEBSOCKET>>[0];
33
+ /** SDK 1.3.3 has no public receiver configuration/state accessor. This seam only reads its verified native fields. */
34
+ export declare function configureQqWebsocketReceiver(bot: Bot<ReceiverMode.WEBSOCKET>, config: ResolvedQqWebsocketConfig): {
35
+ state(): EndpointTransportState;
36
+ dispose(): void;
37
+ };
38
+ /** Session.stop leaves Auth refresh alive for reconnect; actual instance retirement destroys it. */
39
+ export declare function disposeQqAuthorization(bot: Bot): void;
63
40
  export {};
package/lib/ws.js CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import path from 'node:path';
5
5
  import { createQqStreamAgent } from './stream-proxy.js';
6
- import { Bot, ReceiverMode, } from 'qq-official-bot';
6
+ import { ReceiverMode, } from 'qq-official-bot';
7
7
  /**
8
8
  * qq-official-bot 1.2 used a separate connection manager; 1.3 moved WebSocket
9
9
  * ownership into the receiver, but Session.stop() still leaves the auth refresh
@@ -72,6 +72,8 @@ export function normalizeQqMessage(raw) {
72
72
  && msg.mentions.some((m) => m?.is_you === true || (m?.bot === true && channelKind !== 'group'));
73
73
  return {
74
74
  id: String(msg.message_id),
75
+ event: raw,
76
+ ...(typeof msg.timestamp === 'number' && Number.isFinite(msg.timestamp) ? { platformSentAt: msg.timestamp * 1_000 } : {}),
75
77
  content,
76
78
  channelKind,
77
79
  channelId,
@@ -183,87 +185,36 @@ function extractTextContent(message) {
183
185
  })
184
186
  .join('');
185
187
  }
186
- export function defaultCreateBot(config) {
187
- const bot = new Bot({
188
- ...{ handleProcessErrors: false },
189
- appid: config.appid,
190
- secret: config.secret,
191
- mode: ReceiverMode.WEBSOCKET,
192
- sandbox: config.sandbox,
193
- dataDir: path.join(process.cwd(), 'data', 'qq'),
194
- // 关闭 qq-official-bot 内置 log4js,入/出站由 getAdapterLogger('qq', …) 统一格式
195
- logLevel: 'off',
188
+ export function qqWebsocketBotOptions(config) {
189
+ return { handleProcessErrors: false, appid: config.appid, secret: config.secret,
190
+ mode: ReceiverMode.WEBSOCKET, sandbox: config.sandbox,
191
+ dataDir: path.join(process.cwd(), 'data', 'qq'), logLevel: 'off',
196
192
  ...(config.intents ? { intents: config.intents } : {}),
197
193
  ...(config.accessTokenUrl ? { accessTokenUrl: config.accessTokenUrl } : {}),
198
- ...(config.gatewayUrl ? { gatewayUrl: config.gatewayUrl } : {}),
199
- });
194
+ ...(config.gatewayUrl ? { gatewayUrl: config.gatewayUrl } : {}), };
195
+ }
196
+ /** SDK 1.3.3 has no public receiver configuration/state accessor. This seam only reads its verified native fields. */
197
+ export function configureQqWebsocketReceiver(bot, config) {
200
198
  const receiver = bot.receiver;
201
199
  receiver.config.autoReconnect = false;
202
200
  const agent = config.streamProxy ? createQqStreamAgent(config.streamProxy) : undefined;
203
201
  receiver.config.agent = agent;
204
- const receiverListeners = [];
205
- // Keep receiver error ownership after Session.start removes its temporary listener.
206
- const receiverError = () => { };
207
- receiver.on('error', receiverError);
202
+ const observeError = () => undefined;
203
+ bot.receiver.on('error', observeError);
208
204
  return {
209
- get transportState() {
210
- const receiver = bot.receiver;
205
+ state() {
211
206
  const state = receiver.handler?.ws?.readyState;
212
207
  if (state === 1 && receiver.isReady && receiver.isClosed === false)
213
208
  return 'open';
214
209
  if (state === 0)
215
210
  return 'connecting';
216
- if (receiver.isStarted)
217
- return 'reconnecting';
218
- return 'closed';
219
- },
220
- api: bot,
221
- start: () => bot.start().then(() => undefined),
222
- stop: async () => {
223
- try {
224
- await stopQqOfficialBot(bot);
225
- }
226
- finally {
227
- agent?.destroy();
228
- }
229
- },
230
- on: (event, listener) => {
231
- if (event === 'transport.close') {
232
- receiver.on('close', listener);
233
- receiverListeners.push({ event: 'close', listener });
234
- }
235
- else
236
- bot.on(event, listener);
211
+ return receiver.isStarted ? 'reconnecting' : 'closed';
237
212
  },
238
- removeAllListeners: () => {
239
- for (const entry of receiverListeners.splice(0))
240
- receiver.removeListener(entry.event, entry.listener);
241
- bot.removeAllListeners(undefined);
242
- },
243
- sendPrivateMessage: (userId, message, source, options) => bot.sendPrivateMessage(userId, message, source, options),
244
- sendGroupMessage: (groupId, message, source, options) => bot.sendGroupMessage(groupId, message, source, options),
245
- sendGuildMessage: (channelId, message, source, options) => bot.sendGuildMessage(channelId, message, source, options),
246
- sendDirectMessage: (guildId, message, source, options) => bot.sendDirectMessage(guildId, message, source, options),
247
- recallPrivateMessage: (userId, messageId) => bot.recallPrivateMessage(userId, messageId),
248
- recallGroupMessage: (groupId, messageId) => bot.recallGroupMessage(groupId, messageId),
249
- recallGuildMessage: (channelId, messageId) => bot.recallGuildMessage(channelId, messageId),
250
- recallDirectMessage: (guildId, messageId) => bot.recallDirectMessage(guildId, messageId),
251
- getGuilds: () => bot.guildService.getList(),
252
- getChannels: (guildId) => bot.channelService.getList(guildId),
253
- getChannelInfo: (channelId) => bot.channelService.getInfo(channelId),
254
- getGuildMember: (guildId, userId) => bot.memberService.getGuildMemberInfo(guildId, userId),
255
- getGuildRoles: (guildId) => bot.guildService.getRoles(guildId),
256
- createGuildRole: (guildId, name, color) => bot.guildService.createRole(guildId, { name, color: color || 0, hoist: 0 }),
257
- addMemberRole: async (guildId, channelId, userId, roleId) => {
258
- await bot.memberService.addMemberRole(guildId, channelId, userId, roleId);
259
- return true;
260
- },
261
- removeMemberRole: async (guildId, channelId, userId, roleId) => {
262
- await bot.memberService.removeMemberRole(guildId, channelId, userId, roleId);
263
- return true;
264
- },
265
- approveGroupJoinRequest: (groupId, userId, options) => bot
266
- .approveGroupJoinRequest?.(groupId, userId, options)
267
- ?? Promise.reject(new Error('approveGroupJoinRequest is not available')),
213
+ dispose() { bot.receiver.off('error', observeError); agent?.destroy(); },
268
214
  };
269
215
  }
216
+ /** Session.stop leaves Auth refresh alive for reconnect; actual instance retirement destroys it. */
217
+ export function disposeQqAuthorization(bot) {
218
+ const session = bot.sessionManager;
219
+ session.authManager.destroy();
220
+ }
@@ -1,7 +1,6 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
2
  import { messages as t } from "../../lib/i18n.js";
3
3
  import { defineMiddleware } from 'zhin.js/middleware';
4
- import { endpointConfigurationStoreToken } from 'zhin.js/adapter';
5
4
  import { completeQqPendingBotKind, } from '../../commands/qq/endpoint/definition.js';
6
5
  import { parseQqBotKindAnswer, qqCommandSessionKey, qqBotKindPrompt, } from "../../lib/qq-bot-kind-prompt.js";
7
6
  import { qqRuntimeStateToken } from "../../lib/qq-runtime-state.js";
@@ -37,7 +36,7 @@ export default defineMiddleware({
37
36
  return;
38
37
  }
39
38
  try {
40
- const text = await completeQqPendingBotKind(pending, botKind, context.use(endpointConfigurationStoreToken));
39
+ const text = await completeQqPendingBotKind(pending, botKind);
41
40
  state.pendingBotKind = null;
42
41
  await context.input.$reply?.(text);
43
42
  }
@@ -1,7 +1,6 @@
1
1
  import { messages as t } from '../../src/i18n.js';
2
2
  import { defineMiddleware } from 'zhin.js/middleware';
3
3
  import type { CommandMessage } from 'zhin.js/command';
4
- import { endpointConfigurationStoreToken } from 'zhin.js/adapter';
5
4
  import {
6
5
  completeQqPendingBotKind,
7
6
  } from '../../commands/qq/endpoint/definition.js';
@@ -53,7 +52,6 @@ export default defineMiddleware<CommandMessage>({
53
52
  const text = await completeQqPendingBotKind(
54
53
  pending,
55
54
  botKind,
56
- context.use(endpointConfigurationStoreToken),
57
55
  );
58
56
  state.pendingBotKind = null;
59
57
  await context.input.$reply?.(text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-qq",
3
- "version": "1.1.17",
3
+ "version": "1.1.19",
4
4
  "description": "Zhin.js QQ Official adapter for Plugin Runtime (WebSocket gateway)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -34,24 +34,27 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "qq-official-bot": "^1.3.3",
37
- "@zhin.js/adapter": "1.1.24",
38
- "@zhin.js/agent-feature": "1.1.10",
39
- "@zhin.js/core": "1.1.52",
40
- "@zhin.js/feature-kit": "1.1.9",
41
- "@zhin.js/host-http": "1.1.11",
42
- "@zhin.js/im-contract": "1.1.8",
37
+ "@zhin.js/adapter": "1.1.26",
38
+ "@zhin.js/agent-feature": "1.1.11",
39
+ "@zhin.js/client": "1.1.22",
40
+ "@zhin.js/console-contract": "1.1.3",
41
+ "@zhin.js/core": "1.1.54",
42
+ "@zhin.js/feature-kit": "1.1.10",
43
+ "@zhin.js/host-http": "1.1.12",
44
+ "@zhin.js/im-contract": "1.1.9",
43
45
  "@zhin.js/logger": "1.1.2",
44
- "@zhin.js/permission": "1.1.9",
45
- "@zhin.js/skill": "1.1.10",
46
- "@zhin.js/tool": "1.1.11"
46
+ "@zhin.js/page": "1.1.11",
47
+ "@zhin.js/permission": "1.1.10",
48
+ "@zhin.js/skill": "1.1.11",
49
+ "@zhin.js/tool": "1.1.13"
47
50
  },
48
51
  "peerDependencies": {
49
52
  "zod": "^4.0.0",
50
- "@zhin.js/adapter": "^1.1.24",
51
- "@zhin.js/command": "^1.1.11",
52
- "@zhin.js/core": "^1.1.52",
53
- "@zhin.js/middleware": "^1.1.10",
54
- "zhin.js": "^1.1.17"
53
+ "@zhin.js/adapter": "^1.1.26",
54
+ "@zhin.js/command": "^1.1.12",
55
+ "@zhin.js/core": "^1.1.54",
56
+ "@zhin.js/middleware": "^1.1.11",
57
+ "zhin.js": "^1.1.19"
55
58
  },
56
59
  "peerDependenciesMeta": {
57
60
  "@zhin.js/command": {
@@ -72,13 +75,14 @@
72
75
  "typescript": "^6.0.3",
73
76
  "vitest": "^4.1.10",
74
77
  "zod": "^4.4.3",
75
- "@zhin.js/host-http": "1.1.11",
76
- "zhin.js": "1.1.17"
78
+ "@zhin.js/host-http": "1.1.12",
79
+ "zhin.js": "1.1.19"
77
80
  },
78
81
  "files": [
79
82
  "adapters",
80
83
  "commands",
81
84
  "middlewares",
85
+ "pages",
82
86
  "plugin.js",
83
87
  "schema.json",
84
88
  "src",
@@ -105,7 +109,7 @@
105
109
  "features": [
106
110
  {
107
111
  "package": "@zhin.js/adapter",
108
- "api": "^1.0.0"
112
+ "api": "^2.0.0"
109
113
  },
110
114
  {
111
115
  "package": "@zhin.js/command",
@@ -126,6 +130,10 @@
126
130
  {
127
131
  "package": "@zhin.js/agent-feature",
128
132
  "api": "^1.0.0"
133
+ },
134
+ {
135
+ "package": "@zhin.js/page",
136
+ "api": "^1.0.0"
129
137
  }
130
138
  ],
131
139
  "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: 'QQ',
6
+ titleI18n: { en: 'QQ' },
7
+ icon: 'MessageCircle',
8
+ order: 60,
9
+ requiredPermissions: ['qq:read'],
10
+ });
11
+ /**
12
+ * Convention page: `pages/index/index.ts` → `/qq`(插件路径)。
13
+ * 连接状态、测试、endpoint 增删、扫码绑定(原本 `qq endpoint add` 的聊天流程)。
14
+ */
15
+ export default createAdapterConsolePage('qq');
@@ -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: 'QQ',
6
+ titleI18n: { en: 'QQ' },
7
+ icon: 'MessageCircle',
8
+ order: 60,
9
+ requiredPermissions: ['qq:read'],
10
+ });
11
+
12
+ /**
13
+ * Convention page: `pages/index/index.ts` → `/qq`(插件路径)。
14
+ * 连接状态、测试、endpoint 增删、扫码绑定(原本 `qq endpoint add` 的聊天流程)。
15
+ */
16
+ export default createAdapterConsolePage('qq');
package/plugin.js CHANGED
@@ -1,18 +1,21 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { definePlugin } from 'zhin.js';
2
+ import { definePlugin, pluginConsoleRpcToken } from 'zhin.js';
3
3
  import { permissionHostToken } from '@zhin.js/permission';
4
4
  import { checkQqPlatformPermit } from "./lib/platform-permit.js";
5
5
  import { createQqRuntimeState, qqRuntimeStateToken } from "./lib/qq-runtime-state.js";
6
+ import { createQqConsoleRpc } from "./lib/console.js";
7
+ import { cancelQqConsoleBind } from "./lib/qq-console.js";
6
8
  export default definePlugin({
7
9
  name: 'qq',
8
10
  metadata: {
9
11
  displayName: 'QQ Official WebSocket Adapter',
10
12
  },
11
13
  setup(context) {
12
- context.resources.provide(qqRuntimeStateToken, createQqRuntimeState());
13
- if (context.resources.has(permissionHostToken)) {
14
- const host = context.resources.use(permissionHostToken);
15
- return host.registerPlatform('qq', checkQqPlatformPermit);
16
- }
14
+ const state = createQqRuntimeState();
15
+ context.resources.provide(qqRuntimeStateToken, state);
16
+ context.resources.provide(pluginConsoleRpcToken, createQqConsoleRpc(state, () => context.config.get()));
17
+ const releasePermission = context.resources.has(permissionHostToken)
18
+ ? context.resources.use(permissionHostToken).registerPlatform('qq', checkQqPlatformPermit) : undefined;
19
+ return () => { cancelQqConsoleBind(state); releasePermission?.(); };
17
20
  },
18
21
  });