@zhin.js/adapter-discord 1.1.17 → 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.
Files changed (46) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +19 -2
  3. package/adapters/discord/index.js +3 -23
  4. package/adapters/discord/index.ts +2 -27
  5. package/agents/discord/skills/discord/tools/send_embed/index.js +1 -1
  6. package/agents/discord/skills/discord/tools/send_embed/index.ts +1 -1
  7. package/agents/discord/skills/discord-guild/tools/create_thread/index.js +6 -2
  8. package/agents/discord/skills/discord-guild/tools/create_thread/index.ts +5 -2
  9. package/agents/discord/skills/discord-guild/tools/forum_post/index.js +1 -1
  10. package/agents/discord/skills/discord-guild/tools/forum_post/index.ts +1 -1
  11. package/commands/discord/endpoint/definition.js +0 -2
  12. package/commands/discord/endpoint/definition.ts +0 -2
  13. package/lib/adapter.d.ts +14 -0
  14. package/lib/adapter.js +55 -0
  15. package/lib/client.d.ts +9 -9
  16. package/lib/client.js +0 -2
  17. package/lib/console-spec.d.ts +12 -0
  18. package/lib/console-spec.js +19 -0
  19. package/lib/console.d.ts +2 -0
  20. package/lib/console.js +13 -0
  21. package/lib/endpoint.d.ts +36 -32
  22. package/lib/endpoint.js +174 -170
  23. package/lib/gateway.d.ts +6 -124
  24. package/lib/gateway.js +88 -63
  25. package/lib/index.d.ts +4 -3
  26. package/lib/index.js +3 -2
  27. package/lib/protocol.d.ts +4 -0
  28. package/lib/protocol.js +1 -0
  29. package/lib/webhook.d.ts +2 -3
  30. package/lib/webhook.js +42 -30
  31. package/package.json +27 -19
  32. package/pages/index/index.js +15 -0
  33. package/pages/index/index.ts +16 -0
  34. package/plugin.js +3 -4
  35. package/src/adapter.ts +38 -0
  36. package/src/client.ts +6 -12
  37. package/src/console-spec.ts +24 -0
  38. package/src/console.ts +17 -0
  39. package/src/endpoint.ts +197 -172
  40. package/src/gateway.ts +103 -151
  41. package/src/index.ts +2 -3
  42. package/src/protocol.ts +5 -0
  43. package/src/webhook.ts +42 -32
  44. package/lib/discord-runtime-state.d.ts +0 -1
  45. package/lib/discord-runtime-state.js +0 -6
  46. package/src/discord-runtime-state.ts +0 -7
package/src/gateway.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import type { EndpointEventSink } from 'zhin.js/adapter';
1
2
  import { DiscordFaultProxyStrategy } from './gateway-fault-proxy.js';
2
- import { createReadStream, promises as fs } from 'node:fs';
3
+ import { promises as fs } from 'node:fs';
3
4
  import path from 'node:path';
4
5
  import {
5
6
  Client,
@@ -12,6 +13,7 @@ import {
12
13
  ButtonStyle,
13
14
  REST,
14
15
  type RESTOptions,
16
+ type ClientOptions,
15
17
  Routes,
16
18
  PermissionFlagsBits,
17
19
  type MessageCreateOptions,
@@ -38,85 +40,10 @@ export const DEFAULT_INTENTS = [
38
40
  GatewayIntentBits.GuildMessageReactions,
39
41
  ];
40
42
 
41
- /** Minimal client surface used by the endpoint (real discord.js or test mock). */
42
- export interface DiscordClientTransport {
43
- isReady?(): boolean;
44
- login(token: string): Promise<string>;
45
- destroy(): Promise<void>;
46
- on(event: string, listener: (...args: unknown[]) => void): void;
47
- once(event: string, listener: (...args: unknown[]) => void): void;
48
- removeAllListeners(): void;
49
- readonly user?: {
50
- readonly id: string;
51
- readonly tag?: string;
52
- setActivity?(name: string, options?: { type?: number; url?: string }): void;
53
- } | null;
54
- channels: {
55
- fetch(id: string): Promise<{
56
- id: string;
57
- type: number;
58
- isTextBased(): boolean;
59
- send?(options: MessageCreateOptions): Promise<{ id: string }>;
60
- messages?: {
61
- fetch(id: string): Promise<{
62
- delete(): Promise<unknown>;
63
- react(emoji: string): Promise<unknown>;
64
- reactions: {
65
- resolve(emoji: unknown): { users: { remove(userId: string): Promise<unknown> } } | null;
66
- cache: { find(fn: (r: { emoji: { toString(): string; name?: string | null; id?: string | null } }) => boolean): { users: { remove(userId: string): Promise<unknown> } } | undefined };
67
- };
68
- }>;
69
- };
70
- threads?: {
71
- create(options: Record<string, unknown>): Promise<{ id: string }>;
72
- };
73
- availableTags?: Array<{ id: string; name: string }>;
74
- } | null>;
75
- };
76
- guilds: {
77
- fetch(id: string): Promise<{
78
- id: string;
79
- name: string;
80
- ownerId: string;
81
- memberCount: number;
82
- createdAt?: Date | null;
83
- iconURL?(options?: { size?: number }): string | null;
84
- roles: {
85
- fetch(): Promise<unknown>;
86
- cache: Map<string, {
87
- id: string;
88
- name: string;
89
- hexColor: string;
90
- position: number;
91
- permissions: { bitfield: bigint };
92
- }> | { map(fn: (role: {
93
- id: string;
94
- name: string;
95
- hexColor: string;
96
- position: number;
97
- permissions: { bitfield: bigint };
98
- }) => unknown): unknown[] };
99
- };
100
- members: {
101
- fetch(userId: string | { limit?: number }): Promise<unknown>;
102
- ban(userId: string, options?: { reason?: string; deleteMessageSeconds?: number }): Promise<unknown>;
103
- unban(userId: string, reason?: string): Promise<unknown>;
104
- };
105
- }>;
106
- cache: { values(): IterableIterator<{
107
- id: string;
108
- name: string;
109
- channels: {
110
- cache: { values(): IterableIterator<{ id: string; name?: string; type: number }> };
111
- };
112
- }> };
113
- };
114
- }
115
-
116
- export type CreateDiscordClient = (intents: readonly number[], gatewayFaultProxyUrl?: string, restOptions?: Partial<RESTOptions>) => DiscordClientTransport;
117
-
118
- export function defaultCreateClient(intents: readonly number[], gatewayFaultProxyUrl?: string, restOptions: Partial<RESTOptions> = {}): DiscordClientTransport {
119
- return new Client({ rest: { ...restOptions, retries: 0, rejectOnRateLimit: () => true }, intents: [...intents], partials: [Partials.Channel], ...(gatewayFaultProxyUrl ? { ws: { buildStrategy: manager => new DiscordFaultProxyStrategy(manager, gatewayFaultProxyUrl) } } : {}) }) as unknown as DiscordClientTransport;
43
+ /** Native SDK surface; tests inject transport below the Client rather than replacing its identity. */
44
+ export type DiscordClientTransport = Client;
45
+ export function discordClientOptions(intents: readonly number[], gatewayFaultProxyUrl?: string, restOptions: Partial<RESTOptions> = {}): ClientOptions {
46
+ return { rest: { ...restOptions, retries: 0, rejectOnRateLimit: () => true }, intents: [...intents], partials: [Partials.Channel], ...(gatewayFaultProxyUrl ? { ws: { buildStrategy: manager => new DiscordFaultProxyStrategy(manager, gatewayFaultProxyUrl) } } : {}) };
120
47
  }
121
48
 
122
49
  export function resolveSenderRole(msg: DiscordInboundMessage): string | undefined {
@@ -155,6 +82,8 @@ export function normalizeDiscordMessage(raw: unknown): DiscordInboundMessage | n
155
82
 
156
83
  return {
157
84
  id: msg.id,
85
+ event: raw,
86
+ platformSentAt: msg.createdTimestamp,
158
87
  content: msg.content ?? '',
159
88
  channelId: msg.channel.id,
160
89
  channelKind: resolveChannelKind(msg.channel.type),
@@ -208,7 +137,7 @@ export async function toMessageCreateOptions(body: DiscordOutboundBody): Promise
208
137
  name: file.name || 'attachment',
209
138
  }));
210
139
  } else if (file.file && await fileExists(file.file)) {
211
- files.push(new AttachmentBuilder(createReadStream(file.file), {
140
+ files.push(new AttachmentBuilder(file.file, {
212
141
  name: file.name || path.basename(file.file),
213
142
  }));
214
143
  } else if (file.url) {
@@ -290,6 +219,7 @@ function decodeBase64(value: string): Buffer {
290
219
  }
291
220
 
292
221
  export interface DiscordGatewayConnectHandlers {
222
+ readonly receive: EndpointEventSink['run'];
293
223
  onPlatformEvent(name: string, event: unknown): void;
294
224
  onMessage(msg: DiscordInboundMessage): void;
295
225
  onButton(interaction: DiscordButtonInbound): void;
@@ -311,85 +241,96 @@ export async function connectDiscordGatewayClient(
311
241
  const releaseAbort = () => signal?.removeEventListener('abort', onAbort);
312
242
 
313
243
  client.on('messageCreate', (raw) => {
314
- handlers.onPlatformEvent('messageCreate', raw);
315
- const msg = normalizeDiscordMessage(raw);
316
- if (!msg) return;
317
- // clientReady 之后 client.user 一定可用;消息事件只会在此之后到达
318
- const botId = client.user?.id;
319
- const mentions = (raw as DiscordMessage).mentions;
320
- const mentionedBot = !!botId && mentions?.users?.has?.(botId) === true;
321
- handlers.onMessage(mentionedBot ? { ...msg, mentionedBot: true } : msg);
244
+ void handlers.receive(() => {
245
+ handlers.onPlatformEvent('messageCreate', raw);
246
+ const msg = normalizeDiscordMessage(raw);
247
+ if (!msg) return;
248
+ // clientReady 之后 client.user 一定可用;消息事件只会在此之后到达
249
+ const botId = client.user?.id;
250
+ const mentions = (raw as DiscordMessage).mentions;
251
+ const mentionedBot = !!botId && mentions?.users?.has?.(botId) === true;
252
+ handlers.onMessage(mentionedBot ? { ...msg, mentionedBot: true } : msg);
253
+ }).catch(() => logger.debug('Native callback was not admitted'));
322
254
  });
323
255
 
324
256
  client.on('interactionCreate', (raw) => {
325
- handlers.onPlatformEvent('interactionCreate', raw);
326
- const interaction = raw as {
327
- isButton?(): boolean;
328
- deferUpdate?(): Promise<unknown>;
329
- id: string;
330
- customId: string;
331
- channel?: { id: string; type: number } | null;
332
- channelId?: string;
333
- guildId?: string | null;
334
- user: { id: string; username?: string; displayName?: string };
335
- message?: { id: string };
336
- };
337
- const isButton = interaction.isButton?.() === true;
338
- logger.info(formatCompact({ op: 'discord_gateway_interaction_received', button: isButton, cachedChannel: !!interaction.channel, hasChannelId: !!interaction.channelId }));
339
- if (!isButton) return;
340
- void interaction.deferUpdate?.().then(() => {
341
- logger.info(formatCompact({ op: 'discord_gateway_button_ack', status: 'confirmed' }));
342
- }).catch(() => {
343
- // SDK errors may contain token-bearing callback URLs; log no raw error.
344
- logger.warn(formatCompact({ op: 'discord_gateway_button_ack', status: 'failed' }));
345
- });
346
- const channelId = interaction.channelId ?? interaction.channel?.id;
347
- if (!channelId) {
348
- logger.warn(formatCompact({ op: 'discord_gateway_button_dropped', reason: 'missing_channel_id' }));
349
- return;
350
- }
351
- handlers.onButton({
352
- id: interaction.id,
353
- customId: interaction.customId,
354
- channelId,
355
- channelKind: interaction.channel ? resolveChannelKind(interaction.channel.type) : interaction.guildId ? 'channel' : 'private',
356
- ...(interaction.guildId ? { guildId: interaction.guildId } : {}),
357
- userId: interaction.user.id,
358
- userName: interaction.user.username || interaction.user.displayName || interaction.user.id,
359
- sourceMessageId: interaction.message?.id,
360
- });
257
+ void handlers.receive(() => {
258
+ handlers.onPlatformEvent('interactionCreate', raw);
259
+ const interaction = raw as {
260
+ isButton?(): boolean;
261
+ deferUpdate?(): Promise<unknown>;
262
+ id: string;
263
+ customId: string;
264
+ channel?: { id: string; type: number } | null;
265
+ channelId?: string;
266
+ guildId?: string | null;
267
+ user: { id: string; username?: string; displayName?: string };
268
+ message?: { id: string };
269
+ };
270
+ const isButton = interaction.isButton?.() === true;
271
+ logger.info(formatCompact({ op: 'discord_gateway_interaction_received', button: isButton, cachedChannel: !!interaction.channel, hasChannelId: !!interaction.channelId }));
272
+ if (!isButton) return;
273
+ const ack = interaction.deferUpdate?.().then(() => {
274
+ logger.info(formatCompact({ op: 'discord_gateway_button_ack', status: 'confirmed' }));
275
+ }).catch(() => {
276
+ // SDK errors may contain token-bearing callback URLs; log no raw error.
277
+ logger.warn(formatCompact({ op: 'discord_gateway_button_ack', status: 'failed' }));
278
+ });
279
+ const channelId = interaction.channelId ?? interaction.channel?.id;
280
+ if (!channelId) {
281
+ logger.warn(formatCompact({ op: 'discord_gateway_button_dropped', reason: 'missing_channel_id' }));
282
+ return ack ? awaitOwnedAck(ack, signal) : undefined;
283
+ }
284
+ handlers.onButton({
285
+ event: raw,
286
+ platformSentAt: raw.createdTimestamp,
287
+ id: interaction.id,
288
+ customId: interaction.customId,
289
+ channelId,
290
+ channelKind: interaction.channel ? resolveChannelKind(interaction.channel.type) : interaction.guildId ? 'channel' : 'private',
291
+ ...(interaction.guildId ? { guildId: interaction.guildId } : {}),
292
+ userId: interaction.user.id,
293
+ userName: interaction.user.username || interaction.user.displayName || interaction.user.id,
294
+ sourceMessageId: interaction.message?.id,
295
+ });
296
+ return ack ? awaitOwnedAck(ack, signal) : undefined;
297
+ }).catch(() => logger.debug('Native callback was not admitted'));
361
298
  });
362
299
 
363
300
  client.on('guildMemberAdd', (raw) => {
364
- handlers.onPlatformEvent('guildMemberAdd', raw);
365
- const member = raw as {
366
- guild?: { id?: string };
367
- user?: { id?: string; username?: string; displayName?: string };
368
- };
369
- const guildId = member.guild?.id;
370
- const userId = member.user?.id;
371
- if (!guildId || !userId) return;
372
- handlers.onGuildMemberAdd?.({
373
- guildId,
374
- userId,
375
- userName: member.user?.username || member.user?.displayName,
376
- });
301
+ void handlers.receive(() => {
302
+ handlers.onPlatformEvent('guildMemberAdd', raw);
303
+ const member = raw as {
304
+ guild?: { id?: string };
305
+ user?: { id?: string; username?: string; displayName?: string };
306
+ };
307
+ const guildId = member.guild?.id;
308
+ const userId = member.user?.id;
309
+ if (!guildId || !userId) return;
310
+ handlers.onGuildMemberAdd?.({
311
+ guildId,
312
+ userId,
313
+ userName: member.user?.username || member.user?.displayName,
314
+ });
315
+ }).catch(() => logger.debug('Native callback was not admitted'));
377
316
  });
378
317
 
379
318
  client.on('guildMemberRemove', (raw) => {
380
- handlers.onPlatformEvent('guildMemberRemove', raw);
381
- const member = raw as {
382
- guild?: { id?: string };
383
- user?: { id?: string; username?: string; displayName?: string };
384
- };
385
- const guildId = member.guild?.id;
386
- const userId = member.user?.id;
387
- if (!guildId || !userId) return;
388
- handlers.onGuildMemberRemove?.({
389
- guildId,
390
- userId,
391
- userName: member.user?.username || member.user?.displayName,
392
- });
319
+ void handlers.receive(() => {
320
+ handlers.onPlatformEvent('guildMemberRemove', raw);
321
+ const member = raw as {
322
+ guild?: { id?: string };
323
+ user?: { id?: string; username?: string; displayName?: string };
324
+ };
325
+ const guildId = member.guild?.id;
326
+ const userId = member.user?.id;
327
+ if (!guildId || !userId) return;
328
+ handlers.onGuildMemberRemove?.({
329
+ guildId,
330
+ userId,
331
+ userName: member.user?.username || member.user?.displayName,
332
+ });
333
+ }).catch(() => logger.debug('Native callback was not admitted'));
393
334
  });
394
335
 
395
336
  client.once('clientReady', () => {
@@ -455,3 +396,14 @@ export async function connectDiscordGatewayClient(
455
396
  });
456
397
  });
457
398
  }
399
+
400
+ /** Logical cancellation only; the native SDK still owns its already-started ACK. */
401
+ function awaitOwnedAck<T>(operation: Promise<T>, signal?: AbortSignal): Promise<T> {
402
+ if (!signal) return operation;
403
+ return new Promise((resolve, reject) => {
404
+ const abort = () => reject(signal.reason);
405
+ signal.addEventListener('abort', abort, { once: true });
406
+ void operation.then(resolve, reject).finally(() => signal.removeEventListener('abort', abort));
407
+ if (signal.aborted) abort();
408
+ });
409
+ }
package/src/index.ts CHANGED
@@ -19,7 +19,6 @@ export {
19
19
  } from './protocol.js';
20
20
 
21
21
  export {
22
- discordClient,
23
22
  requireDiscordGatewayClient,
24
23
  type DiscordClient,
25
24
  type DiscordClientEventMap,
@@ -35,7 +34,6 @@ export {
35
34
  export {
36
35
  DiscordGatewayEndpoint,
37
36
  DiscordInteractionsEndpoint,
38
- type CreateDiscordClient,
39
37
  type DiscordClientTransport,
40
38
  type DiscordEndpointOptions,
41
39
  type DiscordInteractionsEndpointOptions,
@@ -43,7 +41,7 @@ export {
43
41
 
44
42
  export {
45
43
  connectDiscordGatewayClient,
46
- defaultCreateClient,
44
+ discordClientOptions,
47
45
  normalizeDiscordMessage,
48
46
  resolveSenderRole,
49
47
  toMessageCreateOptions,
@@ -55,3 +53,4 @@ export {
55
53
  registerDiscordInteractionRoutes,
56
54
  type DiscordInteractionsHandler,
57
55
  } from './webhook.js';
56
+ export { DiscordAdapter } from './adapter.js';
package/src/protocol.ts CHANGED
@@ -72,6 +72,8 @@ export interface DiscordInboundAttachment {
72
72
  }
73
73
 
74
74
  export interface DiscordInboundMessage {
75
+ readonly event?: unknown;
76
+ readonly platformSentAt?: number;
75
77
  readonly id: string;
76
78
  readonly content: string;
77
79
  readonly channelId: string;
@@ -92,6 +94,8 @@ export interface DiscordInboundMessage {
92
94
  }
93
95
 
94
96
  export interface DiscordButtonInbound {
97
+ readonly event?: unknown;
98
+ readonly platformSentAt?: number;
95
99
  readonly guildId?: string;
96
100
  readonly id: string;
97
101
  readonly customId: string;
@@ -491,6 +495,7 @@ export function interactionToInboundMessage(interaction: Record<string, unknown>
491
495
  const user = (interaction.member as { user?: Record<string, unknown> } | undefined)?.user
492
496
  ?? (interaction.user as Record<string, unknown> | undefined);
493
497
  return {
498
+ event: interaction,
494
499
  id: String(interaction.id),
495
500
  content: formatSlashCommandContent(interaction),
496
501
  channelId: String(interaction.channel_id ?? ''),
package/src/webhook.ts CHANGED
@@ -1,8 +1,9 @@
1
+ import { endpointSpec, type Endpoint } from 'zhin.js/adapter';
1
2
  /**
2
3
  * Discord interactions HTTP: signature → parse → admit.
3
4
  */
4
5
  import type { IncomingMessage, ServerResponse } from 'node:http';
5
- import { createMessages, type Locale } from 'zhin.js/i18n';
6
+ import { createMessages } from 'zhin.js/i18n';
6
7
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
7
8
  import { formatCompact, getLogger } from '@zhin.js/logger';
8
9
  import {
@@ -25,11 +26,10 @@ const INTERACTION_RESPONSE_CHANNEL_MESSAGE_WITH_SOURCE = 4;
25
26
  /** EPHEHEMERAL — 仅发起者可见(对齐旧 endpoint-interactions 默认响应)。 */
26
27
  const INTERACTION_FLAG_EPHEMERAL = 64;
27
28
 
28
- export interface DiscordInteractionsHandler {
29
+ export interface DiscordInteractionsHandler extends Endpoint {
29
30
  readonly config: ResolvedDiscordInteractionsConfig;
30
31
  readonly isOpen: boolean;
31
32
  admit(msg: DiscordInboundMessage): void;
32
- resolveLocale?(senderId: string): Promise<Locale | undefined>;
33
33
  admitButton(interaction: DiscordButtonInbound): void;
34
34
  admitPlatform(event: Record<string, unknown>): void;
35
35
  }
@@ -71,42 +71,52 @@ export async function handleDiscordInteractionRequest(
71
71
  return;
72
72
  }
73
73
  const interaction = JSON.parse(rawBody) as Record<string, unknown>;
74
- if (handler.isOpen) handler.admitPlatform(interaction);
74
+ if (!interaction || interaction.application_id !== handler.config.applicationId) {
75
+ response.writeHead(401, { 'Content-Type': 'text/plain' });
76
+ response.end('Unauthorized');
77
+ return;
78
+ }
79
+ // A verified protocol challenge is not a business message and keeps its immediate ACK.
75
80
  if (interaction.type === INTERACTION_TYPE_PING) {
81
+ if (handler.isOpen) void handler[endpointSpec].events.run(() => handler.admitPlatform(interaction))
82
+ .catch(() => logger.debug('Protocol event dispatch did not complete'));
76
83
  writeJson(response, 200, { type: INTERACTION_RESPONSE_PONG });
77
84
  return;
78
85
  }
79
- if (interaction.type === 3) {
80
- const data = interaction.data as { component_type?: number; custom_id?: string } | undefined;
81
- if (data?.component_type === 2 && typeof data.custom_id === 'string') {
82
- if (!handler.isOpen) { response.writeHead(503); response.end('Unavailable'); return; }
86
+ await handler[endpointSpec].events.run(async () => {
87
+ if (handler.isOpen) handler.admitPlatform(interaction);
88
+ if (interaction.type === 3) {
89
+ const data = interaction.data as { component_type?: number; custom_id?: string } | undefined;
90
+ if (data?.component_type === 2 && typeof data.custom_id === 'string') {
91
+ if (!handler.isOpen) { response.writeHead(503); response.end('Unavailable'); return; }
92
+ const message = interactionToInboundMessage(interaction);
93
+ handler.admitButton({ event: interaction, id: message.id, customId: data.custom_id, channelId: message.channelId, channelKind: message.channelKind, userId: message.authorId, userName: message.authorName, ...(message.guildId ? { guildId: message.guildId } : {}), sourceMessageId: (interaction.message as { id?: string } | undefined)?.id });
94
+ writeJson(response, 200, { type: 6 });
95
+ return;
96
+ }
97
+ }
98
+ if (interaction.type === INTERACTION_TYPE_APPLICATION_COMMAND) {
83
99
  const message = interactionToInboundMessage(interaction);
84
- handler.admitButton({ id: message.id, customId: data.custom_id, channelId: message.channelId, channelKind: message.channelKind, userId: message.authorId, userName: message.authorName, ...(message.guildId ? { guildId: message.guildId } : {}), sourceMessageId: (interaction.message as { id?: string } | undefined)?.id });
85
- writeJson(response, 200, { type: 6 });
100
+ if (handler.isOpen) {
101
+ handler.admit(message);
102
+ }
103
+ const locale = await handler[endpointSpec].resolveLocale(message.authorId);
104
+ // 即时响应(type 4):defer(type 5) 需要 followup PATCH,未实现会让用户端一直转圈
105
+ const commandName = String(
106
+ (interaction.data as { name?: unknown } | undefined)?.name ?? '',
107
+ );
108
+ writeJson(response, 200, {
109
+ type: INTERACTION_RESPONSE_CHANNEL_MESSAGE_WITH_SOURCE,
110
+ data: {
111
+ content: messages(locale, 'processing', { command: commandName }),
112
+ flags: INTERACTION_FLAG_EPHEMERAL,
113
+ },
114
+ });
86
115
  return;
87
116
  }
88
- }
89
- if (interaction.type === INTERACTION_TYPE_APPLICATION_COMMAND) {
90
- const message = interactionToInboundMessage(interaction);
91
- if (handler.isOpen) {
92
- handler.admit(message);
93
- }
94
- const locale = await handler.resolveLocale?.(message.authorId);
95
- // 即时响应(type 4):defer(type 5) 需要 followup PATCH,未实现会让用户端一直转圈
96
- const commandName = String(
97
- (interaction.data as { name?: unknown } | undefined)?.name ?? '',
98
- );
99
- writeJson(response, 200, {
100
- type: INTERACTION_RESPONSE_CHANNEL_MESSAGE_WITH_SOURCE,
101
- data: {
102
- content: messages(locale, 'processing', { command: commandName }),
103
- flags: INTERACTION_FLAG_EPHEMERAL,
104
- },
105
- });
106
- return;
107
- }
108
- response.writeHead(400, { 'Content-Type': 'text/plain' });
109
- response.end('Unsupported interaction type');
117
+ response.writeHead(400, { 'Content-Type': 'text/plain' });
118
+ response.end('Unsupported interaction type');
119
+ });
110
120
  } catch (error) {
111
121
  logger.error('Discord interactions error:', error);
112
122
  if (!response.headersSent) {
@@ -1 +0,0 @@
1
- export declare const discordRuntimeStateToken: import("zhin.js").Token<import("@zhin.js/adapter").EndpointRuntimeState>;
@@ -1,6 +0,0 @@
1
- /**
2
- * Discord 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
- * 由 plugin.ts setup() provide,adapter create 与 `discord endpoint` 命令共享(同一 owner generation)。
4
- */
5
- import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
6
- export const discordRuntimeStateToken = defineEndpointRuntimeStateToken('discord');
@@ -1,7 +0,0 @@
1
- /**
2
- * Discord 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
- * 由 plugin.ts setup() provide,adapter create 与 `discord endpoint` 命令共享(同一 owner generation)。
4
- */
5
- import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
6
-
7
- export const discordRuntimeStateToken = defineEndpointRuntimeStateToken('discord');