@zhin.js/adapter-kook 1.1.18 → 1.1.20
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 +46 -0
- package/README.md +11 -4
- package/adapters/kook/index.js +3 -23
- package/adapters/kook/index.ts +3 -27
- package/commands/kook/endpoint/definition.js +0 -2
- package/commands/kook/endpoint/definition.ts +0 -2
- package/lib/adapter.d.ts +14 -0
- package/lib/adapter.js +44 -0
- package/lib/client.d.ts +7 -8
- package/lib/client.js +1 -2
- package/lib/console-spec.js +10 -0
- package/lib/console.d.ts +1 -7
- package/lib/console.js +2 -5
- package/lib/endpoint.d.ts +10 -24
- package/lib/endpoint.js +160 -159
- package/lib/index.d.ts +3 -2
- package/lib/index.js +2 -2
- package/lib/outbound.d.ts +3 -4
- package/lib/outbound.js +15 -11
- package/lib/protocol.d.ts +4 -0
- package/lib/protocol.js +3 -1
- package/lib/webhook.d.ts +3 -2
- package/lib/webhook.js +24 -21
- package/lib/ws.d.ts +4 -66
- package/lib/ws.js +17 -8
- package/package.json +18 -18
- package/plugin.js +1 -4
- package/src/adapter.ts +35 -0
- package/src/client.ts +4 -11
- package/src/console-spec.ts +10 -0
- package/src/console.ts +2 -6
- package/src/endpoint.ts +162 -163
- package/src/index.ts +4 -4
- package/src/outbound.ts +18 -14
- package/src/protocol.ts +6 -2
- package/src/webhook.ts +25 -22
- package/src/ws.ts +13 -64
- package/lib/kook-runtime-state.d.ts +0 -1
- package/lib/kook-runtime-state.js +0 -6
- package/src/kook-runtime-state.ts +0 -7
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
|
-
|
|
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
|
-
|
|
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
|
|
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 #
|
|
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:
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
113
|
+
return this.getTransportState?.();
|
|
108
114
|
}
|
|
109
115
|
|
|
110
|
-
async
|
|
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.#
|
|
115
|
-
this
|
|
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
|
|
128
|
+
self_id: this.self_id != null ? String(this.self_id) : undefined,
|
|
122
129
|
}));
|
|
123
130
|
} catch (error) {
|
|
124
|
-
await this
|
|
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
|
-
|
|
137
|
+
#openFramework(): void {
|
|
131
138
|
this.#open = true;
|
|
132
139
|
}
|
|
133
140
|
|
|
134
|
-
|
|
141
|
+
#closeFramework(): void {
|
|
135
142
|
this.#open = false;
|
|
136
143
|
}
|
|
137
144
|
|
|
138
|
-
async
|
|
145
|
+
async #stopFramework(): Promise<void> {
|
|
139
146
|
this.#open = false;
|
|
140
147
|
this.#sentConversations.clear();
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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.
|
|
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.#
|
|
226
|
-
return this
|
|
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
|
|
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 #
|
|
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:
|
|
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
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
277
|
+
get endpointConfig(): ResolvedKookWebhookConfig {
|
|
271
278
|
return this.#options.config;
|
|
272
279
|
}
|
|
273
280
|
|
|
274
281
|
get selfId(): string | undefined {
|
|
275
|
-
return this
|
|
282
|
+
return this.self_id != null ? String(this.self_id) : undefined;
|
|
276
283
|
}
|
|
277
284
|
|
|
278
|
-
async
|
|
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
|
-
|
|
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
|
|
298
|
+
self_id: this.self_id != null ? String(this.self_id) : undefined,
|
|
291
299
|
}));
|
|
292
300
|
} catch (error) {
|
|
293
|
-
await this
|
|
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
|
-
|
|
307
|
+
#openFramework(): void {
|
|
300
308
|
this.#open = true;
|
|
301
309
|
}
|
|
302
310
|
|
|
303
|
-
|
|
311
|
+
#closeFramework(): void {
|
|
304
312
|
this.#open = false;
|
|
305
313
|
}
|
|
306
314
|
|
|
307
|
-
async
|
|
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
|
-
|
|
313
|
-
|
|
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
|
|
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
|
-
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
this
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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.#
|
|
401
|
-
return this
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
31
|
-
|
|
30
|
+
kookWebsocketClientOptions,
|
|
31
|
+
kookWebhookClientOptions,
|
|
32
32
|
normalizeKookMessage,
|
|
33
|
-
type CreateKookClient,
|
|
34
33
|
type KookClientTransport,
|
|
35
34
|
} from './ws.js';
|
|
36
35
|
|
|
37
|
-
export {
|
|
36
|
+
export { type KookClientEventMap } from './client.js';
|
|
37
|
+
export { KookAdapter } from './adapter.js';
|
|
38
38
|
|
|
39
39
|
export {
|
|
40
40
|
checkKookPlatformPermit,
|