@zhin.js/adapter-qq 6.0.5 → 6.0.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @zhin.js/adapter-qq
2
2
 
3
+ ## 6.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - d52f3c5: fix: QQ 适配器群聊误识别为私聊、game-kit 启动崩溃、全面补齐 recallMessage
8
+
9
+ - fix(cli): `resolveChannelType` 增加 `metadata.channelKind` 检查,修复 QQ/Discord/KOOK 群聊消息被误分类为私聊
10
+ - fix(game-kit): `createHostGameDb` 改用延迟代理模型,避免数据库启动前解析模型导致崩溃
11
+ - feat(qq): 实现 `recallMessage`,解析复合 messageId 路由到对应 SDK 撤回方法;`normalizeQqMessage` 增加 `message_type` 缺失时的回退检测
12
+ - feat(slack): 实现 `recallMessage`,利用已有 compound ref 和 `chat.delete` API
13
+ - feat(onebot11): 两个 endpoint 类实现 `recallMessage`(`delete_msg`)
14
+ - feat(onebot12): 两个 endpoint 类实现 `recallMessage`(`delete_message`)
15
+ - feat(napcat): 三个 endpoint 类实现 `recallMessage`(`delete_msg`)
16
+ - feat(discord): 两个 endpoint 类实现 `recallMessage`;`send()` 改为返回 `channelId:snowflake` 复合 ID
17
+ - feat(telegram): 实现 `recallMessage`(`deleteMessage`);`send()` 改为返回 `chatId:messageId` 复合 ID
18
+ - feat(kook): 两个 endpoint 类实现 `recallMessage`,利用 kook-client `recallMsg` API
19
+ - feat(lark): 实现 `recallMessage`(`DELETE /im/v1/messages/{id}`)
20
+ - feat(wecom): 实现 `recallMessage`(`POST /cgi-bin/message/recall`)
21
+
3
22
  ## 6.0.5
4
23
 
5
24
  ### Patch Changes
package/lib/endpoint.d.ts CHANGED
@@ -30,6 +30,7 @@ export declare class QqWebsocketEndpoint implements EndpointInstance {
30
30
  readonly target: string;
31
31
  readonly payload: unknown;
32
32
  }): Promise<string>;
33
+ recallMessage(messageId: string): Promise<void>;
33
34
  /** Test / internal: admit a message when open. */
34
35
  admit(msg: QqInboundMessage): void;
35
36
  getGuilds(): Promise<unknown[]>;
@@ -63,6 +64,7 @@ export declare class QqHttpEndpoint implements EndpointInstance {
63
64
  readonly target: string;
64
65
  readonly payload: unknown;
65
66
  }): Promise<string>;
67
+ recallMessage(messageId: string): Promise<void>;
66
68
  admit(msg: QqInboundMessage): void;
67
69
  getGuilds(): Promise<unknown[]>;
68
70
  getChannels(guildId: string): Promise<unknown[]>;
package/lib/endpoint.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { formatCompact, getLogger } from '@zhin.js/logger';
2
2
  import { formatOutbound } from './outbound.js';
3
3
  import { registerQqAgentEndpoint } from './qq-agent-deps.js';
4
- import { formatInboundContent, formatInboundTarget, parseSendTarget, resolveOutboundMessageId, senderDisplayName, } from './protocol.js';
4
+ import { formatInboundContent, formatInboundTarget, parseCompoundMessageId, parseSendTarget, resolveOutboundMessageId, senderDisplayName, } from './protocol.js';
5
5
  import { defaultCreateHttpBot, registerQqWebhookRoutes, } from './webhook.js';
6
6
  import { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
7
7
  const logger = getLogger('qq');
@@ -99,6 +99,30 @@ export class QqWebsocketEndpoint {
99
99
  }));
100
100
  return messageId;
101
101
  }
102
+ async recallMessage(messageId) {
103
+ const { kind, channelId, qqMsgId } = parseCompoundMessageId(messageId);
104
+ const bot = this.#requireBot();
105
+ switch (kind) {
106
+ case 'private':
107
+ await bot.recallPrivateMessage?.(channelId, qqMsgId);
108
+ break;
109
+ case 'group':
110
+ await bot.recallGroupMessage?.(channelId, qqMsgId);
111
+ break;
112
+ case 'channel':
113
+ await bot.recallGuildMessage?.(channelId, qqMsgId);
114
+ break;
115
+ case 'direct':
116
+ await bot.recallDirectMessage?.(channelId, qqMsgId);
117
+ break;
118
+ }
119
+ logger.debug(formatCompact({
120
+ op: 'qq_recall',
121
+ endpoint: this.#options.config.name,
122
+ kind,
123
+ messageId,
124
+ }));
125
+ }
102
126
  /** Test / internal: admit a message when open. */
103
127
  admit(msg) {
104
128
  if (!this.#open)
@@ -255,6 +279,30 @@ export class QqHttpEndpoint {
255
279
  }
256
280
  return `${parsed.kind}-${parsed.id}:${resolveOutboundMessageId(result)}`;
257
281
  }
282
+ async recallMessage(messageId) {
283
+ const { kind, channelId, qqMsgId } = parseCompoundMessageId(messageId);
284
+ const bot = this.#requireBot();
285
+ switch (kind) {
286
+ case 'private':
287
+ await bot.recallPrivateMessage?.(channelId, qqMsgId);
288
+ break;
289
+ case 'group':
290
+ await bot.recallGroupMessage?.(channelId, qqMsgId);
291
+ break;
292
+ case 'channel':
293
+ await bot.recallGuildMessage?.(channelId, qqMsgId);
294
+ break;
295
+ case 'direct':
296
+ await bot.recallDirectMessage?.(channelId, qqMsgId);
297
+ break;
298
+ }
299
+ logger.debug(formatCompact({
300
+ op: 'qq_recall',
301
+ endpoint: this.#options.config.name,
302
+ kind,
303
+ messageId,
304
+ }));
305
+ }
258
306
  admit(msg) {
259
307
  if (!this.#open)
260
308
  return;
package/lib/protocol.d.ts CHANGED
@@ -92,5 +92,13 @@ export declare function formatInboundContent(msg: QqInboundMessage): string;
92
92
  * Segment canonicalization is intentionally not done here.
93
93
  */
94
94
  export declare function formatOutboundText(payload: unknown): string;
95
+ /**
96
+ * Parse a compound messageId produced by `send()`: `kind-channelId:qqMsgId`.
97
+ */
98
+ export declare function parseCompoundMessageId(messageId: string): {
99
+ kind: QqChannelKind;
100
+ channelId: string;
101
+ qqMsgId: string;
102
+ };
95
103
  /** 从 QQ API SendResult / 审核回包中解析出站消息 ID */
96
104
  export declare function resolveOutboundMessageId(result: unknown): string;
package/lib/protocol.js CHANGED
@@ -120,6 +120,23 @@ export function formatOutboundText(payload) {
120
120
  .filter(Boolean)
121
121
  .join('');
122
122
  }
123
+ /**
124
+ * Parse a compound messageId produced by `send()`: `kind-channelId:qqMsgId`.
125
+ */
126
+ export function parseCompoundMessageId(messageId) {
127
+ const hyphen = messageId.indexOf('-');
128
+ if (hyphen <= 0)
129
+ throw new Error(`invalid QQ messageId format: ${messageId}`);
130
+ const kind = messageId.slice(0, hyphen);
131
+ if (kind !== 'private' && kind !== 'group' && kind !== 'channel' && kind !== 'direct') {
132
+ throw new Error(`invalid QQ messageId kind: ${kind}`);
133
+ }
134
+ const rest = messageId.slice(hyphen + 1);
135
+ const colon = rest.indexOf(':');
136
+ if (colon <= 0)
137
+ throw new Error(`invalid QQ messageId format: ${messageId}`);
138
+ return { kind, channelId: rest.slice(0, colon), qqMsgId: rest.slice(colon + 1) };
139
+ }
123
140
  /** 从 QQ API SendResult / 审核回包中解析出站消息 ID */
124
141
  export function resolveOutboundMessageId(result) {
125
142
  if (!result || typeof result !== 'object') {
package/lib/webhook.js CHANGED
@@ -74,6 +74,10 @@ export function defaultCreateHttpBot(config) {
74
74
  sendGroupMessage: (groupId, message) => bot.sendGroupMessage(groupId, message),
75
75
  sendGuildMessage: (channelId, message) => bot.sendGuildMessage(channelId, message),
76
76
  sendDirectMessage: (guildId, message) => bot.sendDirectMessage(guildId, message),
77
+ recallPrivateMessage: (userId, messageId) => bot.recallPrivateMessage(userId, messageId),
78
+ recallGroupMessage: (groupId, messageId) => bot.recallGroupMessage(groupId, messageId),
79
+ recallGuildMessage: (channelId, messageId) => bot.recallGuildMessage(channelId, messageId),
80
+ recallDirectMessage: (guildId, messageId) => bot.recallDirectMessage(guildId, messageId),
77
81
  getGuilds: () => bot.guildService.getList(),
78
82
  getChannels: (guildId) => bot.channelService.getList(guildId),
79
83
  getChannelInfo: (channelId) => bot.channelService.getInfo(channelId),
package/lib/ws.d.ts CHANGED
@@ -10,6 +10,10 @@ export interface QqBotTransport {
10
10
  sendGroupMessage(groupId: string, message: QqOutboundMessage): Promise<unknown>;
11
11
  sendGuildMessage(channelId: string, message: QqOutboundMessage): Promise<unknown>;
12
12
  sendDirectMessage?(guildId: string, message: QqOutboundMessage): Promise<unknown>;
13
+ recallPrivateMessage?(userId: string, messageId: string): Promise<unknown>;
14
+ recallGroupMessage?(groupId: string, messageId: string): Promise<unknown>;
15
+ recallGuildMessage?(channelId: string, messageId: string): Promise<unknown>;
16
+ recallDirectMessage?(guildId: string, messageId: string): Promise<unknown>;
13
17
  getGuilds(): Promise<unknown[]>;
14
18
  getChannels(guildId: string): Promise<unknown[]>;
15
19
  getChannelInfo(channelId: string): Promise<unknown>;
package/lib/ws.js CHANGED
@@ -34,13 +34,17 @@ export function normalizeQqMessage(raw) {
34
34
  return null;
35
35
  let channelKind = 'private';
36
36
  let channelId = '';
37
- if (msg.message_type === 'guild') {
37
+ const isGroup = msg.message_type === 'group'
38
+ || (msg.message_type == null && (msg.group_id != null || msg.group_openid != null));
39
+ const isGuild = msg.message_type === 'guild'
40
+ || (msg.message_type == null && !isGroup && msg.guild_id != null && msg.channel_id != null);
41
+ if (isGuild) {
38
42
  channelKind = msg.sub_type === 'direct' ? 'direct' : 'channel';
39
43
  channelId = channelKind === 'direct'
40
44
  ? String(msg.guild_id ?? '')
41
45
  : String(msg.channel_id ?? '');
42
46
  }
43
- else if (msg.message_type === 'group') {
47
+ else if (isGroup) {
44
48
  channelKind = 'group';
45
49
  channelId = String(msg.group_id ?? msg.group_openid ?? '');
46
50
  }
@@ -117,6 +121,10 @@ export function defaultCreateBot(config) {
117
121
  sendGroupMessage: (groupId, message) => bot.sendGroupMessage(groupId, message),
118
122
  sendGuildMessage: (channelId, message) => bot.sendGuildMessage(channelId, message),
119
123
  sendDirectMessage: (guildId, message) => bot.sendDirectMessage(guildId, message),
124
+ recallPrivateMessage: (userId, messageId) => bot.recallPrivateMessage(userId, messageId),
125
+ recallGroupMessage: (groupId, messageId) => bot.recallGroupMessage(groupId, messageId),
126
+ recallGuildMessage: (channelId, messageId) => bot.recallGuildMessage(channelId, messageId),
127
+ recallDirectMessage: (guildId, messageId) => bot.recallDirectMessage(guildId, messageId),
120
128
  getGuilds: () => bot.guildService.getList(),
121
129
  getChannels: (guildId) => bot.channelService.getList(guildId),
122
130
  getChannelInfo: (channelId) => bot.channelService.getInfo(channelId),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-qq",
3
- "version": "6.0.5",
3
+ "version": "6.0.6",
4
4
  "description": "Zhin.js QQ Official adapter for Plugin Runtime (WebSocket gateway)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
package/src/endpoint.ts CHANGED
@@ -15,6 +15,7 @@ import { registerQqAgentEndpoint } from './qq-agent-deps.js';
15
15
  import {
16
16
  formatInboundContent,
17
17
  formatInboundTarget,
18
+ parseCompoundMessageId,
18
19
  parseSendTarget,
19
20
  resolveOutboundMessageId,
20
21
  senderDisplayName,
@@ -149,6 +150,31 @@ export class QqWebsocketEndpoint implements EndpointInstance {
149
150
  return messageId;
150
151
  }
151
152
 
153
+ async recallMessage(messageId: string): Promise<void> {
154
+ const { kind, channelId, qqMsgId } = parseCompoundMessageId(messageId);
155
+ const bot = this.#requireBot();
156
+ switch (kind) {
157
+ case 'private':
158
+ await bot.recallPrivateMessage?.(channelId, qqMsgId);
159
+ break;
160
+ case 'group':
161
+ await bot.recallGroupMessage?.(channelId, qqMsgId);
162
+ break;
163
+ case 'channel':
164
+ await bot.recallGuildMessage?.(channelId, qqMsgId);
165
+ break;
166
+ case 'direct':
167
+ await bot.recallDirectMessage?.(channelId, qqMsgId);
168
+ break;
169
+ }
170
+ logger.debug(formatCompact({
171
+ op: 'qq_recall',
172
+ endpoint: this.#options.config.name,
173
+ kind,
174
+ messageId,
175
+ }));
176
+ }
177
+
152
178
  /** Test / internal: admit a message when open. */
153
179
  admit(msg: QqInboundMessage): void {
154
180
  if (!this.#open) return;
@@ -329,6 +355,31 @@ export class QqHttpEndpoint implements EndpointInstance {
329
355
  return `${parsed.kind}-${parsed.id}:${resolveOutboundMessageId(result)}`;
330
356
  }
331
357
 
358
+ async recallMessage(messageId: string): Promise<void> {
359
+ const { kind, channelId, qqMsgId } = parseCompoundMessageId(messageId);
360
+ const bot = this.#requireBot();
361
+ switch (kind) {
362
+ case 'private':
363
+ await bot.recallPrivateMessage?.(channelId, qqMsgId);
364
+ break;
365
+ case 'group':
366
+ await bot.recallGroupMessage?.(channelId, qqMsgId);
367
+ break;
368
+ case 'channel':
369
+ await bot.recallGuildMessage?.(channelId, qqMsgId);
370
+ break;
371
+ case 'direct':
372
+ await bot.recallDirectMessage?.(channelId, qqMsgId);
373
+ break;
374
+ }
375
+ logger.debug(formatCompact({
376
+ op: 'qq_recall',
377
+ endpoint: this.#options.config.name,
378
+ kind,
379
+ messageId,
380
+ }));
381
+ }
382
+
332
383
  admit(msg: QqInboundMessage): void {
333
384
  if (!this.#open) return;
334
385
  const target = formatInboundTarget(msg);
package/src/protocol.ts CHANGED
@@ -219,6 +219,26 @@ export function formatOutboundText(payload: unknown): string {
219
219
  .join('');
220
220
  }
221
221
 
222
+ /**
223
+ * Parse a compound messageId produced by `send()`: `kind-channelId:qqMsgId`.
224
+ */
225
+ export function parseCompoundMessageId(messageId: string): {
226
+ kind: QqChannelKind;
227
+ channelId: string;
228
+ qqMsgId: string;
229
+ } {
230
+ const hyphen = messageId.indexOf('-');
231
+ if (hyphen <= 0) throw new Error(`invalid QQ messageId format: ${messageId}`);
232
+ const kind = messageId.slice(0, hyphen);
233
+ if (kind !== 'private' && kind !== 'group' && kind !== 'channel' && kind !== 'direct') {
234
+ throw new Error(`invalid QQ messageId kind: ${kind}`);
235
+ }
236
+ const rest = messageId.slice(hyphen + 1);
237
+ const colon = rest.indexOf(':');
238
+ if (colon <= 0) throw new Error(`invalid QQ messageId format: ${messageId}`);
239
+ return { kind, channelId: rest.slice(0, colon), qqMsgId: rest.slice(colon + 1) };
240
+ }
241
+
222
242
  /** 从 QQ API SendResult / 审核回包中解析出站消息 ID */
223
243
  export function resolveOutboundMessageId(result: unknown): string {
224
244
  if (!result || typeof result !== 'object') {
package/src/webhook.ts CHANGED
@@ -101,6 +101,10 @@ export function defaultCreateHttpBot(config: ResolvedQqHttpConfig): QqHttpBotTra
101
101
  sendGroupMessage: (groupId, message) => bot.sendGroupMessage(groupId, message as Sendable),
102
102
  sendGuildMessage: (channelId, message) => bot.sendGuildMessage(channelId, message as Sendable),
103
103
  sendDirectMessage: (guildId, message) => bot.sendDirectMessage(guildId, message as Sendable),
104
+ recallPrivateMessage: (userId, messageId) => bot.recallPrivateMessage(userId, messageId),
105
+ recallGroupMessage: (groupId, messageId) => bot.recallGroupMessage(groupId, messageId),
106
+ recallGuildMessage: (channelId, messageId) => bot.recallGuildMessage(channelId, messageId),
107
+ recallDirectMessage: (guildId, messageId) => bot.recallDirectMessage(guildId, messageId),
104
108
  getGuilds: () => bot.guildService.getList(),
105
109
  getChannels: (guildId) => bot.channelService.getList(guildId),
106
110
  getChannelInfo: (channelId) => bot.channelService.getInfo(channelId),
package/src/ws.ts CHANGED
@@ -17,6 +17,10 @@ export interface QqBotTransport {
17
17
  sendGroupMessage(groupId: string, message: QqOutboundMessage): Promise<unknown>;
18
18
  sendGuildMessage(channelId: string, message: QqOutboundMessage): Promise<unknown>;
19
19
  sendDirectMessage?(guildId: string, message: QqOutboundMessage): Promise<unknown>;
20
+ recallPrivateMessage?(userId: string, messageId: string): Promise<unknown>;
21
+ recallGroupMessage?(groupId: string, messageId: string): Promise<unknown>;
22
+ recallGuildMessage?(channelId: string, messageId: string): Promise<unknown>;
23
+ recallDirectMessage?(guildId: string, messageId: string): Promise<unknown>;
20
24
  getGuilds(): Promise<unknown[]>;
21
25
  getChannels(guildId: string): Promise<unknown[]>;
22
26
  getChannelInfo(channelId: string): Promise<unknown>;
@@ -97,12 +101,18 @@ export function normalizeQqMessage(raw: unknown): QqInboundMessage | null {
97
101
 
98
102
  let channelKind: QqChannelKind = 'private';
99
103
  let channelId = '';
100
- if (msg.message_type === 'guild') {
104
+
105
+ const isGroup = msg.message_type === 'group'
106
+ || (msg.message_type == null && (msg.group_id != null || msg.group_openid != null));
107
+ const isGuild = msg.message_type === 'guild'
108
+ || (msg.message_type == null && !isGroup && msg.guild_id != null && msg.channel_id != null);
109
+
110
+ if (isGuild) {
101
111
  channelKind = msg.sub_type === 'direct' ? 'direct' : 'channel';
102
112
  channelId = channelKind === 'direct'
103
113
  ? String(msg.guild_id ?? '')
104
114
  : String(msg.channel_id ?? '');
105
- } else if (msg.message_type === 'group') {
115
+ } else if (isGroup) {
106
116
  channelKind = 'group';
107
117
  channelId = String(msg.group_id ?? msg.group_openid ?? '');
108
118
  } else {
@@ -180,6 +190,10 @@ export function defaultCreateBot(config: ResolvedQqWebsocketConfig): QqBotTransp
180
190
  sendGroupMessage: (groupId, message) => bot.sendGroupMessage(groupId, message as Sendable),
181
191
  sendGuildMessage: (channelId, message) => bot.sendGuildMessage(channelId, message as Sendable),
182
192
  sendDirectMessage: (guildId, message) => bot.sendDirectMessage(guildId, message as Sendable),
193
+ recallPrivateMessage: (userId, messageId) => bot.recallPrivateMessage(userId, messageId),
194
+ recallGroupMessage: (groupId, messageId) => bot.recallGroupMessage(groupId, messageId),
195
+ recallGuildMessage: (channelId, messageId) => bot.recallGuildMessage(channelId, messageId),
196
+ recallDirectMessage: (guildId, messageId) => bot.recallDirectMessage(guildId, messageId),
183
197
  getGuilds: () => bot.guildService.getList(),
184
198
  getChannels: (guildId) => bot.channelService.getList(guildId),
185
199
  getChannelInfo: (channelId) => bot.channelService.getInfo(channelId),