@zhin.js/adapter-kook 1.1.18 → 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.
package/src/endpoint.ts CHANGED
@@ -2,15 +2,20 @@ import { EndpointDeliveryError, type ConversationRef } from '@zhin.js/im-contrac
2
2
  /**
3
3
  * KookEndpoint — lifecycle, outbound, admit, OpenAPI helpers for agent tools.
4
4
  */
5
- import { Client } from 'kook-client';
6
5
  import {
7
- Endpoint,
6
+ createEndpointSpecification,
7
+ endpointSpec,
8
+ type Endpoint,
9
+ type EndpointSpecification,
8
10
  type EndpointControl,
9
11
  type EndpointChannel,
10
12
  type EndpointGroup,
11
13
  type EndpointManagement,
12
14
  type EndpointSendRequest,
13
15
  type EndpointTransportState,
16
+ type EndpointManagementRequest,
17
+ createEndpointOperation,
18
+ type EndpointGroupRequest,
14
19
  } from 'zhin.js/adapter';
15
20
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
16
21
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
@@ -30,10 +35,8 @@ import {
30
35
  } from './protocol.js';
31
36
  import { registerKookWebhookRoutes } from './webhook.js';
32
37
  import {
33
- defaultCreateClient,
34
- defaultCreateWebhookClient,
38
+ RuntimeKookClient, kookWebsocketClientOptions, kookWebhookClientOptions,
35
39
  normalizeKookMessage,
36
- type CreateKookClient,
37
40
  type KookClientTransport,
38
41
  } from './ws.js';
39
42
  import { recallKookMessage, sendKookOutbound } from './outbound.js';
@@ -43,7 +46,6 @@ import { receiveKookSideEvent } from './side-event-dispatch.js';
43
46
  export interface KookEndpointOptions {
44
47
  readonly id: CapabilityId;
45
48
  readonly config: ResolvedKookWebsocketConfig;
46
- readonly createClient?: CreateKookClient;
47
49
  }
48
50
 
49
51
  /** 入站 message.receive 载荷:规范角色 owner/admin 进 roles,原始角色 id 进 permissions。 */
@@ -56,6 +58,8 @@ function kookInboundPayload(
56
58
  ) {
57
59
  return {
58
60
  conversation,
61
+ event: msg.event,
62
+ platformSentAt: msg.platformSentAt,
59
63
  message: { conversation, id: msg.id },
60
64
  content: formatInboundContent(msg),
61
65
  sender: {
@@ -76,82 +80,78 @@ function kookInboundPayload(
76
80
  };
77
81
  }
78
82
 
79
- export class KookWebsocketEndpoint extends Endpoint<KookClientTransport> {
83
+ export class KookWebsocketEndpoint extends RuntimeKookClient implements Endpoint {
84
+ readonly [endpointSpec]: EndpointSpecification;
80
85
  #sentConversations = new Map<string, ConversationRef>();
81
86
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
82
87
 
83
88
  readonly #options: KookEndpointOptions;
84
- readonly #createClient: CreateKookClient;
85
- #client: KookClientTransport | null = null;
86
- readonly #roles = new KookSenderRoleResolver(() => this.#client);
89
+ readonly #roles = new KookSenderRoleResolver(() => this);
87
90
  #open = false;
91
+ #executionSignal?: AbortSignal;
88
92
  #started = false;
89
- readonly management: EndpointManagement = createKookEndpointManagement(() => this.#requireClient());
90
- readonly control: EndpointControl = Object.freeze<EndpointControl>({
91
- recall: (message) => recallKookMessage(this.#requireClient(), message),
93
+ readonly #management: EndpointManagement = createKookEndpointManagement(() => this.#requireClient());
94
+ readonly #control: EndpointControl = Object.freeze<EndpointControl>({
95
+ recall: request => recallKookMessage(this.#requireClient(), request),
92
96
  });
93
97
 
94
98
  constructor(options: KookEndpointOptions) {
95
- super();
99
+ super(kookWebsocketClientOptions(options.config), undefined, options.config.apiProxy);
96
100
  this.#logger = getAdapterLogger('kook', options.config.id);
97
101
  this.#options = options;
98
- this.#createClient = options.createClient ?? (defaultCreateClient as CreateKookClient);
99
- }
100
-
101
- get client(): KookClientTransport {
102
- return this.#requireClient();
102
+ const endpoint = this;
103
+ this[endpointSpec] = createEndpointSpecification(this, {
104
+ start: signal => this.#startFramework(signal), open: () => this.#openFramework(), close: () => this.#closeFramework(),
105
+ stop: () => this.#stopFramework(), send: request => this.#sendFramework(request),
106
+ name: options.config.id, get transportState() { return endpoint.#transportState; },
107
+ management: this.#management, control: this.#control,
108
+ });
103
109
  }
104
110
 
105
- get transportState(): EndpointTransportState | undefined {
111
+ get #transportState(): EndpointTransportState | undefined {
106
112
  if (!this.#started) return 'stopped';
107
- return this.#client?.getTransportState?.();
113
+ return this.getTransportState?.();
108
114
  }
109
115
 
110
- async start(): Promise<void> {
116
+ async #startFramework(signal: AbortSignal): Promise<void> {
117
+ this.#executionSignal = signal;
118
+ this.request.interceptors.request.use(request => { request.signal = signal; return request; });
111
119
  if (this.#started) return;
112
120
  this.#started = true;
113
121
  try {
114
- this.#client = this.#createClient(this.#options.config);
115
- this.#bindClient(this.#client);
116
- await this.#client.connect();
122
+ this.#bindClient(this);
123
+ await this.connect();
117
124
  this.#logger.info(formatCompact({
118
125
  op: 'connect',
119
126
  endpoint: this.#options.config.id,
120
127
  mode: 'websocket',
121
- self_id: this.#client.self_id != null ? String(this.#client.self_id) : undefined,
128
+ self_id: this.self_id != null ? String(this.self_id) : undefined,
122
129
  }));
123
130
  } catch (error) {
124
- await this.stop();
131
+ await this.#stopFramework();
125
132
  this.#logger.error('Failed to connect KOOK websocket:', error);
126
133
  throw error;
127
134
  }
128
135
  }
129
136
 
130
- open(): void {
137
+ #openFramework(): void {
131
138
  this.#open = true;
132
139
  }
133
140
 
134
- close(): void {
141
+ #closeFramework(): void {
135
142
  this.#open = false;
136
143
  }
137
144
 
138
- async stop(): Promise<void> {
145
+ async #stopFramework(): Promise<void> {
139
146
  this.#open = false;
140
147
  this.#sentConversations.clear();
141
- if (this.#client) {
142
- try {
143
- this.#client.removeAllListeners();
144
- await this.#client.disconnect();
145
- } catch {
146
- /* ignore */
147
- }
148
- this.#client = null;
149
- }
148
+ this.removeAllListeners();
149
+ await this.disconnect();
150
150
  this.#started = false;
151
151
  this.#logger.debug(formatCompact({ op: 'disconnect' }));
152
152
  }
153
153
 
154
- async send(request: EndpointSendRequest): Promise<string> {
154
+ async #sendFramework(request: EndpointSendRequest): Promise<string> {
155
155
  const { conversation } = request;
156
156
  const messageId = await sendKookOutbound(this.#requireClient(), request);
157
157
  rememberKookConversation(this.#sentConversations, messageId, conversation);
@@ -166,53 +166,61 @@ export class KookWebsocketEndpoint extends Endpoint<KookClientTransport> {
166
166
 
167
167
  /** Test / internal: admit a normalized message when open. */
168
168
  admit(msg: KookInboundMessage): void {
169
- if (!this.#open) return;
170
- if (msg.authorBot) return;
171
- const conversation = kookInboundConversation(String(this.#options.id), msg);
172
- const selfId = this.#client?.self_id != null ? String(this.#client.self_id) : undefined;
173
- void this.#roles.resolve(msg).then((projection) => this.emit('message.receive',
174
- kookInboundPayload(this.#options.config.id, conversation, msg, projection, selfId),
175
- )).catch((err) => {
176
- this.#logger.warn(formatCompact({
177
- op: 'kook_gateway_receive_failed',
178
- target: `${conversation.kind}:${conversation.id}`,
179
- error: err instanceof Error ? err.message : String(err),
180
- }));
181
- });
169
+ void this[endpointSpec].events.run(() => {
170
+ if (msg.authorBot) return;
171
+ const conversation = kookInboundConversation(String(this.#options.id), msg);
172
+ const selfId = this.self_id != null ? String(this.self_id) : undefined;
173
+ return this.#roles.resolve(msg).then((projection) => {
174
+ this.#executionSignal?.throwIfAborted();
175
+ return this[endpointSpec].events.emit('message.receive',
176
+ kookInboundPayload(this.#options.config.id, conversation, msg, projection, selfId));
177
+ }).catch((err) => {
178
+ this.#logger.warn(formatCompact({
179
+ op: 'kook_gateway_receive_failed',
180
+ target: `${conversation.kind}:${conversation.id}`,
181
+ error: err instanceof Error ? err.message : String(err),
182
+ }));
183
+ });
184
+ }).catch(() => this.#logger.debug('Inbound callback was not admitted'));
182
185
  }
183
186
 
184
187
  async recallMessage(messageId: string, conversation?: ConversationRef): Promise<void> {
185
188
  if (!conversation) throw new EndpointDeliveryError('unsupported_operation', 'KOOK recall requires conversation routing', 'rejected');
186
- await recallKookMessage(this.#requireClient(), { id: messageId, conversation });
189
+ await recallKookMessage(this.#requireClient(), { message: { id: messageId, conversation } });
187
190
  }
188
191
 
189
192
  #bindClient(client: KookClientTransport): void {
190
193
  client.on('message', (raw) => {
191
- this.#emitPlatformEvent('message', raw);
192
- const msg = normalizeKookMessage(raw);
193
- if (msg) this.admit(msg);
194
+ void this[endpointSpec].events.run(() => {
195
+ this.#emitPlatformEvent('message', raw);
196
+ const msg = normalizeKookMessage(raw);
197
+ if (msg) this.admit(msg);
198
+ }).catch(() => this.#logger.debug('Native callback was not admitted'));
194
199
  });
195
200
  const receiver = (client as { receiver?: { on(event: string, listener: (...args: unknown[]) => void): void } }).receiver;
196
201
  receiver?.on('event', (raw) => {
197
- if (this.#client !== client || !this.#open) return;
198
- this.#emitPlatformEvent('event', raw);
199
- if (normalizeKookButtonEvent(raw)) {
200
- void this.admitButton(raw).catch(error => this.#logger.warn(formatCompact({ op: 'kook_button_failed', error: error instanceof Error ? error.message : 'dispatch failed' })));
201
- return;
202
- }
203
- receiveKookSideEvent((name, payload) => this.emit(name, payload), this.#options.config.id, raw, this.#logger);
202
+ void this[endpointSpec].events.run(() => {
203
+ if (this !== client || !this.#open) return;
204
+ this.#emitPlatformEvent('event', raw);
205
+ if (normalizeKookButtonEvent(raw)) {
206
+ void this.admitButton(raw).catch(error => this.#logger.warn(formatCompact({ op: 'kook_button_failed', error: error instanceof Error ? error.message : 'dispatch failed' })));
207
+ return;
208
+ }
209
+ receiveKookSideEvent((name, payload) => this[endpointSpec].events.emit(name, payload), this.#options.config.id, raw, this.#logger);
210
+ }).catch(() => this.#logger.debug('Native callback was not admitted'));
204
211
  });
205
212
  }
206
213
 
207
214
  async admitButton(raw: unknown): Promise<void> {
208
- if (!this.#open) throw new Error('KOOK endpoint is closed');
209
- const event = normalizeKookButtonEvent(raw);
210
- if (!event) throw new Error('Invalid KOOK button event');
211
- await this.emitAccepted('message.receive', kookButtonIngress(String(this.#options.id), this.#options.config.id, event, this.#sentConversations.get(event.sourceMessageId)));
215
+ return this[endpointSpec].events.run(async () => {
216
+ const event = normalizeKookButtonEvent(raw);
217
+ if (!event) throw new Error('Invalid KOOK button event');
218
+ await this[endpointSpec].events.emitAccepted('message.receive', kookButtonIngress(String(this.#options.id), this.#options.config.id, event, this.#sentConversations.get(event.sourceMessageId)));
219
+ });
212
220
  }
213
221
 
214
222
  #emitPlatformEvent(name: string, event: unknown): void {
215
- void this.emitPlatform(name, event).catch((error) => {
223
+ void this[endpointSpec].events.platform(name, event).catch((error) => {
216
224
  this.#logger.warn(formatCompact({
217
225
  op: 'kook_platform_event_failed',
218
226
  event: name,
@@ -222,8 +230,8 @@ export class KookWebsocketEndpoint extends Endpoint<KookClientTransport> {
222
230
  }
223
231
 
224
232
  #requireClient(): KookClientTransport {
225
- if (!this.#client) throw new Error('KOOK client not connected');
226
- return this.#client;
233
+ if (!this.#started) throw new Error('KOOK client not connected');
234
+ return this;
227
235
  }
228
236
  }
229
237
 
@@ -231,35 +239,34 @@ export interface KookWebhookEndpointOptions {
231
239
  readonly id: CapabilityId;
232
240
  readonly http: HttpHost;
233
241
  readonly config: ResolvedKookWebhookConfig;
234
- readonly createClient?: CreateKookClient;
235
242
  }
236
243
 
237
- export class KookWebhookEndpoint extends Endpoint<KookClientTransport> {
244
+ export class KookWebhookEndpoint extends RuntimeKookClient implements Endpoint {
245
+ readonly [endpointSpec]: EndpointSpecification;
238
246
  #sentConversations = new Map<string, ConversationRef>();
239
247
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
240
248
 
241
249
  readonly #options: KookWebhookEndpointOptions;
242
- readonly #createClient: CreateKookClient;
243
- #client: KookClientTransport | null = null;
244
- readonly #roles = new KookSenderRoleResolver(() => this.#client);
250
+ readonly #roles = new KookSenderRoleResolver(() => this);
245
251
  #routeReleases: HttpRouteRegistration[] = [];
246
252
  #processedSn = new Set<number>();
247
253
  #open = false;
254
+ #executionSignal?: AbortSignal;
248
255
  #started = false;
249
- readonly management: EndpointManagement = createKookEndpointManagement(() => this.#requireClient());
250
- readonly control: EndpointControl = Object.freeze<EndpointControl>({
251
- recall: (message) => recallKookMessage(this.#requireClient(), message),
256
+ readonly #management: EndpointManagement = createKookEndpointManagement(() => this.#requireClient());
257
+ readonly #control: EndpointControl = Object.freeze<EndpointControl>({
258
+ recall: request => recallKookMessage(this.#requireClient(), request),
252
259
  });
253
260
 
254
261
  constructor(options: KookWebhookEndpointOptions) {
255
- super();
262
+ super(kookWebhookClientOptions(options.config), undefined, options.config.apiProxy);
256
263
  this.#logger = getAdapterLogger('kook', options.config.id);
257
264
  this.#options = options;
258
- this.#createClient = options.createClient ?? (defaultCreateWebhookClient as CreateKookClient);
259
- }
260
-
261
- get client(): KookClientTransport {
262
- return this.#requireClient();
265
+ this[endpointSpec] = createEndpointSpecification(this, {
266
+ start: signal => this.#startFramework(signal), open: () => this.#openFramework(), close: () => this.#closeFramework(),
267
+ stop: () => this.#stopFramework(), send: request => this.#sendFramework(request), name: options.config.id,
268
+ management: this.#management, control: this.#control,
269
+ });
263
270
  }
264
271
 
265
272
  /** Used by webhook handler. */
@@ -267,62 +274,56 @@ export class KookWebhookEndpoint extends Endpoint<KookClientTransport> {
267
274
  return this.#open;
268
275
  }
269
276
 
270
- get config(): ResolvedKookWebhookConfig {
277
+ get endpointConfig(): ResolvedKookWebhookConfig {
271
278
  return this.#options.config;
272
279
  }
273
280
 
274
281
  get selfId(): string | undefined {
275
- return this.#client?.self_id != null ? String(this.#client.self_id) : undefined;
282
+ return this.self_id != null ? String(this.self_id) : undefined;
276
283
  }
277
284
 
278
- async start(): Promise<void> {
285
+ async #startFramework(signal: AbortSignal): Promise<void> {
286
+ this.#executionSignal = signal;
287
+ this.request.interceptors.request.use(request => { request.signal = signal; return request; });
279
288
  if (this.#started) return;
280
289
  this.#started = true;
281
290
  try {
282
- this.#client = this.#createClient(this.#options.config);
283
- await (this.#client as Client).init();
291
+ await this.init();
284
292
  this.#routeReleases.push(...registerKookWebhookRoutes(this.#options.http, this));
285
293
  this.#logger.info(formatCompact({
286
294
  op: 'connect',
287
295
  endpoint: this.#options.config.id,
288
296
  mode: 'webhook',
289
297
  path: this.#options.config.webhookPath,
290
- self_id: this.#client.self_id != null ? String(this.#client.self_id) : undefined,
298
+ self_id: this.self_id != null ? String(this.self_id) : undefined,
291
299
  }));
292
300
  } catch (error) {
293
- await this.stop();
301
+ await this.#stopFramework();
294
302
  this.#logger.error('Failed to connect KOOK webhook:', error);
295
303
  throw error;
296
304
  }
297
305
  }
298
306
 
299
- open(): void {
307
+ #openFramework(): void {
300
308
  this.#open = true;
301
309
  }
302
310
 
303
- close(): void {
311
+ #closeFramework(): void {
304
312
  this.#open = false;
305
313
  }
306
314
 
307
- async stop(): Promise<void> {
315
+ async #stopFramework(): Promise<void> {
308
316
  this.#open = false;
309
317
  this.#sentConversations.clear();
310
318
  for (const release of this.#routeReleases.splice(0)) release();
311
319
  this.#processedSn.clear();
312
- if (this.#client) {
313
- try {
314
- this.#client.removeAllListeners();
315
- await this.#client.disconnect();
316
- } catch {
317
- /* ignore */
318
- }
319
- this.#client = null;
320
- }
320
+ this.removeAllListeners();
321
+ await this.disconnect();
321
322
  this.#started = false;
322
323
  this.#logger.debug(formatCompact({ op: 'disconnect' }));
323
324
  }
324
325
 
325
- async send(request: EndpointSendRequest): Promise<string> {
326
+ async #sendFramework(request: EndpointSendRequest): Promise<string> {
326
327
  const { conversation } = request;
327
328
  const messageId = await sendKookOutbound(this.#requireClient(), request);
328
329
  rememberKookConversation(this.#sentConversations, messageId, conversation);
@@ -336,52 +337,57 @@ export class KookWebhookEndpoint extends Endpoint<KookClientTransport> {
336
337
  }
337
338
 
338
339
  async admitEvent(event: KookWebhookEventData): Promise<void> {
339
- if (normalizeKookButtonEvent(event)) {
340
- if (!this.#open) throw new Error('KOOK endpoint is closed');
341
- const button = normalizeKookButtonEvent(event)!;
342
- await this.emitAccepted('message.receive', kookButtonIngress(String(this.#options.id), this.#options.config.id, button, this.#sentConversations.get(button.sourceMessageId)));
343
- return;
344
- }
345
- void this.emitPlatform(String(event.type ?? 'event'), event).catch((error) => {
346
- this.#logger.warn(formatCompact({
347
- op: 'kook_platform_event_failed',
348
- event: String(event.type ?? 'event'),
349
- error: error instanceof Error ? error.message : String(error),
350
- }));
351
- });
352
- if (receiveKookSideEvent(
353
- (name, payload) => this.emit(name, payload),
354
- this.#options.config.id,
355
- event,
356
- this.#logger,
357
- )) return;
358
- const message = normalizeKookWebhookEvent(event, {
359
- ignore: this.#options.config.ignore,
360
- selfId: this.selfId,
340
+ return this[endpointSpec].events.run(async () => {
341
+ if (normalizeKookButtonEvent(event)) {
342
+ if (!this.#open) throw new Error('KOOK endpoint is closed');
343
+ const button = normalizeKookButtonEvent(event)!;
344
+ await this[endpointSpec].events.emitAccepted('message.receive', kookButtonIngress(String(this.#options.id), this.#options.config.id, button, this.#sentConversations.get(button.sourceMessageId)));
345
+ return;
346
+ }
347
+ void this[endpointSpec].events.platform(String(event.type ?? 'event'), event).catch((error) => {
348
+ this.#logger.warn(formatCompact({
349
+ op: 'kook_platform_event_failed',
350
+ event: String(event.type ?? 'event'),
351
+ error: error instanceof Error ? error.message : String(error),
352
+ }));
353
+ });
354
+ if (receiveKookSideEvent(
355
+ (name, payload) => this[endpointSpec].events.emit(name, payload),
356
+ this.#options.config.id,
357
+ event,
358
+ this.#logger,
359
+ )) return;
360
+ const message = normalizeKookWebhookEvent(event, {
361
+ ignore: this.#options.config.ignore,
362
+ selfId: this.selfId,
363
+ });
364
+ if (message) this.admit(message);
361
365
  });
362
- if (message) this.admit(message);
363
366
  }
364
367
 
365
368
  /** Test / internal: admit a message when open. */
366
369
  admit(msg: KookInboundMessage): void {
367
- if (!this.#open) return;
368
- if (msg.authorBot) return;
369
- const conversation = kookInboundConversation(String(this.#options.id), msg);
370
- const selfId = this.#client?.self_id != null ? String(this.#client.self_id) : undefined;
371
- void this.#roles.resolve(msg).then((projection) => this.emit('message.receive',
372
- kookInboundPayload(this.#options.config.id, conversation, msg, projection, selfId),
373
- )).catch((err) => {
374
- this.#logger.warn(formatCompact({
375
- op: 'kook_gateway_receive_failed',
376
- target: `${conversation.kind}:${conversation.id}`,
377
- error: err instanceof Error ? err.message : String(err),
378
- }));
379
- });
370
+ void this[endpointSpec].events.run(() => {
371
+ if (msg.authorBot) return;
372
+ const conversation = kookInboundConversation(String(this.#options.id), msg);
373
+ const selfId = this.self_id != null ? String(this.self_id) : undefined;
374
+ return this.#roles.resolve(msg).then((projection) => {
375
+ this.#executionSignal?.throwIfAborted();
376
+ return this[endpointSpec].events.emit('message.receive',
377
+ kookInboundPayload(this.#options.config.id, conversation, msg, projection, selfId));
378
+ }).catch((err) => {
379
+ this.#logger.warn(formatCompact({
380
+ op: 'kook_gateway_receive_failed',
381
+ target: `${conversation.kind}:${conversation.id}`,
382
+ error: err instanceof Error ? err.message : String(err),
383
+ }));
384
+ });
385
+ }).catch(() => this.#logger.debug('Inbound callback was not admitted'));
380
386
  }
381
387
 
382
388
  async recallMessage(messageId: string, conversation?: ConversationRef): Promise<void> {
383
389
  if (!conversation) throw new EndpointDeliveryError('unsupported_operation', 'KOOK recall requires conversation routing', 'rejected');
384
- await recallKookMessage(this.#requireClient(), { id: messageId, conversation });
390
+ await recallKookMessage(this.#requireClient(), { message: { id: messageId, conversation } });
385
391
  }
386
392
 
387
393
  checkAndRememberSn(sn: number): boolean {
@@ -397,21 +403,11 @@ export class KookWebhookEndpoint extends Endpoint<KookClientTransport> {
397
403
  forgetSn(sn: number): void { this.#processedSn.delete(sn); }
398
404
 
399
405
  #requireClient(): KookClientTransport {
400
- if (!this.#client) throw new Error('KOOK client not initialized');
401
- return this.#client;
406
+ if (!this.#started) throw new Error('KOOK client not initialized');
407
+ return this;
402
408
  }
403
409
  }
404
410
 
405
- /**
406
- * KOOK guild/channel/user id 是字符串形式的雪花号,超出
407
- * Number.MAX_SAFE_INTEGER,Number() 会丢精度。Console 社交面只把 group_id
408
- * 当 JSON 值透传、并以字符串回传给 listGroupMembers,因此保留原始字符串
409
- * (仅按契约类型声明强转)是全链路最不丢信息的方案。
410
- */
411
- function toGroupId(id: string): number {
412
- return id as unknown as number;
413
- }
414
-
415
411
  /** KOOK 文字频道标记:HTTP API type=1(2=语音),kook-client 消息侧用 'GROUP'。 */
416
412
  function rememberKookConversation(cache: Map<string, ConversationRef>, messageId: string, conversation: ConversationRef): void {
417
413
  cache.set(messageId, conversation);
@@ -422,7 +418,7 @@ function kookButtonIngress(endpointKey: string, endpointId: string, event: NonNu
422
418
  const conversation: ConversationRef = { endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
423
419
  kind: sentConversation?.kind ?? event.channelKind, id: sentConversation?.id ?? event.channelId,
424
420
  ...(sentConversation?.parent ? { parent: sentConversation.parent } : {}) };
425
- return { conversation, message: { conversation, id: event.id }, content: `[action: ${event.payload}]`,
421
+ return { event: event.event, platformSentAt: event.platformSentAt, conversation, message: { conversation, id: event.id }, content: `[action: ${event.payload}]`,
426
422
  segments: [{ type: 'action', data: { id: event.id, payload: event.payload, sourceMessageId: event.sourceMessageId } }],
427
423
  sender: { id: event.userId }, endpointId,
428
424
  metadata: Object.freeze({ eventType: 'message_btn_click', payload: event.payload, sourceMessageId: event.sourceMessageId, timestamp: event.timestamp }) };
@@ -441,26 +437,28 @@ export function createKookEndpointManagement(
441
437
  requireClient: () => KookClientTransport,
442
438
  ): EndpointManagement {
443
439
  return Object.freeze<EndpointManagement>({
444
- async listGroups(): Promise<readonly EndpointGroup[]> {
445
- const guilds = await requireClient().getGuildList();
440
+ async listGroups(request: EndpointManagementRequest = {}): Promise<readonly EndpointGroup[]> {
441
+ const operation = createEndpointOperation(request);
442
+ const guilds = await operation.run(() => requireClient().getGuildList());
446
443
  const groups: EndpointGroup[] = [];
447
444
  for (const guild of guilds) {
448
445
  if (guild?.id == null) continue;
449
446
  groups.push({
450
- group_id: toGroupId(String(guild.id)),
447
+ group_id: String(guild.id),
451
448
  name: String(guild.name ?? guild.id),
452
449
  });
453
450
  }
454
451
  return groups;
455
452
  },
456
- async listChannels(): Promise<readonly EndpointChannel[]> {
453
+ async listChannels(request: EndpointManagementRequest = {}): Promise<readonly EndpointChannel[]> {
454
+ const operation = createEndpointOperation(request);
457
455
  const client = requireClient();
458
456
  const channels: EndpointChannel[] = [];
459
- for (const guild of await client.getGuildList()) {
457
+ for (const guild of await operation.run(() => client.getGuildList())) {
460
458
  if (guild?.id == null) continue;
461
459
  const guildId = String(guild.id);
462
460
  const guildName = String(guild.name ?? guildId);
463
- for (const channel of await client.getChannelList(guildId)) {
461
+ for (const channel of await operation.run(() => client.getChannelList(guildId))) {
464
462
  if (channel?.id == null) continue;
465
463
  if (channel.is_category) continue;
466
464
  if (!isKookTextChannelType(channel.type)) continue;
@@ -473,9 +471,10 @@ export function createKookEndpointManagement(
473
471
  }
474
472
  return channels;
475
473
  },
476
- async listGroupMembers(groupId: string): Promise<readonly unknown[]> {
474
+ async listGroupMembers({ groupId, signal }: EndpointGroupRequest): Promise<readonly unknown[]> {
475
+ const operation = createEndpointOperation({ signal });
477
476
  // 平台形状(User.Info[])原样返回
478
- return requireClient().getGuildUserList(groupId);
477
+ return operation.run(() => requireClient().getGuildUserList(groupId));
479
478
  },
480
479
  });
481
480
  }
package/src/index.ts CHANGED
@@ -27,14 +27,14 @@ export {
27
27
  } from './webhook.js';
28
28
 
29
29
  export {
30
- defaultCreateClient,
31
- defaultCreateWebhookClient,
30
+ kookWebsocketClientOptions,
31
+ kookWebhookClientOptions,
32
32
  normalizeKookMessage,
33
- type CreateKookClient,
34
33
  type KookClientTransport,
35
34
  } from './ws.js';
36
35
 
37
- export { kookClient, type KookClientEventMap } from './client.js';
36
+ export { type KookClientEventMap } from './client.js';
37
+ export { KookAdapter } from './adapter.js';
38
38
 
39
39
  export {
40
40
  checkKookPlatformPermit,