@zhin.js/adapter-qq 8.0.0 → 8.0.2

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,3 +1,4 @@
1
+ import { Endpoint } from 'zhin.js/adapter';
1
2
  /**
2
3
  * QQ endpoints — lifecycle, outbound, admit, agent tool surface.
3
4
  */
@@ -5,16 +6,13 @@ import {
5
6
  createRecallEndpointControl,
6
7
  type EndpointControl,
7
8
  type EndpointChannel,
8
- type EndpointInstance,
9
9
  type EndpointManagement,
10
10
  type EndpointSendRequest,
11
11
  } from 'zhin.js/adapter';
12
- import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
13
12
  import type { HttpHost } from '@zhin.js/host-http';
14
13
  import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logger';
15
14
  import type { CapabilityId } from 'zhin.js';
16
15
  import { formatOutbound } from './outbound.js';
17
- import { registerQqAgentEndpoint } from './qq-agent-deps.js';
18
16
  import {
19
17
  formatInboundContent,
20
18
  parseCompoundMessageId,
@@ -50,13 +48,11 @@ export type { CreateQqHttpBot, QqHttpBotTransport } from './webhook.js';
50
48
 
51
49
  export interface QqEndpointOptions {
52
50
  readonly id: CapabilityId;
53
- readonly gateway: MessageGateway;
54
- readonly sideEvents?: SideEventGateway;
55
51
  readonly config: ResolvedQqWebsocketConfig;
56
52
  readonly createBot?: CreateQqBot;
57
53
  }
58
54
 
59
- export class QqWebsocketEndpoint implements EndpointInstance {
55
+ export class QqWebsocketEndpoint extends Endpoint<QqBotTransport> {
60
56
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
61
57
 
62
58
  readonly #options: QqEndpointOptions;
@@ -64,16 +60,20 @@ export class QqWebsocketEndpoint implements EndpointInstance {
64
60
  #bot: QqBotTransport | null = null;
65
61
  #open = false;
66
62
  #started = false;
67
- #unregisterAgent?: () => void;
68
- readonly management: EndpointManagement = createQqEndpointManagement(this);
63
+ readonly management: EndpointManagement = createQqEndpointManagement(() => this.#requireBot());
69
64
  readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
70
65
 
71
66
  constructor(options: QqEndpointOptions) {
67
+ super();
72
68
  this.#logger = getAdapterLogger('qq', options.config.id);
73
69
  this.#options = options;
74
70
  this.#createBot = options.createBot ?? defaultCreateBot;
75
71
  }
76
72
 
73
+ get client(): QqBotTransport {
74
+ return this.#requireBot();
75
+ }
76
+
77
77
  /** Live endpoint 名(Console/AdapterIndex 展示用,如 bot appid 别名)。 */
78
78
  get name(): string {
79
79
  return this.#options.config.id;
@@ -83,7 +83,6 @@ export class QqWebsocketEndpoint implements EndpointInstance {
83
83
  if (this.#started) return;
84
84
  this.#started = true;
85
85
  try {
86
- this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.id, this);
87
86
  this.#bot = this.#createBot(this.#options.config);
88
87
  this.#bindBot(this.#bot);
89
88
  await this.#bot.start();
@@ -111,8 +110,6 @@ export class QqWebsocketEndpoint implements EndpointInstance {
111
110
 
112
111
  async stop(): Promise<void> {
113
112
  this.#open = false;
114
- this.#unregisterAgent?.();
115
- this.#unregisterAgent = undefined;
116
113
  if (this.#bot) {
117
114
  try {
118
115
  this.#bot.removeAllListeners();
@@ -191,7 +188,7 @@ export class QqWebsocketEndpoint implements EndpointInstance {
191
188
  + (msg.mentioned ? ' (mentioned)' : '')
192
189
  + ` | ${truncatePreview(content, 80)}`,
193
190
  );
194
- void this.#options.gateway.receive({
191
+ void this.emit('message.receive', {
195
192
  conversation,
196
193
  message: { conversation, id: msg.id },
197
194
  content,
@@ -218,46 +215,16 @@ export class QqWebsocketEndpoint implements EndpointInstance {
218
215
  });
219
216
  }
220
217
 
221
- getGuilds() {
222
- return this.#requireBot().getGuilds();
223
- }
224
-
225
- getChannels(guildId: string) {
226
- return this.#requireBot().getChannels(guildId);
227
- }
228
-
229
- getChannelInfo(channelId: string) {
230
- return this.#requireBot().getChannelInfo(channelId);
231
- }
232
-
233
- getGuildMember(guildId: string, userId: string) {
234
- return this.#requireBot().getGuildMember(guildId, userId);
235
- }
236
-
237
- getGuildRoles(guildId: string) {
238
- return this.#requireBot().getGuildRoles(guildId);
239
- }
240
-
241
- createGuildRole(guildId: string, name: string, color?: number) {
242
- return this.#requireBot().createGuildRole(guildId, name, color);
243
- }
244
-
245
- addMemberRole(guildId: string, channelId: string, userId: string, roleId: string) {
246
- return this.#requireBot().addMemberRole(guildId, channelId, userId, roleId);
247
- }
248
-
249
- removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string) {
250
- return this.#requireBot().removeMemberRole(guildId, channelId, userId, roleId);
251
- }
252
-
253
218
  #bindBot(bot: QqBotTransport): void {
254
219
  bindQqBotInboundEvents(bot, (raw) => {
220
+ this.#emitPlatformEvent('message', raw);
255
221
  const msg = normalizeQqMessage(raw);
256
222
  if (msg) this.admit(msg);
257
223
  });
258
224
  bindQqBotSideEvents(bot, (eventName, raw) => {
225
+ this.#emitPlatformEvent(eventName, raw);
259
226
  receiveQqSideEvent(
260
- this.#options.sideEvents,
227
+ (name, payload) => this.emit(name, payload),
261
228
  this.#options.config.id,
262
229
  bot as QqSideEventCaller,
263
230
  eventName,
@@ -267,6 +234,16 @@ export class QqWebsocketEndpoint implements EndpointInstance {
267
234
  });
268
235
  }
269
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
+
270
247
  #requireBot(): QqBotTransport {
271
248
  if (!this.#bot) throw new Error('QQ bot not connected');
272
249
  return this.#bot;
@@ -275,15 +252,13 @@ export class QqWebsocketEndpoint implements EndpointInstance {
275
252
 
276
253
  export interface QqHttpEndpointOptions {
277
254
  readonly id: CapabilityId;
278
- readonly gateway: MessageGateway;
279
- readonly sideEvents?: SideEventGateway;
280
255
  readonly http: HttpHost;
281
256
  readonly config: ResolvedQqHttpConfig;
282
257
  readonly createBot?: CreateQqHttpBot;
283
258
  }
284
259
 
285
260
  /** Webhook / middleware inbound via httpHostToken POST (qq-official-bot Middleware receiver). */
286
- export class QqHttpEndpoint implements EndpointInstance {
261
+ export class QqHttpEndpoint extends Endpoint<QqHttpBotTransport> {
287
262
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
288
263
 
289
264
  readonly #options: QqHttpEndpointOptions;
@@ -292,16 +267,20 @@ export class QqHttpEndpoint implements EndpointInstance {
292
267
  #routeReleases: ReturnType<typeof registerQqWebhookRoutes> = [];
293
268
  #open = false;
294
269
  #started = false;
295
- #unregisterAgent?: () => void;
296
- readonly management: EndpointManagement = createQqEndpointManagement(this);
270
+ readonly management: EndpointManagement = createQqEndpointManagement(() => this.#requireBot());
297
271
  readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
298
272
 
299
273
  constructor(options: QqHttpEndpointOptions) {
274
+ super();
300
275
  this.#logger = getAdapterLogger('qq', options.config.id);
301
276
  this.#options = options;
302
277
  this.#createBot = options.createBot ?? defaultCreateHttpBot;
303
278
  }
304
279
 
280
+ get client(): QqHttpBotTransport {
281
+ return this.#requireBot();
282
+ }
283
+
305
284
  /** Live endpoint 名(Console/AdapterIndex 展示用)。 */
306
285
  get name(): string {
307
286
  return this.#options.config.id;
@@ -311,10 +290,6 @@ export class QqHttpEndpoint implements EndpointInstance {
311
290
  if (this.#started) return;
312
291
  this.#started = true;
313
292
  try {
314
- this.#unregisterAgent = registerQqAgentEndpoint(
315
- this.#options.config.id,
316
- this as unknown as QqWebsocketEndpoint,
317
- );
318
293
  this.#setupRoutes();
319
294
  this.#bot = this.#createBot(this.#options.config);
320
295
  this.#bindBot(this.#bot);
@@ -340,8 +315,6 @@ export class QqHttpEndpoint implements EndpointInstance {
340
315
  async stop(): Promise<void> {
341
316
  this.#open = false;
342
317
  for (const release of this.#routeReleases.splice(0)) release();
343
- this.#unregisterAgent?.();
344
- this.#unregisterAgent = undefined;
345
318
  if (this.#bot) {
346
319
  try {
347
320
  this.#bot.removeAllListeners();
@@ -419,7 +392,7 @@ export class QqHttpEndpoint implements EndpointInstance {
419
392
  + (msg.mentioned ? ' (mentioned)' : '')
420
393
  + ` | ${truncatePreview(content, 80)}`,
421
394
  );
422
- void this.#options.gateway.receive({
395
+ void this.emit('message.receive', {
423
396
  conversation,
424
397
  message: { conversation, id: msg.id },
425
398
  content,
@@ -446,38 +419,6 @@ export class QqHttpEndpoint implements EndpointInstance {
446
419
  });
447
420
  }
448
421
 
449
- getGuilds() {
450
- return this.#requireBot().getGuilds();
451
- }
452
-
453
- getChannels(guildId: string) {
454
- return this.#requireBot().getChannels(guildId);
455
- }
456
-
457
- getChannelInfo(channelId: string) {
458
- return this.#requireBot().getChannelInfo(channelId);
459
- }
460
-
461
- getGuildMember(guildId: string, userId: string) {
462
- return this.#requireBot().getGuildMember(guildId, userId);
463
- }
464
-
465
- getGuildRoles(guildId: string) {
466
- return this.#requireBot().getGuildRoles(guildId);
467
- }
468
-
469
- createGuildRole(guildId: string, name: string, color?: number) {
470
- return this.#requireBot().createGuildRole(guildId, name, color);
471
- }
472
-
473
- addMemberRole(guildId: string, channelId: string, userId: string, roleId: string) {
474
- return this.#requireBot().addMemberRole(guildId, channelId, userId, roleId);
475
- }
476
-
477
- removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string) {
478
- return this.#requireBot().removeMemberRole(guildId, channelId, userId, roleId);
479
- }
480
-
481
422
  #setupRoutes(): void {
482
423
  this.#routeReleases.push(...registerQqWebhookRoutes(this.#options.http, {
483
424
  config: this.#options.config,
@@ -487,12 +428,14 @@ export class QqHttpEndpoint implements EndpointInstance {
487
428
 
488
429
  #bindBot(bot: QqBotTransport): void {
489
430
  bindQqBotInboundEvents(bot, (raw) => {
431
+ this.#emitPlatformEvent('message', raw);
490
432
  const msg = normalizeQqMessage(raw);
491
433
  if (msg) this.admit(msg);
492
434
  });
493
435
  bindQqBotSideEvents(bot, (eventName, raw) => {
436
+ this.#emitPlatformEvent(eventName, raw);
494
437
  receiveQqSideEvent(
495
- this.#options.sideEvents,
438
+ (name, payload) => this.emit(name, payload),
496
439
  this.#options.config.id,
497
440
  bot as QqSideEventCaller,
498
441
  eventName,
@@ -502,26 +445,36 @@ export class QqHttpEndpoint implements EndpointInstance {
502
445
  });
503
446
  }
504
447
 
505
- #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 {
506
459
  if (!this.#bot) throw new Error('QQ bot not connected');
507
460
  return this.#bot;
508
461
  }
509
462
  }
510
463
 
511
- function createQqEndpointManagement(endpoint: {
512
- getGuilds(): Promise<unknown[]>;
513
- getChannels(guildId: string): Promise<unknown[]>;
514
- }): EndpointManagement {
464
+ function createQqEndpointManagement(
465
+ requireClient: () => Pick<QqBotTransport, 'getGuilds' | 'getChannels'>,
466
+ ): EndpointManagement {
515
467
  return Object.freeze<EndpointManagement>({
516
468
  async listChannels(): Promise<readonly EndpointChannel[]> {
517
469
  const channels: EndpointChannel[] = [];
518
- const guilds = await endpoint.getGuilds();
470
+ const client = requireClient();
471
+ const guilds = await client.getGuilds();
519
472
  for (const guildValue of Array.isArray(guilds) ? guilds : []) {
520
473
  const guild = asRecord(guildValue);
521
474
  const guildId = String(guild.id ?? guild.guild_id ?? guildValue ?? '');
522
475
  if (!guildId) continue;
523
476
  const guildName = String(guild.name ?? guild.guild_name ?? guildId);
524
- const rows = await endpoint.getChannels(guildId);
477
+ const rows = await client.getChannels(guildId);
525
478
  for (const channelValue of Array.isArray(rows) ? rows : []) {
526
479
  const channel = asRecord(channelValue);
527
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
- }