@zhin.js/adapter-discord 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/CHANGELOG.md +39 -0
- package/README.md +10 -3
- package/adapters/discord/index.js +3 -23
- package/adapters/discord/index.ts +2 -27
- package/agents/discord/skills/discord/tools/send_embed/index.js +1 -1
- package/agents/discord/skills/discord/tools/send_embed/index.ts +1 -1
- package/agents/discord/skills/discord-guild/tools/create_thread/index.js +6 -2
- package/agents/discord/skills/discord-guild/tools/create_thread/index.ts +5 -2
- package/agents/discord/skills/discord-guild/tools/forum_post/index.js +1 -1
- package/agents/discord/skills/discord-guild/tools/forum_post/index.ts +1 -1
- package/commands/discord/endpoint/definition.js +0 -2
- package/commands/discord/endpoint/definition.ts +0 -2
- package/lib/adapter.d.ts +14 -0
- package/lib/adapter.js +55 -0
- package/lib/client.d.ts +9 -9
- package/lib/client.js +0 -2
- package/lib/console-spec.js +9 -0
- package/lib/console.d.ts +1 -7
- package/lib/console.js +2 -5
- package/lib/endpoint.d.ts +36 -32
- package/lib/endpoint.js +174 -170
- package/lib/gateway.d.ts +6 -124
- package/lib/gateway.js +88 -63
- package/lib/index.d.ts +4 -3
- package/lib/index.js +3 -2
- package/lib/protocol.d.ts +4 -0
- package/lib/protocol.js +1 -0
- package/lib/webhook.d.ts +2 -3
- package/lib/webhook.js +42 -30
- package/package.json +18 -18
- package/plugin.js +1 -4
- package/src/adapter.ts +38 -0
- package/src/client.ts +6 -12
- package/src/console-spec.ts +9 -0
- package/src/console.ts +2 -6
- package/src/endpoint.ts +197 -172
- package/src/gateway.ts +103 -151
- package/src/index.ts +2 -3
- package/src/protocol.ts +5 -0
- package/src/webhook.ts +42 -32
- package/lib/discord-runtime-state.d.ts +0 -1
- package/lib/discord-runtime-state.js +0 -6
- 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 {
|
|
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
|
-
/**
|
|
42
|
-
export
|
|
43
|
-
|
|
44
|
-
|
|
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(
|
|
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.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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');
|