@zhin.js/adapter-qq 7.0.14 → 8.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/endpoint.ts CHANGED
@@ -1,18 +1,18 @@
1
+ import { Endpoint } from 'zhin.js/adapter';
1
2
  /**
2
3
  * QQ endpoints — lifecycle, outbound, admit, agent tool surface.
3
4
  */
4
- import type {
5
- EndpointChannel,
6
- EndpointInstance,
7
- EndpointManagement,
8
- EndpointSendRequest,
5
+ import {
6
+ createRecallEndpointControl,
7
+ type EndpointControl,
8
+ type EndpointChannel,
9
+ type EndpointManagement,
10
+ type EndpointSendRequest,
9
11
  } from 'zhin.js/adapter';
10
- import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
11
12
  import type { HttpHost } from '@zhin.js/host-http';
12
13
  import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logger';
13
14
  import type { CapabilityId } from 'zhin.js';
14
15
  import { formatOutbound } from './outbound.js';
15
- import { registerQqAgentEndpoint } from './qq-agent-deps.js';
16
16
  import {
17
17
  formatInboundContent,
18
18
  parseCompoundMessageId,
@@ -48,13 +48,11 @@ export type { CreateQqHttpBot, QqHttpBotTransport } from './webhook.js';
48
48
 
49
49
  export interface QqEndpointOptions {
50
50
  readonly id: CapabilityId;
51
- readonly gateway: MessageGateway;
52
- readonly sideEvents?: SideEventGateway;
53
51
  readonly config: ResolvedQqWebsocketConfig;
54
52
  readonly createBot?: CreateQqBot;
55
53
  }
56
54
 
57
- export class QqWebsocketEndpoint implements EndpointInstance {
55
+ export class QqWebsocketEndpoint extends Endpoint<QqBotTransport> {
58
56
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
59
57
 
60
58
  readonly #options: QqEndpointOptions;
@@ -62,15 +60,20 @@ export class QqWebsocketEndpoint implements EndpointInstance {
62
60
  #bot: QqBotTransport | null = null;
63
61
  #open = false;
64
62
  #started = false;
65
- #unregisterAgent?: () => void;
66
- readonly management: EndpointManagement = createQqEndpointManagement(this);
63
+ readonly management: EndpointManagement = createQqEndpointManagement(() => this.#requireBot());
64
+ readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
67
65
 
68
66
  constructor(options: QqEndpointOptions) {
67
+ super();
69
68
  this.#logger = getAdapterLogger('qq', options.config.id);
70
69
  this.#options = options;
71
70
  this.#createBot = options.createBot ?? defaultCreateBot;
72
71
  }
73
72
 
73
+ get client(): QqBotTransport {
74
+ return this.#requireBot();
75
+ }
76
+
74
77
  /** Live endpoint 名(Console/AdapterIndex 展示用,如 bot appid 别名)。 */
75
78
  get name(): string {
76
79
  return this.#options.config.id;
@@ -80,7 +83,6 @@ export class QqWebsocketEndpoint implements EndpointInstance {
80
83
  if (this.#started) return;
81
84
  this.#started = true;
82
85
  try {
83
- this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.id, this);
84
86
  this.#bot = this.#createBot(this.#options.config);
85
87
  this.#bindBot(this.#bot);
86
88
  await this.#bot.start();
@@ -108,8 +110,6 @@ export class QqWebsocketEndpoint implements EndpointInstance {
108
110
 
109
111
  async stop(): Promise<void> {
110
112
  this.#open = false;
111
- this.#unregisterAgent?.();
112
- this.#unregisterAgent = undefined;
113
113
  if (this.#bot) {
114
114
  try {
115
115
  this.#bot.removeAllListeners();
@@ -188,7 +188,7 @@ export class QqWebsocketEndpoint implements EndpointInstance {
188
188
  + (msg.mentioned ? ' (mentioned)' : '')
189
189
  + ` | ${truncatePreview(content, 80)}`,
190
190
  );
191
- void this.#options.gateway.receive({
191
+ void this.emit('message.receive', {
192
192
  conversation,
193
193
  message: { conversation, id: msg.id },
194
194
  content,
@@ -215,46 +215,16 @@ export class QqWebsocketEndpoint implements EndpointInstance {
215
215
  });
216
216
  }
217
217
 
218
- getGuilds() {
219
- return this.#requireBot().getGuilds();
220
- }
221
-
222
- getChannels(guildId: string) {
223
- return this.#requireBot().getChannels(guildId);
224
- }
225
-
226
- getChannelInfo(channelId: string) {
227
- return this.#requireBot().getChannelInfo(channelId);
228
- }
229
-
230
- getGuildMember(guildId: string, userId: string) {
231
- return this.#requireBot().getGuildMember(guildId, userId);
232
- }
233
-
234
- getGuildRoles(guildId: string) {
235
- return this.#requireBot().getGuildRoles(guildId);
236
- }
237
-
238
- createGuildRole(guildId: string, name: string, color?: number) {
239
- return this.#requireBot().createGuildRole(guildId, name, color);
240
- }
241
-
242
- addMemberRole(guildId: string, channelId: string, userId: string, roleId: string) {
243
- return this.#requireBot().addMemberRole(guildId, channelId, userId, roleId);
244
- }
245
-
246
- removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string) {
247
- return this.#requireBot().removeMemberRole(guildId, channelId, userId, roleId);
248
- }
249
-
250
218
  #bindBot(bot: QqBotTransport): void {
251
219
  bindQqBotInboundEvents(bot, (raw) => {
220
+ this.#emitPlatformEvent('message', raw);
252
221
  const msg = normalizeQqMessage(raw);
253
222
  if (msg) this.admit(msg);
254
223
  });
255
224
  bindQqBotSideEvents(bot, (eventName, raw) => {
225
+ this.#emitPlatformEvent(eventName, raw);
256
226
  receiveQqSideEvent(
257
- this.#options.sideEvents,
227
+ (name, payload) => this.emit(name, payload),
258
228
  this.#options.config.id,
259
229
  bot as QqSideEventCaller,
260
230
  eventName,
@@ -264,6 +234,16 @@ export class QqWebsocketEndpoint implements EndpointInstance {
264
234
  });
265
235
  }
266
236
 
237
+ #emitPlatformEvent(name: string, event: unknown): void {
238
+ void this.emitPlatform(name, event).catch((error) => {
239
+ this.#logger.warn(formatCompact({
240
+ op: 'qq_platform_event_failed',
241
+ event: name,
242
+ error: error instanceof Error ? error.message : String(error),
243
+ }));
244
+ });
245
+ }
246
+
267
247
  #requireBot(): QqBotTransport {
268
248
  if (!this.#bot) throw new Error('QQ bot not connected');
269
249
  return this.#bot;
@@ -272,15 +252,13 @@ export class QqWebsocketEndpoint implements EndpointInstance {
272
252
 
273
253
  export interface QqHttpEndpointOptions {
274
254
  readonly id: CapabilityId;
275
- readonly gateway: MessageGateway;
276
- readonly sideEvents?: SideEventGateway;
277
255
  readonly http: HttpHost;
278
256
  readonly config: ResolvedQqHttpConfig;
279
257
  readonly createBot?: CreateQqHttpBot;
280
258
  }
281
259
 
282
260
  /** Webhook / middleware inbound via httpHostToken POST (qq-official-bot Middleware receiver). */
283
- export class QqHttpEndpoint implements EndpointInstance {
261
+ export class QqHttpEndpoint extends Endpoint<QqHttpBotTransport> {
284
262
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
285
263
 
286
264
  readonly #options: QqHttpEndpointOptions;
@@ -289,15 +267,20 @@ export class QqHttpEndpoint implements EndpointInstance {
289
267
  #routeReleases: ReturnType<typeof registerQqWebhookRoutes> = [];
290
268
  #open = false;
291
269
  #started = false;
292
- #unregisterAgent?: () => void;
293
- readonly management: EndpointManagement = createQqEndpointManagement(this);
270
+ readonly management: EndpointManagement = createQqEndpointManagement(() => this.#requireBot());
271
+ readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
294
272
 
295
273
  constructor(options: QqHttpEndpointOptions) {
274
+ super();
296
275
  this.#logger = getAdapterLogger('qq', options.config.id);
297
276
  this.#options = options;
298
277
  this.#createBot = options.createBot ?? defaultCreateHttpBot;
299
278
  }
300
279
 
280
+ get client(): QqHttpBotTransport {
281
+ return this.#requireBot();
282
+ }
283
+
301
284
  /** Live endpoint 名(Console/AdapterIndex 展示用)。 */
302
285
  get name(): string {
303
286
  return this.#options.config.id;
@@ -307,10 +290,6 @@ export class QqHttpEndpoint implements EndpointInstance {
307
290
  if (this.#started) return;
308
291
  this.#started = true;
309
292
  try {
310
- this.#unregisterAgent = registerQqAgentEndpoint(
311
- this.#options.config.id,
312
- this as unknown as QqWebsocketEndpoint,
313
- );
314
293
  this.#setupRoutes();
315
294
  this.#bot = this.#createBot(this.#options.config);
316
295
  this.#bindBot(this.#bot);
@@ -336,8 +315,6 @@ export class QqHttpEndpoint implements EndpointInstance {
336
315
  async stop(): Promise<void> {
337
316
  this.#open = false;
338
317
  for (const release of this.#routeReleases.splice(0)) release();
339
- this.#unregisterAgent?.();
340
- this.#unregisterAgent = undefined;
341
318
  if (this.#bot) {
342
319
  try {
343
320
  this.#bot.removeAllListeners();
@@ -415,7 +392,7 @@ export class QqHttpEndpoint implements EndpointInstance {
415
392
  + (msg.mentioned ? ' (mentioned)' : '')
416
393
  + ` | ${truncatePreview(content, 80)}`,
417
394
  );
418
- void this.#options.gateway.receive({
395
+ void this.emit('message.receive', {
419
396
  conversation,
420
397
  message: { conversation, id: msg.id },
421
398
  content,
@@ -442,38 +419,6 @@ export class QqHttpEndpoint implements EndpointInstance {
442
419
  });
443
420
  }
444
421
 
445
- getGuilds() {
446
- return this.#requireBot().getGuilds();
447
- }
448
-
449
- getChannels(guildId: string) {
450
- return this.#requireBot().getChannels(guildId);
451
- }
452
-
453
- getChannelInfo(channelId: string) {
454
- return this.#requireBot().getChannelInfo(channelId);
455
- }
456
-
457
- getGuildMember(guildId: string, userId: string) {
458
- return this.#requireBot().getGuildMember(guildId, userId);
459
- }
460
-
461
- getGuildRoles(guildId: string) {
462
- return this.#requireBot().getGuildRoles(guildId);
463
- }
464
-
465
- createGuildRole(guildId: string, name: string, color?: number) {
466
- return this.#requireBot().createGuildRole(guildId, name, color);
467
- }
468
-
469
- addMemberRole(guildId: string, channelId: string, userId: string, roleId: string) {
470
- return this.#requireBot().addMemberRole(guildId, channelId, userId, roleId);
471
- }
472
-
473
- removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string) {
474
- return this.#requireBot().removeMemberRole(guildId, channelId, userId, roleId);
475
- }
476
-
477
422
  #setupRoutes(): void {
478
423
  this.#routeReleases.push(...registerQqWebhookRoutes(this.#options.http, {
479
424
  config: this.#options.config,
@@ -483,12 +428,14 @@ export class QqHttpEndpoint implements EndpointInstance {
483
428
 
484
429
  #bindBot(bot: QqBotTransport): void {
485
430
  bindQqBotInboundEvents(bot, (raw) => {
431
+ this.#emitPlatformEvent('message', raw);
486
432
  const msg = normalizeQqMessage(raw);
487
433
  if (msg) this.admit(msg);
488
434
  });
489
435
  bindQqBotSideEvents(bot, (eventName, raw) => {
436
+ this.#emitPlatformEvent(eventName, raw);
490
437
  receiveQqSideEvent(
491
- this.#options.sideEvents,
438
+ (name, payload) => this.emit(name, payload),
492
439
  this.#options.config.id,
493
440
  bot as QqSideEventCaller,
494
441
  eventName,
@@ -498,26 +445,36 @@ export class QqHttpEndpoint implements EndpointInstance {
498
445
  });
499
446
  }
500
447
 
501
- #requireBot(): QqBotTransport {
448
+ #emitPlatformEvent(name: string, event: unknown): void {
449
+ void this.emitPlatform(name, event).catch((error) => {
450
+ this.#logger.warn(formatCompact({
451
+ op: 'qq_platform_event_failed',
452
+ event: name,
453
+ error: error instanceof Error ? error.message : String(error),
454
+ }));
455
+ });
456
+ }
457
+
458
+ #requireBot(): QqHttpBotTransport {
502
459
  if (!this.#bot) throw new Error('QQ bot not connected');
503
460
  return this.#bot;
504
461
  }
505
462
  }
506
463
 
507
- function createQqEndpointManagement(endpoint: {
508
- getGuilds(): Promise<unknown[]>;
509
- getChannels(guildId: string): Promise<unknown[]>;
510
- }): EndpointManagement {
464
+ function createQqEndpointManagement(
465
+ requireClient: () => Pick<QqBotTransport, 'getGuilds' | 'getChannels'>,
466
+ ): EndpointManagement {
511
467
  return Object.freeze<EndpointManagement>({
512
468
  async listChannels(): Promise<readonly EndpointChannel[]> {
513
469
  const channels: EndpointChannel[] = [];
514
- const guilds = await endpoint.getGuilds();
470
+ const client = requireClient();
471
+ const guilds = await client.getGuilds();
515
472
  for (const guildValue of Array.isArray(guilds) ? guilds : []) {
516
473
  const guild = asRecord(guildValue);
517
474
  const guildId = String(guild.id ?? guild.guild_id ?? guildValue ?? '');
518
475
  if (!guildId) continue;
519
476
  const guildName = String(guild.name ?? guild.guild_name ?? guildId);
520
- const rows = await endpoint.getChannels(guildId);
477
+ const rows = await client.getChannels(guildId);
521
478
  for (const channelValue of Array.isArray(rows) ? rows : []) {
522
479
  const channel = asRecord(channelValue);
523
480
  const id = String(channel.id ?? channel.channel_id ?? channelValue ?? '');
package/src/index.ts CHANGED
@@ -55,13 +55,7 @@ export {
55
55
  normalizeQqMessage,
56
56
  } from './ws.js';
57
57
 
58
- export {
59
- getQqAgentDeps,
60
- registerQqAgentEndpoint,
61
- setQqAgentDeps,
62
- type QqAgentDeps,
63
- type QqAgentEndpoint,
64
- } from './qq-agent-deps.js';
58
+ export { qqClient, type QqClientEventMap } from './client.js';
65
59
 
66
60
  export {
67
61
  checkQqPlatformPermit,
@@ -1,5 +1,5 @@
1
1
  import { receiveOneBotLikeSideEvent, type OneBotLikeRawEvent } from '@zhin.js/core';
2
- import type { SideEventGateway } from '@zhin.js/core/runtime';
2
+ import type { EndpointEventEmitter } from 'zhin.js/adapter';
3
3
  import { formatCompact, type getAdapterLogger } from '@zhin.js/logger';
4
4
 
5
5
  export interface QqSideEventCaller {
@@ -66,21 +66,21 @@ function toOneBotLikeRaw(eventName: string, raw: unknown): OneBotLikeRawEvent |
66
66
  }
67
67
 
68
68
  export function receiveQqSideEvent(
69
- sideEvents: SideEventGateway | undefined,
69
+ emit: EndpointEventEmitter,
70
70
  endpointKey: string,
71
71
  caller: QqSideEventCaller,
72
72
  eventName: string,
73
73
  raw: unknown,
74
74
  logger: ReturnType<typeof getAdapterLogger>,
75
75
  ): void {
76
- if (!sideEvents) return;
76
+ if (!emit) return;
77
77
  const mapped = toOneBotLikeRaw(eventName, raw);
78
78
  if (!mapped) return;
79
79
  const isJoinRequest = eventName === 'notice.group.join_request';
80
80
  const joinRaw = isJoinRequest && raw && typeof raw === 'object'
81
81
  ? raw as { group_id?: string | number; user_id?: string | number; join_request_id?: string | number }
82
82
  : undefined;
83
- void receiveOneBotLikeSideEvent(sideEvents, {
83
+ void receiveOneBotLikeSideEvent(emit, {
84
84
  adapter: 'qq',
85
85
  endpointKey,
86
86
  platform: 'qq',
@@ -1,21 +0,0 @@
1
- /**
2
- * Agent tool deps for qq.
3
- * Endpoints register themselves on start; tools look up by config name / endpoint id.
4
- */
5
- export interface QqAgentEndpoint {
6
- getGuilds(): Promise<unknown[]>;
7
- getChannels(guildId: string): Promise<unknown[]>;
8
- getChannelInfo(channelId: string): Promise<unknown>;
9
- getGuildMember(guildId: string, userId: string): Promise<unknown>;
10
- getGuildRoles(guildId: string): Promise<unknown[]>;
11
- createGuildRole(guildId: string, name: string, color?: number): Promise<unknown>;
12
- addMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean>;
13
- removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean>;
14
- }
15
- export interface QqAgentDeps {
16
- getEndpoint: (endpointKey: string) => QqAgentEndpoint;
17
- }
18
- export declare function registerQqAgentEndpoint(endpointKey: string, endpoint: QqAgentEndpoint): () => void;
19
- /** Optional override used by tests / transitional callers. Pass `null` to clear. */
20
- export declare function setQqAgentDeps(deps: QqAgentDeps | null): void;
21
- export declare function getQqAgentDeps(): QqAgentDeps;
@@ -1,30 +0,0 @@
1
- /**
2
- * Agent tool deps for qq.
3
- * Endpoints register themselves on start; tools look up by config name / endpoint id.
4
- */
5
- const endpoints = new Map();
6
- let override = null;
7
- export function registerQqAgentEndpoint(endpointKey, endpoint) {
8
- endpoints.set(endpointKey, endpoint);
9
- return () => {
10
- if (endpoints.get(endpointKey) === endpoint) {
11
- endpoints.delete(endpointKey);
12
- }
13
- };
14
- }
15
- /** Optional override used by tests / transitional callers. Pass `null` to clear. */
16
- export function setQqAgentDeps(deps) {
17
- override = deps;
18
- }
19
- export function getQqAgentDeps() {
20
- if (override)
21
- return override;
22
- return {
23
- getEndpoint(endpointKey) {
24
- const registered = endpoints.get(endpointKey);
25
- if (!registered)
26
- throw new Error(`Endpoint ${endpointKey} 不存在`);
27
- return registered;
28
- },
29
- };
30
- }
@@ -1,60 +0,0 @@
1
- /**
2
- * Agent tool deps for qq.
3
- * Endpoints register themselves on start; tools look up by config name / endpoint id.
4
- */
5
-
6
- export interface QqAgentEndpoint {
7
- getGuilds(): Promise<unknown[]>;
8
- getChannels(guildId: string): Promise<unknown[]>;
9
- getChannelInfo(channelId: string): Promise<unknown>;
10
- getGuildMember(guildId: string, userId: string): Promise<unknown>;
11
- getGuildRoles(guildId: string): Promise<unknown[]>;
12
- createGuildRole(guildId: string, name: string, color?: number): Promise<unknown>;
13
- addMemberRole(
14
- guildId: string,
15
- channelId: string,
16
- userId: string,
17
- roleId: string,
18
- ): Promise<boolean>;
19
- removeMemberRole(
20
- guildId: string,
21
- channelId: string,
22
- userId: string,
23
- roleId: string,
24
- ): Promise<boolean>;
25
- }
26
-
27
- export interface QqAgentDeps {
28
- getEndpoint: (endpointKey: string) => QqAgentEndpoint;
29
- }
30
-
31
- const endpoints = new Map<string, QqAgentEndpoint>();
32
- let override: QqAgentDeps | null = null;
33
-
34
- export function registerQqAgentEndpoint(
35
- endpointKey: string,
36
- endpoint: QqAgentEndpoint,
37
- ): () => void {
38
- endpoints.set(endpointKey, endpoint);
39
- return () => {
40
- if (endpoints.get(endpointKey) === endpoint) {
41
- endpoints.delete(endpointKey);
42
- }
43
- };
44
- }
45
-
46
- /** Optional override used by tests / transitional callers. Pass `null` to clear. */
47
- export function setQqAgentDeps(deps: QqAgentDeps | null): void {
48
- override = deps;
49
- }
50
-
51
- export function getQqAgentDeps(): QqAgentDeps {
52
- if (override) return override;
53
- return {
54
- getEndpoint(endpointKey: string): QqAgentEndpoint {
55
- const registered = endpoints.get(endpointKey);
56
- if (!registered) throw new Error(`Endpoint ${endpointKey} 不存在`);
57
- return registered;
58
- },
59
- };
60
- }