@zhin.js/adapter-qq 6.0.0 → 6.0.1

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.
package/src/webhook.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import path from 'node:path';
5
5
  import type { IncomingMessage, ServerResponse } from 'node:http';
6
- import { Bot, ReceiverMode } from 'qq-official-bot';
6
+ import { Bot, ReceiverMode, type Sendable } from 'qq-official-bot';
7
7
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
8
8
  import { formatCompact, getLogger } from '@zhin.js/logger';
9
9
  import type { ResolvedQqHttpConfig } from './protocol.js';
@@ -46,19 +46,13 @@ export async function handleQqWebhookRequest(
46
46
  return;
47
47
  }
48
48
  try {
49
- let parsedBody: unknown;
50
- const raw = await readRequestBodyText(request);
51
- if (raw) {
52
- try {
53
- parsedBody = JSON.parse(raw);
54
- } catch {
55
- parsedBody = undefined;
56
- }
57
- }
49
+ // 不预消费请求流:库的验签必须基于原始字节(JSON.parse → JSON.stringify
50
+ // 会改变空白/转义/键序导致 401),body 留 undefined 让库自己读流;
51
+ // 预消费还会让非 JSON body 时库监听已结束的 end 事件而挂死。
58
52
  await bot.middleware({
59
53
  req: request,
60
54
  res: response,
61
- request: { body: parsedBody },
55
+ request: { body: undefined },
62
56
  }, async () => undefined);
63
57
  } catch (error) {
64
58
  logger.error('QQ webhook error:', error);
@@ -103,10 +97,10 @@ export function defaultCreateHttpBot(config: ResolvedQqHttpConfig): QqHttpBotTra
103
97
  removeAllListeners: () => {
104
98
  bot.removeAllListeners(undefined as never);
105
99
  },
106
- sendPrivateMessage: (userId, message) => bot.sendPrivateMessage(userId, message),
107
- sendGroupMessage: (groupId, message) => bot.sendGroupMessage(groupId, message),
108
- sendGuildMessage: (channelId, message) => bot.sendGuildMessage(channelId, message),
109
- sendDirectMessage: (guildId, message) => bot.sendDirectMessage(guildId, message),
100
+ sendPrivateMessage: (userId, message) => bot.sendPrivateMessage(userId, message as Sendable),
101
+ sendGroupMessage: (groupId, message) => bot.sendGroupMessage(groupId, message as Sendable),
102
+ sendGuildMessage: (channelId, message) => bot.sendGuildMessage(channelId, message as Sendable),
103
+ sendDirectMessage: (guildId, message) => bot.sendDirectMessage(guildId, message as Sendable),
110
104
  getGuilds: () => bot.guildService.getList(),
111
105
  getChannels: (guildId) => bot.channelService.getList(guildId),
112
106
  getChannelInfo: (channelId) => bot.channelService.getInfo(channelId),
package/src/ws.ts CHANGED
@@ -2,8 +2,9 @@
2
2
  * QQ WebSocket transport: bot factory and inbound message normalization.
3
3
  */
4
4
  import path from 'node:path';
5
- import { Bot, ReceiverMode } from 'qq-official-bot';
5
+ import { Bot, ReceiverMode, type Sendable } from 'qq-official-bot';
6
6
  import { getLevel, toLog4jsLevel } from '@zhin.js/logger';
7
+ import type { QqOutboundMessage } from './outbound.js';
7
8
  import type { QqChannelKind, QqInboundMessage, ResolvedQqWebsocketConfig } from './protocol.js';
8
9
 
9
10
  /** Minimal bot surface used by the endpoint (real qq-official-bot or test mock). */
@@ -12,10 +13,10 @@ export interface QqBotTransport {
12
13
  stop(): Promise<void>;
13
14
  on(event: string, listener: (...args: unknown[]) => void): void;
14
15
  removeAllListeners(): void;
15
- sendPrivateMessage(userId: string, message: string): Promise<unknown>;
16
- sendGroupMessage(groupId: string, message: string): Promise<unknown>;
17
- sendGuildMessage(channelId: string, message: string): Promise<unknown>;
18
- sendDirectMessage?(guildId: string, message: string): Promise<unknown>;
16
+ sendPrivateMessage(userId: string, message: QqOutboundMessage): Promise<unknown>;
17
+ sendGroupMessage(groupId: string, message: QqOutboundMessage): Promise<unknown>;
18
+ sendGuildMessage(channelId: string, message: QqOutboundMessage): Promise<unknown>;
19
+ sendDirectMessage?(guildId: string, message: QqOutboundMessage): Promise<unknown>;
19
20
  getGuilds(): Promise<unknown[]>;
20
21
  getChannels(guildId: string): Promise<unknown[]>;
21
22
  getChannelInfo(channelId: string): Promise<unknown>;
@@ -38,6 +39,32 @@ export interface QqBotTransport {
38
39
 
39
40
  export type CreateQqBot = (config: ResolvedQqWebsocketConfig) => QqBotTransport;
40
41
 
42
+ interface QqOfficialBotLifecycle {
43
+ stop(): Promise<unknown>;
44
+ readonly sessionManager: {
45
+ userClose: boolean;
46
+ readonly connectionManager?: { destroy(): void };
47
+ readonly authManager?: { destroy(): void };
48
+ };
49
+ }
50
+
51
+ /**
52
+ * qq-official-bot 1.2.x keeps a second `userClose` flag in Connection.
53
+ * Session.stop() does not update it, so a normal shutdown is treated as an
54
+ * unexpected disconnect and starts a reconnect loop. Destroy both managers at
55
+ * the SDK boundary until upstream exposes a complete public shutdown method.
56
+ */
57
+ export async function stopQqOfficialBot(bot: QqOfficialBotLifecycle): Promise<void> {
58
+ const session = bot.sessionManager;
59
+ session.userClose = true;
60
+ session.connectionManager?.destroy();
61
+ try {
62
+ await bot.stop();
63
+ } finally {
64
+ session.authManager?.destroy();
65
+ }
66
+ }
67
+
41
68
  export function bindQqBotInboundEvents(
42
69
  bot: QqBotTransport,
43
70
  admitRaw: (raw: unknown) => void,
@@ -142,17 +169,17 @@ export function defaultCreateBot(config: ResolvedQqWebsocketConfig): QqBotTransp
142
169
 
143
170
  return {
144
171
  start: () => bot.start().then(() => undefined),
145
- stop: () => bot.stop().then(() => undefined),
172
+ stop: () => stopQqOfficialBot(bot as unknown as QqOfficialBotLifecycle),
146
173
  on: (event, listener) => {
147
174
  bot.on(event as never, listener as never);
148
175
  },
149
176
  removeAllListeners: () => {
150
177
  bot.removeAllListeners(undefined as never);
151
178
  },
152
- sendPrivateMessage: (userId, message) => bot.sendPrivateMessage(userId, message),
153
- sendGroupMessage: (groupId, message) => bot.sendGroupMessage(groupId, message),
154
- sendGuildMessage: (channelId, message) => bot.sendGuildMessage(channelId, message),
155
- sendDirectMessage: (guildId, message) => bot.sendDirectMessage(guildId, message),
179
+ sendPrivateMessage: (userId, message) => bot.sendPrivateMessage(userId, message as Sendable),
180
+ sendGroupMessage: (groupId, message) => bot.sendGroupMessage(groupId, message as Sendable),
181
+ sendGuildMessage: (channelId, message) => bot.sendGuildMessage(channelId, message as Sendable),
182
+ sendDirectMessage: (guildId, message) => bot.sendDirectMessage(guildId, message as Sendable),
156
183
  getGuilds: () => bot.guildService.getList(),
157
184
  getChannels: (guildId) => bot.channelService.getList(guildId),
158
185
  getChannelInfo: (channelId) => bot.channelService.getInfo(channelId),