@seedcord/gateway 0.8.0 → 0.9.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.d.mts +212 -236
- package/dist/index.mjs +544 -993
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -14
package/dist/index.mjs
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
import { DRAIN_TASK_TIMEOUT_MS, DRAIN_WINDOW_MS, HealthCheck, InFlight, ShutdownPhase, StartupPhase, setEnvaptBaseDir, shutdownOf } from "@seedcord/core/node/internal";
|
|
3
3
|
import { WinstonConsoleSink, WinstonFileSink, installNodeConsole } from "@seedcord/logger/node";
|
|
4
|
-
import { COMMAND_DEPLOY_TIMEOUT_MS, CommandRegistry, EventMetadataKey, EventMiddlewareMetadataKey,
|
|
4
|
+
import { AutocompleteResponder, COMMAND_DEPLOY_TIMEOUT_MS, CommandRegistry, DispatchContext, EventMetadataKey, EventMiddlewareMetadataKey, FaultBoundary, FixedScope, GatedMetadataKey, HandlerLoader, HmrManager, InteractionMetadataKey, InteractionMiddlewareMetadataKey, InteractionRun, MiddlewareRegistry, NoticeCard, OnceTracker, PublishDefault, RouteRegistry, SubscriberLoader, accessorStore, assertUniqueKeys, attachmentsOf, bindBotColor, busLoggerOf, clearStore, componentKindOf, connectDevChannel, decodeComponentRoute, deferFlags, eventResultFor, guardedAccessor, interactionFaultSource, interactionMiddleware, isEmojiTuple, matchRoute, readBotToken, resultFor, runAfter, runCase, runHandlerGates, sendFlags } from "@seedcord/core/internal";
|
|
5
5
|
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
6
6
|
import { SeedcordError, SeedcordTypeError, throwSingleOrAggregate } from "@seedcord/errors/internal";
|
|
7
|
-
import { ArraySourceBase, BaseHandler as BaseHandler$1, BaseReplySender, BuilderComponent,
|
|
7
|
+
import { ArraySourceBase, BaseHandler as BaseHandler$1, BaseReplySender, BuilderComponent, CursorSourceBase, CustomId, InteractionKind, Notice, PaginatorBase, RepliableHandler as RepliableHandler$1, ResolvedEmoji, RowComponent, Silence, Subscriber as Subscriber$1, WebhookLog as WebhookLog$1, assertPermissions, defineGate } from "@seedcord/core";
|
|
8
|
+
import { ButtonStyle, Client, ComponentType, DiscordAPIError, Events, FormattingPatterns, GatewayIntentBits, Guild, GuildMember, IntentsBitField, InteractionContextType, Message, MessageFlags, PermissionFlagsBits, Role, User } from "discord.js";
|
|
8
9
|
import { Logger } from "@seedcord/logger";
|
|
9
|
-
import { ButtonStyle, Client, ComponentType, DiscordAPIError, Events, GatewayIntentBits, Guild, GuildMember, IntentsBitField, InteractionContextType, Message, MessageFlags, PermissionFlagsBits, RESTJSONErrorCodes, Role, User } from "discord.js";
|
|
10
10
|
import { ButtonBuilder, TextDisplayBuilder } from "@discordjs/builders";
|
|
11
|
-
import { asError, buildSlashRoute, isStringArray } from "@seedcord/utils/internal";
|
|
12
11
|
import { Plugin as Plugin$1 } from "@seedcord/core/plugin";
|
|
13
12
|
import { CoordinatedShutdown, CoordinatedStartup, ServerHost } from "@seedcord/core/node";
|
|
14
|
-
import { MemoryRateLimiter } from "@seedcord/rate-limiter";
|
|
15
13
|
import { HostAugmentTarget, HostVersion, SeedcordBrand } from "@seedcord/types/internal";
|
|
16
14
|
import { Envapter } from "envapt";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import * as crypto from "node:crypto";
|
|
15
|
+
import { hasKeys } from "@seedcord/utils";
|
|
16
|
+
import { asError, buildSlashRoute } from "@seedcord/utils/internal";
|
|
20
17
|
import { prefixOf } from "@seedcord/custom-id";
|
|
18
|
+
import { HmrModuleHandler } from "@seedcord/core/hmr";
|
|
21
19
|
|
|
22
20
|
export * from "@seedcord/errors"
|
|
23
21
|
|
|
@@ -25,8 +23,6 @@ export * from "@seedcord/core"
|
|
|
25
23
|
|
|
26
24
|
export * from "@seedcord/logger"
|
|
27
25
|
|
|
28
|
-
export * from "@seedcord/rate-limiter"
|
|
29
|
-
|
|
30
26
|
export * from "@seedcord/utils"
|
|
31
27
|
|
|
32
28
|
export * from "@seedcord/event-emitter"
|
|
@@ -82,12 +78,12 @@ installNodeConsole();
|
|
|
82
78
|
*/
|
|
83
79
|
function RegisterEvent(...defs) {
|
|
84
80
|
return function(constructor) {
|
|
85
|
-
const existing = Reflect.getMetadata(EventMetadataKey, constructor) ?? [];
|
|
86
81
|
const entries = defs.map(([event, options]) => ({
|
|
87
82
|
event,
|
|
88
83
|
frequency: options?.frequency ?? "on"
|
|
89
84
|
}));
|
|
90
|
-
|
|
85
|
+
assertUniqueKeys("@RegisterEvent", constructor, entries.map((entry) => entry.event));
|
|
86
|
+
Reflect.defineMetadata(EventMetadataKey, entries, constructor);
|
|
91
87
|
};
|
|
92
88
|
}
|
|
93
89
|
|
|
@@ -97,10 +93,10 @@ function RegisterEvent(...defs) {
|
|
|
97
93
|
* Attaches gates to a handler. The gates run before `execute`, and a gate refusing stops the handler with
|
|
98
94
|
* the reply or drop the gate threw. Multiple gates are ANDed, so each must pass. A gate that requires a
|
|
99
95
|
* context the handler does not provide (a button gate on a slash handler, an interaction gate on an event
|
|
100
|
-
* handler) is a compile error at this line, and the error
|
|
96
|
+
* handler) is a compile error at this line, and the error message shows the gate and the handler kind. Place it
|
|
101
97
|
* above the route decorator.
|
|
102
98
|
*
|
|
103
|
-
* Combine gates with {@link and} or {@link or}
|
|
99
|
+
* Combine gates into a single gate with {@link and} or {@link or}.
|
|
104
100
|
*
|
|
105
101
|
* @example
|
|
106
102
|
* ```ts
|
|
@@ -193,19 +189,55 @@ const IgnoreBots = defineGate("IgnoreBots", (ctx) => {
|
|
|
193
189
|
if (ctx.user?.bot) throw new Silence("actor is a bot");
|
|
194
190
|
});
|
|
195
191
|
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/bot/gates/catalog/MentionPrefix.ts
|
|
194
|
+
function startsWithBotMention(message) {
|
|
195
|
+
const match = FormattingPatterns.User.exec(message.content?.trimStart() ?? "");
|
|
196
|
+
return match?.index === 0 && match.groups?.id === message.client.user.id;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Passes only a message that starts with a mention of the bot, like `@bot ping`. It ignores every other message
|
|
200
|
+
* with a {@link Silence}, including a reply that pings the bot, a role mention, and a mention later in the text.
|
|
201
|
+
*
|
|
202
|
+
* On `messageUpdate` it passes only when the edit adds the mention. An edit that keeps the mention doesn't run the
|
|
203
|
+
* handler again. With `Partials.Message` on, discord.js also emits an edit to a message it didn't cache. That edit
|
|
204
|
+
* runs the handler again. Attach the gate without calling it, since it doesn't take options.
|
|
205
|
+
*
|
|
206
|
+
* A handler behind this gate can read `content` without the Message Content intent, because Discord sends the text of
|
|
207
|
+
* a message that mentions the bot.
|
|
208
|
+
*
|
|
209
|
+
* @see {@link Gated}
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```ts
|
|
213
|
+
* import { Events } from 'discord.js';
|
|
214
|
+
*
|
|
215
|
+
* \@Gated(IgnoreBots, MentionPrefix)
|
|
216
|
+
* \@RegisterEvent([Events.MessageCreate], [Events.MessageUpdate])
|
|
217
|
+
* class MentionCommands extends EventHandler<Events.MessageCreate | Events.MessageUpdate> {
|
|
218
|
+
* async execute() {
|
|
219
|
+
* // ...
|
|
220
|
+
* }
|
|
221
|
+
* }
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
const MentionPrefix = defineGate("MentionPrefix", ({ payload }) => {
|
|
225
|
+
if (!(payload.length === 1 ? startsWithBotMention(payload[0]) : !startsWithBotMention(payload[0]) && startsWithBotMention(payload[1]))) throw new Silence("message does not start with a mention of the bot");
|
|
226
|
+
});
|
|
227
|
+
|
|
196
228
|
//#endregion
|
|
197
229
|
//#region src/bot/notices/index.ts
|
|
198
|
-
var NotNsfw = class extends
|
|
199
|
-
constructor(
|
|
200
|
-
super(
|
|
230
|
+
var NotNsfw = class extends Notice {
|
|
231
|
+
constructor() {
|
|
232
|
+
super("This can only be used in an age-restricted channel.");
|
|
201
233
|
this.summary = "be in an age-restricted channel";
|
|
202
234
|
}
|
|
203
235
|
};
|
|
204
236
|
var RoleHigherThanMe = class extends Notice {
|
|
205
237
|
role;
|
|
206
238
|
botRole;
|
|
207
|
-
constructor(
|
|
208
|
-
super(
|
|
239
|
+
constructor(role, botRole) {
|
|
240
|
+
super("Role is higher than me");
|
|
209
241
|
this.role = role;
|
|
210
242
|
this.botRole = botRole;
|
|
211
243
|
}
|
|
@@ -214,11 +246,13 @@ var RoleHigherThanMe = class extends Notice {
|
|
|
214
246
|
}
|
|
215
247
|
};
|
|
216
248
|
var CannotAssignBotRole = class extends Notice {
|
|
217
|
-
|
|
218
|
-
|
|
249
|
+
role;
|
|
250
|
+
constructor(role) {
|
|
251
|
+
super(`Cannot assign managed role ${role.name}`);
|
|
252
|
+
this.role = role;
|
|
219
253
|
}
|
|
220
254
|
render() {
|
|
221
|
-
return { components: [new NoticeCard(
|
|
255
|
+
return { components: [new NoticeCard(`I cannot assign <@&${this.role.id}>, a managed role.`).component] };
|
|
222
256
|
}
|
|
223
257
|
};
|
|
224
258
|
|
|
@@ -230,11 +264,11 @@ function channelIsNsfw(channel) {
|
|
|
230
264
|
return "nsfw" in channel ? channel.nsfw : false;
|
|
231
265
|
}
|
|
232
266
|
/**
|
|
233
|
-
* Requires an age-restricted channel, else refuses. Pass
|
|
267
|
+
* Requires an age-restricted channel, else refuses. Pass a Notice class as `notice` to replace the refusal.
|
|
234
268
|
*
|
|
235
269
|
* A thread inherits its parent channel's nsfw flag. ModalSubmit has no reliable channel, so it is excluded.
|
|
236
270
|
*
|
|
237
|
-
* @param options -
|
|
271
|
+
* @param options - Replace the refusal with a Notice class as `notice`.
|
|
238
272
|
*
|
|
239
273
|
* @see {@link Gated}
|
|
240
274
|
*
|
|
@@ -252,7 +286,7 @@ function channelIsNsfw(channel) {
|
|
|
252
286
|
function Nsfw(options) {
|
|
253
287
|
return defineGate("Nsfw", (ctx) => {
|
|
254
288
|
if (channelIsNsfw(ctx.interaction.channel)) return;
|
|
255
|
-
throw
|
|
289
|
+
throw new ((options?.notice) ?? NotNsfw)();
|
|
256
290
|
});
|
|
257
291
|
}
|
|
258
292
|
|
|
@@ -353,57 +387,68 @@ function mentionFor(subject) {
|
|
|
353
387
|
|
|
354
388
|
//#endregion
|
|
355
389
|
//#region src/bot/utilities/permissions/checkPermissions.ts
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
390
|
+
/**
|
|
391
|
+
* Checks a {@link Role} or a {@link GuildMember} against the base permission set from roles, or against a
|
|
392
|
+
* channel's set when `in` is given. Refuses when a permission in `scope` is missing, or with `inverse` when
|
|
393
|
+
* one is present.
|
|
394
|
+
*
|
|
395
|
+
* @throws A {@link Notice} when the check refuses. It lists the missing or present permissions.
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* ```ts
|
|
399
|
+
* import { PermissionFlagsBits } from 'discord.js';
|
|
400
|
+
* import { checkPermissions } from '@seedcord/gateway';
|
|
401
|
+
*
|
|
402
|
+
* checkPermissions({ for: member, scope: [PermissionFlagsBits.KickMembers] });
|
|
403
|
+
* checkPermissions({ for: role, in: channel, scope: [PermissionFlagsBits.SendMessages] });
|
|
404
|
+
* ```
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* ```ts
|
|
408
|
+
* // refuse when the member holds Administrator, with your own Notice class
|
|
409
|
+
* checkPermissions({ for: member, scope: [PermissionFlagsBits.Administrator], inverse: true, notice: TooPowerful });
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
function checkPermissions({ for: target, in: channel, scope, inverse = false, notice }) {
|
|
413
|
+
const held = channel ? channel.permissionsFor(target, true) : target.permissions;
|
|
365
414
|
assertPermissions({
|
|
366
|
-
subject: mentionFor(
|
|
367
|
-
permissions:
|
|
415
|
+
subject: mentionFor(target),
|
|
416
|
+
permissions: held.bitfield,
|
|
368
417
|
scope,
|
|
369
418
|
inverse,
|
|
370
|
-
|
|
371
|
-
...dangerousNotice && { dangerousNotice }
|
|
419
|
+
notice
|
|
372
420
|
});
|
|
373
421
|
}
|
|
374
422
|
|
|
375
423
|
//#endregion
|
|
376
424
|
//#region src/bot/utilities/permissions/checkBotPermissions.ts
|
|
425
|
+
function botMemberOf(guild) {
|
|
426
|
+
const me = guild.members.me;
|
|
427
|
+
if (!me) throw new SeedcordError(SeedcordErrorCode.CoreBotMemberMissing, [guild.id]);
|
|
428
|
+
return me;
|
|
429
|
+
}
|
|
377
430
|
/**
|
|
378
|
-
* Checks the bot's own permissions in a {@link Guild} or
|
|
379
|
-
* `scope` is missing, or with `inverse` when one is present.
|
|
380
|
-
*
|
|
431
|
+
* Checks the bot's own permissions in a {@link Guild}, or in a channel or thread. Refuses when a permission
|
|
432
|
+
* in `scope` is missing, or with `inverse` when one is present.
|
|
433
|
+
*
|
|
434
|
+
* @throws A {@link Notice} when the check refuses. It lists the missing or present permissions.
|
|
435
|
+
* @throws A **SeedcordError** when the guild's member cache doesn't hold the bot's member.
|
|
381
436
|
*
|
|
382
437
|
* @example
|
|
383
438
|
* ```ts
|
|
384
439
|
* import { PermissionFlagsBits } from 'discord.js';
|
|
385
440
|
* import { checkBotPermissions } from '@seedcord/gateway';
|
|
386
441
|
*
|
|
387
|
-
* checkBotPermissions(
|
|
442
|
+
* checkBotPermissions({ in: channel, scope: [PermissionFlagsBits.SendMessages, PermissionFlagsBits.ViewChannel] });
|
|
388
443
|
* ```
|
|
389
444
|
*/
|
|
390
|
-
function checkBotPermissions(
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
checkPermissions(me, target, scope, inverse, errors);
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
const me = target.guild.members.me;
|
|
401
|
-
if (!me) {
|
|
402
|
-
new Logger("checkBotPermissions", { channel: "errors" }).warn(`Bot member is unavailable in guild ${target.guild.id} while checking permissions in channel ${target.id}`);
|
|
403
|
-
const names = scope.map((bit) => PermissionNames.get(bit) ?? String(bit));
|
|
404
|
-
throw new ((errors?.missingNotice) ?? MissingPermissions)(void 0, "The bot", names);
|
|
405
|
-
}
|
|
406
|
-
checkPermissions(me, target, scope, inverse, errors);
|
|
445
|
+
function checkBotPermissions({ in: where, ...check }) {
|
|
446
|
+
const [guild, channel] = where instanceof Guild ? [where, void 0] : [where.guild, where];
|
|
447
|
+
checkPermissions({
|
|
448
|
+
for: botMemberOf(guild),
|
|
449
|
+
in: channel,
|
|
450
|
+
...check
|
|
451
|
+
});
|
|
407
452
|
}
|
|
408
453
|
|
|
409
454
|
//#endregion
|
|
@@ -414,42 +459,31 @@ function botRoleOf(guild) {
|
|
|
414
459
|
return botRole;
|
|
415
460
|
}
|
|
416
461
|
/**
|
|
417
|
-
* Validates that the bot can assign a
|
|
462
|
+
* Validates that the bot can assign a role. Refuses when the role is at or above the bot's, when an
|
|
463
|
+
* integration manages it, or when the bot lacks Manage Roles.
|
|
418
464
|
*
|
|
419
|
-
* @param roleOrOptions - Target role or complete options for the check
|
|
420
465
|
* @throws A {@link Notice} when the check refuses.
|
|
421
|
-
* @throws A **SeedcordError** when the
|
|
466
|
+
* @throws A **SeedcordError** when the guild's caches don't hold the bot's role or member.
|
|
422
467
|
*
|
|
423
468
|
* @example
|
|
424
469
|
* ```ts
|
|
425
470
|
* import { hasPermsToAssign } from '@seedcord/gateway';
|
|
426
471
|
*
|
|
427
|
-
*
|
|
428
|
-
* hasPermsToAssign(role);
|
|
472
|
+
* hasPermsToAssign({ role });
|
|
429
473
|
*
|
|
430
|
-
* //
|
|
431
|
-
* hasPermsToAssign({
|
|
432
|
-
* targetRole: role,
|
|
433
|
-
* noticeOverrides: {
|
|
434
|
-
* higherNotice: CustomHigherNotice,
|
|
435
|
-
* managedNotice: CustomManagedNotice,
|
|
436
|
-
* missingNotice: CustomMissingPermissionsNotice
|
|
437
|
-
* }
|
|
438
|
-
* });
|
|
474
|
+
* // with your own Notice classes
|
|
475
|
+
* hasPermsToAssign({ role, higher: RoleTooHigh, managed: RoleIsManaged, missing: CannotManageRoles });
|
|
439
476
|
* ```
|
|
440
477
|
*/
|
|
441
|
-
function hasPermsToAssign(
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (targetRole.comparePositionTo(botRole) >= 0) throw new HigherNotice("Role is higher than me", targetRole, botRole);
|
|
451
|
-
if (targetRole.managed) throw new ManagedNotice(`Cannot assign managed role ${targetRole.name}`);
|
|
452
|
-
checkBotPermissions(targetRole.guild, [PermissionFlagsBits.ManageRoles], false, { missingNotice: MissingNotice });
|
|
478
|
+
function hasPermsToAssign({ role, higher, managed, missing }) {
|
|
479
|
+
const botRole = botRoleOf(role.guild);
|
|
480
|
+
if (role.comparePositionTo(botRole) >= 0) throw new (higher ?? RoleHigherThanMe)(role, botRole);
|
|
481
|
+
if (role.managed) throw new (managed ?? CannotAssignBotRole)(role);
|
|
482
|
+
checkBotPermissions({
|
|
483
|
+
in: role.guild,
|
|
484
|
+
scope: [PermissionFlagsBits.ManageRoles],
|
|
485
|
+
notice: missing
|
|
486
|
+
});
|
|
453
487
|
}
|
|
454
488
|
|
|
455
489
|
//#endregion
|
|
@@ -514,126 +548,6 @@ async function getConfirmation(handler, prompt, options) {
|
|
|
514
548
|
return confirmed;
|
|
515
549
|
}
|
|
516
550
|
|
|
517
|
-
//#endregion
|
|
518
|
-
//#region src/bot/ReplySender.ts
|
|
519
|
-
function isReplyFile(file) {
|
|
520
|
-
return typeof file === "object" && "data" in file && ArrayBuffer.isView(file.data);
|
|
521
|
-
}
|
|
522
|
-
function toDjsFile(file) {
|
|
523
|
-
if (!isReplyFile(file)) return file;
|
|
524
|
-
return {
|
|
525
|
-
attachment: Buffer.from(file.data),
|
|
526
|
-
name: file.name,
|
|
527
|
-
...file.description !== void 0 && { description: file.description },
|
|
528
|
-
...file.title !== void 0 && { title: file.title }
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
function seedState(interaction) {
|
|
532
|
-
if (interaction.deferred && interaction.ephemeral === null) return "deferred-update";
|
|
533
|
-
if (interaction.replied) return "replied";
|
|
534
|
-
if (interaction.deferred) return "deferred-reply";
|
|
535
|
-
return "unacked";
|
|
536
|
-
}
|
|
537
|
-
/** Writes interaction responses through discord.js. The repliable handler bases and the dispatcher construct it. */
|
|
538
|
-
var ReplySender = class extends BaseReplySender {
|
|
539
|
-
interaction;
|
|
540
|
-
constructor(interaction, dispatch, bus) {
|
|
541
|
-
super({
|
|
542
|
-
bus,
|
|
543
|
-
dispatch,
|
|
544
|
-
interactionId: interaction.id
|
|
545
|
-
}, seedState(interaction));
|
|
546
|
-
this.interaction = interaction;
|
|
547
|
-
}
|
|
548
|
-
async writeReply(response, opts) {
|
|
549
|
-
const result = await this.interaction.reply({
|
|
550
|
-
...this.replyOptions(response, sendFlags(opts)),
|
|
551
|
-
withResponse: true
|
|
552
|
-
});
|
|
553
|
-
return this.createdMessage(result);
|
|
554
|
-
}
|
|
555
|
-
async writeDefer(opts) {
|
|
556
|
-
await this.interaction.deferReply({ flags: deferFlags(opts) });
|
|
557
|
-
}
|
|
558
|
-
async writeDeferUpdate() {
|
|
559
|
-
await this.sourceInteraction("deferUpdate").deferUpdate();
|
|
560
|
-
}
|
|
561
|
-
async writeUpdate(response) {
|
|
562
|
-
const result = await this.sourceInteraction("update").update({
|
|
563
|
-
...this.updateOptions(response),
|
|
564
|
-
withResponse: true
|
|
565
|
-
});
|
|
566
|
-
return this.createdMessage(result);
|
|
567
|
-
}
|
|
568
|
-
async writeFollowUp(response, opts) {
|
|
569
|
-
return await this.interaction.followUp(this.replyOptions(response, sendFlags(opts)));
|
|
570
|
-
}
|
|
571
|
-
async writeEditOriginal(response) {
|
|
572
|
-
return await this.interaction.editReply(this.editOptions(response));
|
|
573
|
-
}
|
|
574
|
-
async writeEditTarget(targetId, response) {
|
|
575
|
-
return await this.interaction.webhook.editMessage(targetId, this.editOptions(response));
|
|
576
|
-
}
|
|
577
|
-
async writeDeleteOriginal() {
|
|
578
|
-
await this.interaction.deleteReply();
|
|
579
|
-
}
|
|
580
|
-
async writeDeleteTarget(targetId) {
|
|
581
|
-
await this.interaction.deleteReply(targetId);
|
|
582
|
-
}
|
|
583
|
-
async writeModal(data) {
|
|
584
|
-
const { interaction } = this;
|
|
585
|
-
if (interaction.isModalSubmit()) this.rejectModalSubmit();
|
|
586
|
-
await interaction.showModal(data);
|
|
587
|
-
}
|
|
588
|
-
guardModalSource() {
|
|
589
|
-
if (this.interaction.isModalSubmit()) this.rejectModalSubmit();
|
|
590
|
-
}
|
|
591
|
-
rejectModalSubmit() {
|
|
592
|
-
throw new SeedcordError(SeedcordErrorCode.ReplyIllegalAckState, [
|
|
593
|
-
"showModal",
|
|
594
|
-
"this interaction is a modal submit",
|
|
595
|
-
"A modal cannot open another modal.",
|
|
596
|
-
this.routeId
|
|
597
|
-
]);
|
|
598
|
-
}
|
|
599
|
-
sourceInteraction(method) {
|
|
600
|
-
const { interaction } = this;
|
|
601
|
-
if (interaction.isMessageComponent()) return interaction;
|
|
602
|
-
if (interaction.isModalSubmit() && interaction.isFromMessage()) return interaction;
|
|
603
|
-
throw new SeedcordError(SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
|
|
604
|
-
}
|
|
605
|
-
replyOptions(response, flags) {
|
|
606
|
-
const reply = this.serialize(response);
|
|
607
|
-
return {
|
|
608
|
-
components: reply.components,
|
|
609
|
-
flags,
|
|
610
|
-
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
611
|
-
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
editOptions(response) {
|
|
615
|
-
const reply = this.serialize(response);
|
|
616
|
-
return {
|
|
617
|
-
components: reply.components,
|
|
618
|
-
flags: MessageFlags.IsComponentsV2,
|
|
619
|
-
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
620
|
-
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
updateOptions(response) {
|
|
624
|
-
const reply = this.serialize(response);
|
|
625
|
-
return {
|
|
626
|
-
components: reply.components,
|
|
627
|
-
flags: MessageFlags.IsComponentsV2,
|
|
628
|
-
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
629
|
-
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
createdMessage(result) {
|
|
633
|
-
return result.resource?.message ?? void 0;
|
|
634
|
-
}
|
|
635
|
-
};
|
|
636
|
-
|
|
637
551
|
//#endregion
|
|
638
552
|
//#region src/handlers/RepliableHandler.ts
|
|
639
553
|
/**
|
|
@@ -650,8 +564,9 @@ var RepliableHandler = class extends RepliableHandler$1 {
|
|
|
650
564
|
getEvent() {
|
|
651
565
|
return this.event;
|
|
652
566
|
}
|
|
653
|
-
|
|
654
|
-
|
|
567
|
+
/** The user who sent this interaction. */
|
|
568
|
+
get user() {
|
|
569
|
+
return this.event.user;
|
|
655
570
|
}
|
|
656
571
|
};
|
|
657
572
|
|
|
@@ -694,8 +609,8 @@ var InteractionMiddleware = class extends RepliableHandler {
|
|
|
694
609
|
* @typeParam Repliable - The interaction type this handler processes
|
|
695
610
|
*/
|
|
696
611
|
var InteractionHandler = class extends RepliableHandler {
|
|
697
|
-
constructor(event, core, dispatch) {
|
|
698
|
-
super(event, core, dispatch);
|
|
612
|
+
constructor(event, core, dispatch, sender) {
|
|
613
|
+
super(event, core, dispatch, sender);
|
|
699
614
|
}
|
|
700
615
|
/**
|
|
701
616
|
* Open a modal. Must be the initial response to this interaction. The modal kind rejects this call at
|
|
@@ -706,14 +621,6 @@ var InteractionHandler = class extends RepliableHandler {
|
|
|
706
621
|
}
|
|
707
622
|
};
|
|
708
623
|
|
|
709
|
-
//#endregion
|
|
710
|
-
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
711
|
-
function slashRouteOf(interaction) {
|
|
712
|
-
const group = interaction.options.getSubcommandGroup(false) ?? void 0;
|
|
713
|
-
const subcommand = interaction.options.getSubcommand(false) ?? void 0;
|
|
714
|
-
return buildSlashRoute(interaction.commandName, subcommand, group);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
624
|
//#endregion
|
|
718
625
|
//#region src/handlers/interaction/SlashHandler.ts
|
|
719
626
|
/**
|
|
@@ -721,7 +628,7 @@ function slashRouteOf(interaction) {
|
|
|
721
628
|
*
|
|
722
629
|
* Pass the route from the generated registry as the generic, the same string as `@SlashRoute`, then read
|
|
723
630
|
* `this.options` for a single command or `this.match` for several. Command authoring stays plain
|
|
724
|
-
* discord.js
|
|
631
|
+
* discord.js. `seedcord codegen` reads its `toJSON()` to type these options.
|
|
725
632
|
*
|
|
726
633
|
* @typeParam Route - One or more route keys from {@link SlashRegistry}, e.g. `'ban'` or `'ban' | 'kick'`.
|
|
727
634
|
* @typeParam Cache - The interaction cache state. The command's contexts set it.
|
|
@@ -742,28 +649,28 @@ var SlashHandler = class extends InteractionHandler {
|
|
|
742
649
|
* The typed options for this command's route. Only the getters for kinds this command declares appear.
|
|
743
650
|
* Each getter's name argument is restricted to that command's options of its kind.
|
|
744
651
|
*
|
|
745
|
-
* - A required option
|
|
652
|
+
* - A required option's getter never returns `null`.
|
|
746
653
|
*
|
|
747
654
|
* - Choices narrow to their literal union.
|
|
748
655
|
*
|
|
749
656
|
* - A channel option declared with `addChannelTypes` narrows to those types.
|
|
750
657
|
*
|
|
751
658
|
* Use `this.event.options` directly for anything outside this view, such as `getSubcommand()`.
|
|
752
|
-
* However, for the common case, the methods in this would suffice.
|
|
753
659
|
*/
|
|
754
660
|
get options() {
|
|
755
661
|
return this.event.options;
|
|
756
662
|
}
|
|
757
663
|
/**
|
|
758
|
-
* Run the
|
|
664
|
+
* Run the case for the selected command. Use this only when the handler is registered for several
|
|
759
665
|
* routes.
|
|
760
666
|
*
|
|
761
|
-
* Provide one
|
|
762
|
-
* own narrowed options. The
|
|
667
|
+
* Provide one case per registered route, keyed by its route string, and each case receives that route's
|
|
668
|
+
* own narrowed options. The cases must cover every route in the generic. A missing route or an unknown
|
|
763
669
|
* key is a compile error.
|
|
764
670
|
*
|
|
765
|
-
* @param
|
|
766
|
-
* @
|
|
671
|
+
* @param cases - One handler per registered route, keyed by route string.
|
|
672
|
+
* @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
|
|
673
|
+
* @returns The result of the case that ran.
|
|
767
674
|
*
|
|
768
675
|
* @example
|
|
769
676
|
* ```ts
|
|
@@ -778,29 +685,13 @@ var SlashHandler = class extends InteractionHandler {
|
|
|
778
685
|
* }
|
|
779
686
|
* ```
|
|
780
687
|
*/
|
|
781
|
-
async match(
|
|
782
|
-
|
|
783
|
-
const arm = Object.hasOwn(arms, route) ? arms[route] : void 0;
|
|
784
|
-
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
|
|
785
|
-
this.constructor.name,
|
|
786
|
-
"command route",
|
|
787
|
-
route
|
|
788
|
-
]);
|
|
789
|
-
return await arm(this.options);
|
|
688
|
+
async match(cases) {
|
|
689
|
+
return await runCase(this, cases, "command route", this.route, this.options);
|
|
790
690
|
}
|
|
791
691
|
};
|
|
792
692
|
|
|
793
693
|
//#endregion
|
|
794
694
|
//#region src/handlers/interaction/ContextMenuHandler.ts
|
|
795
|
-
function armFor(handler, arms, name) {
|
|
796
|
-
const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
|
|
797
|
-
if (typeof arm !== "function") throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
|
|
798
|
-
handler.constructor.name,
|
|
799
|
-
"context menu command",
|
|
800
|
-
name
|
|
801
|
-
]);
|
|
802
|
-
return arm;
|
|
803
|
-
}
|
|
804
695
|
/**
|
|
805
696
|
* Base class for a user context-menu command handler (right-click a user).
|
|
806
697
|
*
|
|
@@ -828,21 +719,18 @@ var UserContextMenuHandler = class extends InteractionHandler {
|
|
|
828
719
|
get targetMember() {
|
|
829
720
|
return this.event.targetMember;
|
|
830
721
|
}
|
|
831
|
-
/** Which of the registered commands fired. */
|
|
832
|
-
get commandName() {
|
|
833
|
-
return this.event.commandName;
|
|
834
|
-
}
|
|
835
722
|
/**
|
|
836
|
-
* Run the
|
|
723
|
+
* Run the case for the selected command. Use this only when the handler is registered for several
|
|
837
724
|
* names.
|
|
838
725
|
*
|
|
839
|
-
* Provide one
|
|
840
|
-
*
|
|
841
|
-
* one command. The
|
|
726
|
+
* Provide one case per registered name, keyed by the name Discord shows in the right-click menu. Each
|
|
727
|
+
* case receives the clicked user and that user's guild member, both narrowed to the cache state of that
|
|
728
|
+
* one command. The cases must cover every name in the generic. A missing name or an unknown key is a
|
|
842
729
|
* compile error.
|
|
843
730
|
*
|
|
844
|
-
* @param
|
|
845
|
-
* @
|
|
731
|
+
* @param cases - One handler per registered command name.
|
|
732
|
+
* @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
|
|
733
|
+
* @returns The result of the case that ran.
|
|
846
734
|
*
|
|
847
735
|
* @example
|
|
848
736
|
* ```ts
|
|
@@ -857,8 +745,8 @@ var UserContextMenuHandler = class extends InteractionHandler {
|
|
|
857
745
|
* }
|
|
858
746
|
* ```
|
|
859
747
|
*/
|
|
860
|
-
async match(
|
|
861
|
-
return await
|
|
748
|
+
async match(cases) {
|
|
749
|
+
return await runCase(this, cases, "context menu command", this.route, this.target, this.targetMember);
|
|
862
750
|
}
|
|
863
751
|
};
|
|
864
752
|
/**
|
|
@@ -884,20 +772,17 @@ var MessageContextMenuHandler = class extends InteractionHandler {
|
|
|
884
772
|
get target() {
|
|
885
773
|
return this.event.targetMessage;
|
|
886
774
|
}
|
|
887
|
-
/** Which of the registered commands fired. */
|
|
888
|
-
get commandName() {
|
|
889
|
-
return this.event.commandName;
|
|
890
|
-
}
|
|
891
775
|
/**
|
|
892
|
-
* Run the
|
|
776
|
+
* Run the case for the selected command. Use this only when the handler is registered for several
|
|
893
777
|
* names.
|
|
894
778
|
*
|
|
895
|
-
* Provide one
|
|
896
|
-
*
|
|
779
|
+
* Provide one case per registered name, keyed by the name Discord shows in the right-click menu. Each
|
|
780
|
+
* case receives the clicked message, narrowed to the cache state of that one command. The cases must
|
|
897
781
|
* cover every name in the generic. A missing name or an unknown key is a compile error.
|
|
898
782
|
*
|
|
899
|
-
* @param
|
|
900
|
-
* @
|
|
783
|
+
* @param cases - One handler per registered command name.
|
|
784
|
+
* @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
|
|
785
|
+
* @returns The result of the case that ran.
|
|
901
786
|
*
|
|
902
787
|
* @example
|
|
903
788
|
* ```ts
|
|
@@ -912,8 +797,8 @@ var MessageContextMenuHandler = class extends InteractionHandler {
|
|
|
912
797
|
* }
|
|
913
798
|
* ```
|
|
914
799
|
*/
|
|
915
|
-
async match(
|
|
916
|
-
return await
|
|
800
|
+
async match(cases) {
|
|
801
|
+
return await runCase(this, cases, "context menu command", this.route, this.target);
|
|
917
802
|
}
|
|
918
803
|
};
|
|
919
804
|
|
|
@@ -936,7 +821,7 @@ var BaseHandler = class extends BaseHandler$1 {
|
|
|
936
821
|
*
|
|
937
822
|
* Pass the command route(s) from the generated registry as the generic, the same string(s) as
|
|
938
823
|
* `@AutocompleteRoute`. Branch on the focused field with `this.match`, read already-entered sibling options
|
|
939
|
-
* with `this.options`, and
|
|
824
|
+
* with `this.options`, and read the selected command's path from `this.route`.
|
|
940
825
|
*
|
|
941
826
|
* @typeParam Route - One or more route keys from {@link SlashRegistry}, e.g. `'search'` or `'search' | 'find'`.
|
|
942
827
|
* @typeParam Cache - The interaction cache state. The command's contexts set it.
|
|
@@ -954,8 +839,14 @@ var BaseHandler = class extends BaseHandler$1 {
|
|
|
954
839
|
* ```
|
|
955
840
|
*/
|
|
956
841
|
var AutocompleteHandler = class extends BaseHandler {
|
|
957
|
-
constructor(event, core, dispatch) {
|
|
842
|
+
constructor(event, core, dispatch, responder) {
|
|
958
843
|
super(event, core, dispatch, "interactions");
|
|
844
|
+
this.#responder = responder;
|
|
845
|
+
}
|
|
846
|
+
#responder;
|
|
847
|
+
/** The user who sent this interaction. */
|
|
848
|
+
get user() {
|
|
849
|
+
return this.event.user;
|
|
959
850
|
}
|
|
960
851
|
decodedFocused;
|
|
961
852
|
/**
|
|
@@ -973,16 +864,17 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
973
864
|
return focused;
|
|
974
865
|
}
|
|
975
866
|
/**
|
|
976
|
-
* Run the
|
|
867
|
+
* Run the case for the focused field. Because an autocomplete always dispatches by which field is
|
|
977
868
|
* focused, match is how you read it. There is no single-field shortcut the way slash/component handlers have one.
|
|
978
869
|
*
|
|
979
|
-
* Provide one
|
|
980
|
-
*
|
|
981
|
-
*
|
|
870
|
+
* Provide one case per autocompletable field across the registered commands, keyed by field name. Each
|
|
871
|
+
* case receives the focused partial value and a `respond` typed to that field's choice value. The
|
|
872
|
+
* cases must cover every autocompletable field. A missing field or an unknown key is a compile error.
|
|
982
873
|
*
|
|
983
|
-
* @param
|
|
984
|
-
* @
|
|
985
|
-
* @
|
|
874
|
+
* @param cases - One handler per autocompletable field, keyed by field name.
|
|
875
|
+
* @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
|
|
876
|
+
* @returns The result of the case that ran.
|
|
877
|
+
* @throws A **SeedcordTypeError** with `MatchCaseMissing` when the focused field doesn't have a case. Only a stale-deployed command can send one.
|
|
986
878
|
*
|
|
987
879
|
* @example
|
|
988
880
|
* ```ts
|
|
@@ -997,28 +889,14 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
997
889
|
* }
|
|
998
890
|
* ```
|
|
999
891
|
*/
|
|
1000
|
-
async match(
|
|
892
|
+
async match(cases) {
|
|
1001
893
|
const { name, value } = this.focused;
|
|
1002
894
|
const respond = (choices) => this.respond(choices);
|
|
1003
|
-
|
|
1004
|
-
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
|
|
1005
|
-
this.constructor.name,
|
|
1006
|
-
"focused field",
|
|
1007
|
-
name
|
|
1008
|
-
]);
|
|
1009
|
-
return await arm(value, respond);
|
|
895
|
+
return await runCase(this, cases, "focused field", name, value, respond);
|
|
1010
896
|
}
|
|
1011
897
|
/** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
bus: this.core.bus,
|
|
1015
|
-
dispatch: this.dispatch,
|
|
1016
|
-
interactionId: this.event.id
|
|
1017
|
-
}, "respond", () => this.event.respond(choices));
|
|
1018
|
-
}
|
|
1019
|
-
/** The firing command route, for a field whose completion differs per registered command. */
|
|
1020
|
-
get route() {
|
|
1021
|
-
return slashRouteOf(this.event);
|
|
898
|
+
respond(choices) {
|
|
899
|
+
return this.#responder.respond(choices);
|
|
1022
900
|
}
|
|
1023
901
|
/**
|
|
1024
902
|
* The already-entered options on this command, restricted to the kinds Discord resolves during autocomplete
|
|
@@ -1051,7 +929,7 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1051
929
|
return this.sender.deferUpdate();
|
|
1052
930
|
}
|
|
1053
931
|
decoded;
|
|
1054
|
-
get
|
|
932
|
+
get decodedRoute() {
|
|
1055
933
|
this.decoded ??= decodeComponentRoute(this.constructor, this.event.customId);
|
|
1056
934
|
return this.decoded;
|
|
1057
935
|
}
|
|
@@ -1065,20 +943,21 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1065
943
|
* @throws An **InvalidCustomId** when the wire is corrupt. The controller boundary turns it into a reply.
|
|
1066
944
|
*/
|
|
1067
945
|
get params() {
|
|
1068
|
-
return this.
|
|
946
|
+
return this.decodedRoute.params;
|
|
1069
947
|
}
|
|
1070
948
|
/**
|
|
1071
|
-
* Run the
|
|
1072
|
-
* registered for several routes.
|
|
949
|
+
* Run the case for whichever route the component was minted from. Use this only when the handler is
|
|
950
|
+
* registered for several routes. On a single-route handler, read `this.params` directly. On a multi-route
|
|
1073
951
|
* handler `this.params` is `never`, leaving match as the only way to read the decoded params.
|
|
1074
952
|
*
|
|
1075
|
-
* Provide one
|
|
1076
|
-
* decoded params. The compiler checks that the
|
|
1077
|
-
* decodes the wire before it runs
|
|
953
|
+
* Provide one case per registered route, keyed by its prefix, and each case receives that route's own
|
|
954
|
+
* decoded params. The compiler checks that the cases cover every registered route prefix. `match`
|
|
955
|
+
* decodes the wire before it runs a case.
|
|
1078
956
|
*
|
|
1079
|
-
* @param
|
|
1080
|
-
* @
|
|
1081
|
-
* @
|
|
957
|
+
* @param cases - One callback per registered route, keyed by prefix.
|
|
958
|
+
* @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
|
|
959
|
+
* @returns The result of the case that ran.
|
|
960
|
+
* @throws A **SeedcordTypeError** with `MatchCaseMissing` when the wire's prefix doesn't have a case.
|
|
1082
961
|
* @throws A **StaleCustomId** when the wire was encoded under an older shape.
|
|
1083
962
|
* @throws An **InvalidCustomId** when the wire is corrupt.
|
|
1084
963
|
*
|
|
@@ -1095,15 +974,9 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1095
974
|
* }
|
|
1096
975
|
* ```
|
|
1097
976
|
*/
|
|
1098
|
-
async match(
|
|
1099
|
-
const { prefix, params } = this.
|
|
1100
|
-
|
|
1101
|
-
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
|
|
1102
|
-
this.constructor.name,
|
|
1103
|
-
"component route",
|
|
1104
|
-
prefix
|
|
1105
|
-
]);
|
|
1106
|
-
return await arm(params);
|
|
977
|
+
async match(cases) {
|
|
978
|
+
const { prefix, params } = this.decodedRoute;
|
|
979
|
+
return await runCase(this, cases, "component route", prefix, params);
|
|
1107
980
|
}
|
|
1108
981
|
};
|
|
1109
982
|
|
|
@@ -1175,7 +1048,7 @@ var ModalHandler = class extends ComponentHandler {
|
|
|
1175
1048
|
}
|
|
1176
1049
|
ensureSourceMessage(method) {
|
|
1177
1050
|
if (this.event.isFromMessage()) return;
|
|
1178
|
-
throw new SeedcordError(SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
|
|
1051
|
+
throw new SeedcordError(SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.dispatch.routeId]);
|
|
1179
1052
|
}
|
|
1180
1053
|
};
|
|
1181
1054
|
|
|
@@ -1338,8 +1211,8 @@ var MentionableMenuHandler = class extends SelectMenuHandler {
|
|
|
1338
1211
|
/**
|
|
1339
1212
|
* Base class for a Discord client event handler.
|
|
1340
1213
|
*
|
|
1341
|
-
* Pass the event name(s) as the generic, the same one(s) as `@RegisterEvent`.
|
|
1342
|
-
* `this.event` (the payload tuple) directly.
|
|
1214
|
+
* Pass the event name(s) as the generic, the same one(s) as `@RegisterEvent`. On a single-event handler, read
|
|
1215
|
+
* `this.event` (the payload tuple) directly. On a handler registered for several events, branch with `this.match`,
|
|
1343
1216
|
* keyed by event name, since the union of payload tuples is not directly readable.
|
|
1344
1217
|
*
|
|
1345
1218
|
* @typeParam Names - One or more `ClientEvents` keys, e.g. `Events.MessageCreate` or `Events.MessageCreate | Events.MessageUpdate`.
|
|
@@ -1358,22 +1231,21 @@ var MentionableMenuHandler = class extends SelectMenuHandler {
|
|
|
1358
1231
|
* ```
|
|
1359
1232
|
*/
|
|
1360
1233
|
var EventHandler = class extends BaseHandler {
|
|
1361
|
-
|
|
1362
|
-
constructor(event, core, dispatch, eventName) {
|
|
1234
|
+
constructor(event, core, dispatch) {
|
|
1363
1235
|
super(event, core, dispatch, "events");
|
|
1364
|
-
this.firedEvent = eventName;
|
|
1365
1236
|
}
|
|
1366
1237
|
/**
|
|
1367
|
-
* Run the
|
|
1368
|
-
* events.
|
|
1238
|
+
* Run the case for the current event. Use this only when the handler is registered for several
|
|
1239
|
+
* events. On a single-event handler, read `this.event` directly. On a multi-event handler `this.event` is
|
|
1369
1240
|
* `never`, so match is the only way to read the payload.
|
|
1370
1241
|
*
|
|
1371
|
-
* Provide one
|
|
1372
|
-
* payload tuple. The
|
|
1242
|
+
* Provide one case per registered event, keyed by its name, and each case receives that event's own
|
|
1243
|
+
* payload tuple. The cases must cover every event in the generic. A missing event or an unknown key is a
|
|
1373
1244
|
* compile error.
|
|
1374
1245
|
*
|
|
1375
|
-
* @param
|
|
1376
|
-
* @
|
|
1246
|
+
* @param cases - One handler per registered event, keyed by event name.
|
|
1247
|
+
* @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
|
|
1248
|
+
* @returns The result of the case that ran.
|
|
1377
1249
|
*
|
|
1378
1250
|
* @example
|
|
1379
1251
|
* ```ts
|
|
@@ -1388,16 +1260,8 @@ var EventHandler = class extends BaseHandler {
|
|
|
1388
1260
|
* }
|
|
1389
1261
|
* ```
|
|
1390
1262
|
*/
|
|
1391
|
-
async match(
|
|
1392
|
-
|
|
1393
|
-
if (name === void 0) throw new SeedcordError(SeedcordErrorCode.EventNameUnavailable, [this.constructor.name, "match()"]);
|
|
1394
|
-
const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
|
|
1395
|
-
if (typeof arm !== "function") throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
|
|
1396
|
-
this.constructor.name,
|
|
1397
|
-
"event",
|
|
1398
|
-
name
|
|
1399
|
-
]);
|
|
1400
|
-
return await arm(...this.getEvent());
|
|
1263
|
+
async match(cases) {
|
|
1264
|
+
return await runCase(this, cases, "event", this.route, ...this.getEvent());
|
|
1401
1265
|
}
|
|
1402
1266
|
};
|
|
1403
1267
|
|
|
@@ -1408,26 +1272,135 @@ var EventHandler = class extends BaseHandler {
|
|
|
1408
1272
|
*
|
|
1409
1273
|
* Middleware runs before event handlers and can stop the event by throwing a `Silence`. Unlike `EventHandler`, it
|
|
1410
1274
|
* runs the SAME for every event it is registered for, so it has no `match`. Specify a single event in the
|
|
1411
|
-
* generic and `{ events }` to read `this.event` fully typed.
|
|
1412
|
-
* catchall)
|
|
1413
|
-
* payload shape.
|
|
1275
|
+
* generic and `{ events }` to read `this.event` fully typed. If you span several events (or omit `{ events }` for
|
|
1276
|
+
* a catchall), `this.event` narrows to `never`. Read `this.route` there, for work that doesn't depend on the
|
|
1277
|
+
* payload shape. Write one class per event for a middleware that needs each event's payload.
|
|
1414
1278
|
*
|
|
1415
1279
|
* @typeParam EventName - One or more `ClientEvents` keys. Defaults to every event (catchall).
|
|
1416
1280
|
*/
|
|
1417
1281
|
var EventMiddleware = class extends BaseHandler {
|
|
1418
|
-
|
|
1419
|
-
constructor(event, core, dispatch, eventName) {
|
|
1282
|
+
constructor(event, core, dispatch) {
|
|
1420
1283
|
super(event, core, dispatch, "events");
|
|
1421
|
-
this.firedEvent = eventName;
|
|
1422
1284
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
//#endregion
|
|
1288
|
+
//#region src/bot/ReplySender.ts
|
|
1289
|
+
function isReplyFile(file) {
|
|
1290
|
+
return typeof file === "object" && "data" in file && ArrayBuffer.isView(file.data);
|
|
1291
|
+
}
|
|
1292
|
+
function toDjsFile(file) {
|
|
1293
|
+
if (!isReplyFile(file)) return file;
|
|
1294
|
+
return {
|
|
1295
|
+
attachment: Buffer.from(file.data),
|
|
1296
|
+
name: file.name,
|
|
1297
|
+
...file.description !== void 0 && { description: file.description },
|
|
1298
|
+
...file.title !== void 0 && { title: file.title }
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
function seedState(interaction) {
|
|
1302
|
+
if (interaction.deferred && interaction.ephemeral === null) return "deferred-update";
|
|
1303
|
+
if (interaction.replied) return "replied";
|
|
1304
|
+
if (interaction.deferred) return "deferred-reply";
|
|
1305
|
+
return "unacked";
|
|
1306
|
+
}
|
|
1307
|
+
/** Writes interaction responses through discord.js. The repliable handler bases and the dispatcher construct it. */
|
|
1308
|
+
var ReplySender = class extends BaseReplySender {
|
|
1309
|
+
interaction;
|
|
1310
|
+
constructor(interaction, dispatch, bus) {
|
|
1311
|
+
super({
|
|
1312
|
+
bus,
|
|
1313
|
+
dispatch,
|
|
1314
|
+
interactionId: interaction.id
|
|
1315
|
+
}, seedState(interaction));
|
|
1316
|
+
this.interaction = interaction;
|
|
1317
|
+
}
|
|
1318
|
+
async writeReply(response, opts) {
|
|
1319
|
+
const result = await this.interaction.reply({
|
|
1320
|
+
...this.replyOptions(response, sendFlags(opts)),
|
|
1321
|
+
withResponse: true
|
|
1322
|
+
});
|
|
1323
|
+
return this.createdMessage(result);
|
|
1324
|
+
}
|
|
1325
|
+
async writeDefer(opts) {
|
|
1326
|
+
await this.interaction.deferReply({ flags: deferFlags(opts) });
|
|
1327
|
+
}
|
|
1328
|
+
async writeDeferUpdate() {
|
|
1329
|
+
await this.sourceInteraction("deferUpdate").deferUpdate();
|
|
1330
|
+
}
|
|
1331
|
+
async writeUpdate(response) {
|
|
1332
|
+
const result = await this.sourceInteraction("update").update({
|
|
1333
|
+
...this.updateOptions(response),
|
|
1334
|
+
withResponse: true
|
|
1335
|
+
});
|
|
1336
|
+
return this.createdMessage(result);
|
|
1337
|
+
}
|
|
1338
|
+
async writeFollowUp(response, opts) {
|
|
1339
|
+
return await this.interaction.followUp(this.replyOptions(response, sendFlags(opts)));
|
|
1340
|
+
}
|
|
1341
|
+
async writeEditOriginal(response) {
|
|
1342
|
+
return await this.interaction.editReply(this.editOptions(response));
|
|
1343
|
+
}
|
|
1344
|
+
async writeEditTarget(targetId, response) {
|
|
1345
|
+
return await this.interaction.webhook.editMessage(targetId, this.editOptions(response));
|
|
1346
|
+
}
|
|
1347
|
+
async writeDeleteOriginal() {
|
|
1348
|
+
await this.interaction.deleteReply();
|
|
1349
|
+
}
|
|
1350
|
+
async writeDeleteTarget(targetId) {
|
|
1351
|
+
await this.interaction.deleteReply(targetId);
|
|
1352
|
+
}
|
|
1353
|
+
async writeModal(data) {
|
|
1354
|
+
const { interaction } = this;
|
|
1355
|
+
if (interaction.isModalSubmit()) this.rejectModalSubmit();
|
|
1356
|
+
await interaction.showModal(data);
|
|
1357
|
+
}
|
|
1358
|
+
guardModalSource() {
|
|
1359
|
+
if (this.interaction.isModalSubmit()) this.rejectModalSubmit();
|
|
1360
|
+
}
|
|
1361
|
+
rejectModalSubmit() {
|
|
1362
|
+
throw new SeedcordError(SeedcordErrorCode.ReplyIllegalAckState, [
|
|
1363
|
+
"showModal",
|
|
1364
|
+
"this interaction is a modal submit",
|
|
1365
|
+
"A modal cannot open another modal.",
|
|
1366
|
+
this.routeId
|
|
1367
|
+
]);
|
|
1368
|
+
}
|
|
1369
|
+
sourceInteraction(method) {
|
|
1370
|
+
const { interaction } = this;
|
|
1371
|
+
if (interaction.isMessageComponent()) return interaction;
|
|
1372
|
+
if (interaction.isModalSubmit() && interaction.isFromMessage()) return interaction;
|
|
1373
|
+
throw new SeedcordError(SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
|
|
1374
|
+
}
|
|
1375
|
+
replyOptions(response, flags) {
|
|
1376
|
+
const reply = this.serialize(response);
|
|
1377
|
+
return {
|
|
1378
|
+
components: reply.components,
|
|
1379
|
+
flags,
|
|
1380
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
1381
|
+
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
editOptions(response) {
|
|
1385
|
+
const reply = this.serialize(response);
|
|
1386
|
+
return {
|
|
1387
|
+
components: reply.components,
|
|
1388
|
+
flags: MessageFlags.IsComponentsV2,
|
|
1389
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
1390
|
+
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
updateOptions(response) {
|
|
1394
|
+
const reply = this.serialize(response);
|
|
1395
|
+
return {
|
|
1396
|
+
components: reply.components,
|
|
1397
|
+
flags: MessageFlags.IsComponentsV2,
|
|
1398
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
1399
|
+
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
createdMessage(result) {
|
|
1403
|
+
return result.resource?.message ?? void 0;
|
|
1431
1404
|
}
|
|
1432
1405
|
};
|
|
1433
1406
|
|
|
@@ -1520,14 +1493,14 @@ var CursorSource = class extends CursorSourceBase {};
|
|
|
1520
1493
|
/**
|
|
1521
1494
|
* Base class for a gateway subscriber, binding `this.core` to the gateway {@link Core}.
|
|
1522
1495
|
*
|
|
1523
|
-
* @typeParam KeyOfSubscribers - The subscription key this handler receives
|
|
1496
|
+
* @typeParam KeyOfSubscribers - The subscription key or keys that this handler receives, the same ones as `@Subscribe`
|
|
1524
1497
|
*/
|
|
1525
1498
|
var Subscriber = class extends Subscriber$1 {};
|
|
1526
1499
|
/**
|
|
1527
1500
|
* Base class for a gateway subscriber that delivers its event to a Discord webhook, binding
|
|
1528
1501
|
* `this.core` to the gateway {@link Core}.
|
|
1529
1502
|
*
|
|
1530
|
-
* @typeParam KeyOfSubscribers - The subscription key this reporter receives
|
|
1503
|
+
* @typeParam KeyOfSubscribers - The subscription key or keys that this reporter receives, the same ones as `@Subscribe`
|
|
1531
1504
|
*/
|
|
1532
1505
|
var WebhookLog = class extends WebhookLog$1 {};
|
|
1533
1506
|
|
|
@@ -1540,8 +1513,8 @@ const GATEWAY_SCOPE = {
|
|
|
1540
1513
|
/**
|
|
1541
1514
|
* Base class for a gateway plugin, binding `this.core` to the gateway {@link Core}.
|
|
1542
1515
|
*
|
|
1543
|
-
*
|
|
1544
|
-
*
|
|
1516
|
+
* To run on either transport, extend the base from `@seedcord/core/plugin`. Its `this.core` carries
|
|
1517
|
+
* only the shared members.
|
|
1545
1518
|
*
|
|
1546
1519
|
* @typeParam Opts - The transport and runtime the plugin declares.
|
|
1547
1520
|
*/
|
|
@@ -1552,15 +1525,23 @@ var Plugin = class extends Plugin$1 {
|
|
|
1552
1525
|
|
|
1553
1526
|
//#endregion
|
|
1554
1527
|
//#region src/miscellaneous/deriveEventActor.ts
|
|
1528
|
+
function actorOf(message) {
|
|
1529
|
+
return {
|
|
1530
|
+
guild: message.guild,
|
|
1531
|
+
member: message.member,
|
|
1532
|
+
user: message.author,
|
|
1533
|
+
channelId: message.channelId
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1555
1536
|
function deriveEventActor(args) {
|
|
1556
1537
|
const tuple = Array.isArray(args) ? args : [args];
|
|
1538
|
+
let partial;
|
|
1557
1539
|
for (const arg of tuple) {
|
|
1558
|
-
if (arg instanceof Message)
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
};
|
|
1540
|
+
if (arg instanceof Message) {
|
|
1541
|
+
const message = arg;
|
|
1542
|
+
if (!message.partial) return actorOf(message);
|
|
1543
|
+
partial ??= message;
|
|
1544
|
+
}
|
|
1564
1545
|
if (arg instanceof GuildMember) return {
|
|
1565
1546
|
guild: arg.guild,
|
|
1566
1547
|
member: arg,
|
|
@@ -1574,7 +1555,7 @@ function deriveEventActor(args) {
|
|
|
1574
1555
|
channelId: null
|
|
1575
1556
|
};
|
|
1576
1557
|
}
|
|
1577
|
-
return {
|
|
1558
|
+
return partial ? actorOf(partial) : {
|
|
1578
1559
|
guild: null,
|
|
1579
1560
|
member: null,
|
|
1580
1561
|
user: null,
|
|
@@ -1583,7 +1564,8 @@ function deriveEventActor(args) {
|
|
|
1583
1564
|
}
|
|
1584
1565
|
|
|
1585
1566
|
//#endregion
|
|
1586
|
-
//#region src/bot/gates/
|
|
1567
|
+
//#region src/bot/gates/context.ts
|
|
1568
|
+
const NO_PERMISSIONS = 0n;
|
|
1587
1569
|
function interactionGateContext(interaction, core, dispatch) {
|
|
1588
1570
|
const rawMember = interaction.member;
|
|
1589
1571
|
const memberRoleIds = rawMember instanceof GuildMember ? [...rawMember.roles.cache.keys()].filter((id) => id !== interaction.guildId) : rawMember?.roles ?? [];
|
|
@@ -1602,8 +1584,7 @@ function interactionGateContext(interaction, core, dispatch) {
|
|
|
1602
1584
|
memberPermissions: interaction.memberPermissions?.bitfield ?? null,
|
|
1603
1585
|
appPermissions: interaction.appPermissions.bitfield,
|
|
1604
1586
|
memberGuildPermissions: rawMember instanceof GuildMember ? rawMember.permissions.bitfield : null,
|
|
1605
|
-
appGuildPermissions: interaction.guild
|
|
1606
|
-
declaredRoute: null
|
|
1587
|
+
appGuildPermissions: interaction.guild ? interaction.guild.members.me?.permissions.bitfield ?? null : NO_PERMISSIONS
|
|
1607
1588
|
};
|
|
1608
1589
|
}
|
|
1609
1590
|
function eventGateContext(eventName, args, core, dispatch) {
|
|
@@ -1622,161 +1603,76 @@ function eventGateContext(eventName, args, core, dispatch) {
|
|
|
1622
1603
|
channelId: actor.channelId,
|
|
1623
1604
|
memberRoleIds: actor.member ? [...actor.member.roles.cache.keys()].filter((id) => id !== actor.guild?.id) : [],
|
|
1624
1605
|
memberPermissions: actor.member?.permissions.bitfield ?? null,
|
|
1625
|
-
appPermissions: null,
|
|
1626
1606
|
memberGuildPermissions: actor.member?.permissions.bitfield ?? null,
|
|
1627
|
-
appGuildPermissions: actor.guild?.members.me?.permissions.bitfield ?? null
|
|
1628
|
-
declaredRoute: null
|
|
1607
|
+
appGuildPermissions: actor.guild?.members.me?.permissions.bitfield ?? null
|
|
1629
1608
|
};
|
|
1630
1609
|
}
|
|
1631
1610
|
|
|
1632
1611
|
//#endregion
|
|
1633
|
-
//#region src/miscellaneous/
|
|
1634
|
-
|
|
1635
|
-
function extractErrorResponse(error, core, fault) {
|
|
1636
|
-
const uuid = crypto.randomUUID();
|
|
1637
|
-
const { dispatch } = fault;
|
|
1638
|
-
const developerUsername = core.config.notifications?.developerUsername;
|
|
1639
|
-
const ctx = developerUsername === void 0 ? {
|
|
1640
|
-
uuid,
|
|
1641
|
-
dispatch
|
|
1642
|
-
} : {
|
|
1643
|
-
uuid,
|
|
1644
|
-
developerUsername,
|
|
1645
|
-
dispatch
|
|
1646
|
-
};
|
|
1647
|
-
if (error instanceof Notice) {
|
|
1648
|
-
if (error.report) reportFault(error, core, fault, uuid);
|
|
1649
|
-
return {
|
|
1650
|
-
uuid,
|
|
1651
|
-
response: error.render(ctx)
|
|
1652
|
-
};
|
|
1653
|
-
}
|
|
1654
|
-
reportRawFault(error, core, fault, uuid);
|
|
1655
|
-
const Override = core.config.errors?.defaultError;
|
|
1656
|
-
return {
|
|
1657
|
-
uuid,
|
|
1658
|
-
response: Override ? new Override(uuid).render(ctx) : new Fault().render(ctx)
|
|
1659
|
-
};
|
|
1660
|
-
}
|
|
1661
|
-
function reportFault(denial, core, fault, uuid) {
|
|
1662
|
-
logger$2.error(`${denial.name}: ${uuid}`, denial);
|
|
1663
|
-
if (fault.interaction) core.bus[PublishDefault]("handledException", {
|
|
1664
|
-
denial,
|
|
1665
|
-
uuid,
|
|
1666
|
-
dispatchId: fault.dispatch.id,
|
|
1667
|
-
origin: fault.origin,
|
|
1668
|
-
source: buildInteractionSource(fault.interaction)
|
|
1669
|
-
});
|
|
1670
|
-
else if (fault.event) core.bus[PublishDefault]("handledException", {
|
|
1671
|
-
denial,
|
|
1672
|
-
uuid,
|
|
1673
|
-
dispatchId: fault.dispatch.id,
|
|
1674
|
-
origin: fault.origin,
|
|
1675
|
-
source: buildEventSource(fault.event, fault)
|
|
1676
|
-
});
|
|
1677
|
-
else core.bus[PublishDefault]("unknownException", {
|
|
1678
|
-
uuid,
|
|
1679
|
-
dispatchId: fault.dispatch.id,
|
|
1680
|
-
error: denial,
|
|
1681
|
-
origin: fault.origin,
|
|
1682
|
-
...scalarActors(fault),
|
|
1683
|
-
metadata: metadataFor(fault)
|
|
1684
|
-
});
|
|
1685
|
-
}
|
|
1686
|
-
function reportRawFault(error, core, fault, uuid) {
|
|
1687
|
-
logUnknownFault(logger$2, uuid, error, core.config.errors);
|
|
1688
|
-
core.bus[PublishDefault]("unknownException", {
|
|
1689
|
-
uuid,
|
|
1690
|
-
dispatchId: fault.dispatch.id,
|
|
1691
|
-
error,
|
|
1692
|
-
origin: fault.origin,
|
|
1693
|
-
...scalarActors(fault),
|
|
1694
|
-
metadata: metadataFor(fault)
|
|
1695
|
-
});
|
|
1696
|
-
}
|
|
1697
|
-
function scalarActors(fault) {
|
|
1612
|
+
//#region src/miscellaneous/faultReports.ts
|
|
1613
|
+
function interactionFaultReport(interaction, dispatch) {
|
|
1698
1614
|
return {
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
if (fault.event) return {
|
|
1711
|
-
eventName: fault.event.name,
|
|
1712
|
-
handler: fault.event.handler,
|
|
1713
|
-
args: fault.event.args
|
|
1615
|
+
origin: dispatch.routeId,
|
|
1616
|
+
source: interactionFaultSource(dispatch, {
|
|
1617
|
+
customId: "customId" in interaction ? interaction.customId : null,
|
|
1618
|
+
userId: interaction.user.id,
|
|
1619
|
+
guildId: interaction.guildId,
|
|
1620
|
+
channelId: interaction.channelId,
|
|
1621
|
+
interactionId: interaction.id,
|
|
1622
|
+
raw: interaction
|
|
1623
|
+
}),
|
|
1624
|
+
...scalarActors(interaction.guild, interaction.user),
|
|
1625
|
+
metadata: interaction
|
|
1714
1626
|
};
|
|
1715
|
-
return fault.metadata;
|
|
1716
1627
|
}
|
|
1717
|
-
function
|
|
1628
|
+
function eventFaultReport(event) {
|
|
1718
1629
|
return {
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1630
|
+
origin: `event:${event.name}:${event.handler}`,
|
|
1631
|
+
source: {
|
|
1632
|
+
kind: "event",
|
|
1633
|
+
eventName: event.name,
|
|
1634
|
+
handler: event.handler,
|
|
1635
|
+
userId: event.user?.id ?? null,
|
|
1636
|
+
guildId: event.guild?.id ?? null,
|
|
1637
|
+
channelId: event.channelId,
|
|
1638
|
+
raw: event.args
|
|
1639
|
+
},
|
|
1640
|
+
...scalarActors(event.guild, event.user),
|
|
1641
|
+
metadata: {
|
|
1642
|
+
eventName: event.name,
|
|
1643
|
+
handler: event.handler,
|
|
1644
|
+
args: event.args
|
|
1645
|
+
}
|
|
1726
1646
|
};
|
|
1727
1647
|
}
|
|
1728
|
-
function
|
|
1729
|
-
const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
|
|
1730
|
-
const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
|
|
1648
|
+
function scalarActors(guild, user) {
|
|
1731
1649
|
return {
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
raw: interaction
|
|
1650
|
+
guild: guild ? {
|
|
1651
|
+
id: guild.id,
|
|
1652
|
+
name: guild.name
|
|
1653
|
+
} : void 0,
|
|
1654
|
+
user: user ? {
|
|
1655
|
+
id: user.id,
|
|
1656
|
+
username: user.username
|
|
1657
|
+
} : void 0
|
|
1741
1658
|
};
|
|
1742
1659
|
}
|
|
1743
|
-
function interactionKind(interaction) {
|
|
1744
|
-
if (interaction.isChatInputCommand()) return "slash";
|
|
1745
|
-
if (interaction.isContextMenuCommand()) return "context-menu";
|
|
1746
|
-
if (interaction.isButton()) return "button";
|
|
1747
|
-
if (interaction.isAnySelectMenu()) return "select";
|
|
1748
|
-
return "modal";
|
|
1749
|
-
}
|
|
1750
1660
|
|
|
1751
1661
|
//#endregion
|
|
1752
1662
|
//#region src/bot/handleEventFault.ts
|
|
1753
|
-
const logger$1 = new Logger("Faults", { channel: "errors" });
|
|
1754
1663
|
function handleEventFault(caught, fault, core) {
|
|
1755
|
-
const {
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
return
|
|
1759
|
-
|
|
1760
|
-
const error = asError(caught);
|
|
1761
|
-
const ignore = new Set(core.config.errors?.ignoreEventApiCodes ?? []);
|
|
1762
|
-
if (error instanceof DiscordAPIError && ignore.has(error.code)) {
|
|
1763
|
-
logger$1.debug(`swallowed api code ${error.code}`);
|
|
1764
|
-
return;
|
|
1765
|
-
}
|
|
1766
|
-
if (error instanceof Notice && !error.report) return;
|
|
1767
|
-
const actor = deriveEventActor(args);
|
|
1768
|
-
extractErrorResponse(error, core, {
|
|
1769
|
-
event: {
|
|
1770
|
-
name: eventName,
|
|
1664
|
+
const { handlerName, args, dispatch } = fault;
|
|
1665
|
+
new FaultBoundary(core, core.config.errors?.ignoreEventApiCodes, DiscordAPIError, dispatch, () => {
|
|
1666
|
+
const { channelId, guild, user } = deriveEventActor(args);
|
|
1667
|
+
return eventFaultReport({
|
|
1668
|
+
name: dispatch.route,
|
|
1771
1669
|
handler: handlerName,
|
|
1772
1670
|
args,
|
|
1773
|
-
channelId
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
user: actor.user
|
|
1779
|
-
});
|
|
1671
|
+
channelId,
|
|
1672
|
+
guild,
|
|
1673
|
+
user
|
|
1674
|
+
});
|
|
1675
|
+
}).report(caught);
|
|
1780
1676
|
}
|
|
1781
1677
|
|
|
1782
1678
|
//#endregion
|
|
@@ -1818,33 +1714,29 @@ const eventMiddleware = (ctor) => {
|
|
|
1818
1714
|
*/
|
|
1819
1715
|
var EventDispatcher = class {
|
|
1820
1716
|
core;
|
|
1717
|
+
inFlight;
|
|
1821
1718
|
logger = new Logger("Events", { channel: "events" });
|
|
1822
1719
|
isInitialized = false;
|
|
1823
1720
|
eventMap = /* @__PURE__ */ new Map();
|
|
1824
1721
|
middlewares = new MiddlewareRegistry(eventMiddleware);
|
|
1825
|
-
|
|
1722
|
+
onceRuns = new OnceTracker();
|
|
1826
1723
|
attachedEvents = /* @__PURE__ */ new Set();
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
loadedHandlers = [];
|
|
1830
|
-
loadedMiddlewares = [];
|
|
1831
|
-
hmrHandler;
|
|
1832
|
-
constructor(core) {
|
|
1724
|
+
loader;
|
|
1725
|
+
constructor(core, inFlight) {
|
|
1833
1726
|
this.core = core;
|
|
1727
|
+
this.inFlight = inFlight;
|
|
1834
1728
|
const eventsDir = this.core.config.bot.events.path;
|
|
1835
1729
|
if (!eventsDir) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["EventDispatcher", "events"]);
|
|
1836
|
-
|
|
1837
|
-
this.hmrHandler = new HmrModuleHandler$1({
|
|
1730
|
+
this.loader = new HandlerLoader({
|
|
1838
1731
|
handlersDir: eventsDir,
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
logger: this.logger
|
|
1732
|
+
middlewaresDir: hasKeys(this.core.config.bot.events, ["middlewares"]) ? this.core.config.bot.events.middlewares : void 0,
|
|
1733
|
+
middlewares: this.middlewares,
|
|
1734
|
+
logger: this.logger,
|
|
1735
|
+
isHandler: (value) => this.isEventHandlerClass(value),
|
|
1736
|
+
isMiddleware: (value) => this.isMiddlewareClass(value),
|
|
1737
|
+
storeHandler: (ctor) => this.storeHandler(ctor),
|
|
1738
|
+
removeHandler: (ctor, events) => this.unregisterHandler(ctor, events),
|
|
1739
|
+
artifactsOf: (ctor) => this.getArtifacts(ctor)
|
|
1848
1740
|
});
|
|
1849
1741
|
}
|
|
1850
1742
|
getArtifacts(ctor) {
|
|
@@ -1855,49 +1747,25 @@ var EventDispatcher = class {
|
|
|
1855
1747
|
async init() {
|
|
1856
1748
|
if (this.isInitialized) return;
|
|
1857
1749
|
this.isInitialized = true;
|
|
1858
|
-
|
|
1859
|
-
if (!handlersDir) return;
|
|
1860
|
-
const middlewareDir = hasKeys(this.core.config.bot.events, ["middlewares"]) ? this.core.config.bot.events.middlewares : void 0;
|
|
1861
|
-
this.loading = true;
|
|
1862
|
-
this.loadedHandlers.length = 0;
|
|
1863
|
-
this.loadedMiddlewares.length = 0;
|
|
1864
|
-
try {
|
|
1865
|
-
if (middlewareDir) await this.loadMiddlewares(middlewareDir);
|
|
1866
|
-
await this.loadHandlers(handlersDir);
|
|
1867
|
-
} finally {
|
|
1868
|
-
this.loading = false;
|
|
1869
|
-
}
|
|
1750
|
+
await this.loader.load();
|
|
1870
1751
|
this.attachToClient();
|
|
1871
1752
|
this.reportLoad();
|
|
1872
1753
|
}
|
|
1873
1754
|
reportLoad() {
|
|
1874
1755
|
const { utils } = this.logger;
|
|
1756
|
+
const { loadedHandlers, loadedMiddlewares } = this.loader;
|
|
1875
1757
|
utils.summary("Loaded", {
|
|
1876
|
-
"event handlers":
|
|
1877
|
-
middlewares:
|
|
1758
|
+
"event handlers": loadedHandlers.length,
|
|
1759
|
+
middlewares: loadedMiddlewares.length
|
|
1878
1760
|
}, "debug");
|
|
1879
|
-
if (
|
|
1761
|
+
if (loadedMiddlewares.length > 0) utils.block("Loaded event middlewares", utils.entries(loadedMiddlewares), "debug");
|
|
1880
1762
|
const perEvent = {};
|
|
1881
1763
|
for (const [event, handlers] of this.eventMap) perEvent[event] = handlers.length;
|
|
1882
|
-
utils.block("Loaded event handlers", [...utils.entries(
|
|
1883
|
-
}
|
|
1884
|
-
async loadHandlers(dir) {
|
|
1885
|
-
for await (const { fullPath, relativePath, imported } of importFiles(dir)) for (const val of Object.values(imported)) {
|
|
1886
|
-
if (!this.isEventHandlerClass(val)) continue;
|
|
1887
|
-
this.registerHandler(val, relativePath);
|
|
1888
|
-
this.hmrHandler?.trackHandler(fullPath, val);
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
async loadMiddlewares(dir) {
|
|
1892
|
-
for await (const { fullPath, relativePath, imported } of importFiles(dir)) for (const val of Object.values(imported)) {
|
|
1893
|
-
if (!this.isMiddlewareClass(val)) continue;
|
|
1894
|
-
this.registerMiddleware(val, relativePath);
|
|
1895
|
-
this.hmrHandler?.trackMiddleware(fullPath, val);
|
|
1896
|
-
}
|
|
1764
|
+
utils.block("Loaded event handlers", [...utils.entries(loadedHandlers), ...utils.counts(perEvent)], "debug");
|
|
1897
1765
|
}
|
|
1898
1766
|
/** @internal */
|
|
1899
1767
|
async onHmr(event) {
|
|
1900
|
-
await this.
|
|
1768
|
+
await this.loader.onHmr(event);
|
|
1901
1769
|
}
|
|
1902
1770
|
unregisterHandler(handlerClass, artifacts) {
|
|
1903
1771
|
const events = artifacts ?? [...this.eventMap.keys()];
|
|
@@ -1910,26 +1778,13 @@ var EventDispatcher = class {
|
|
|
1910
1778
|
if (handlers.length === 0) this.eventMap.delete(event);
|
|
1911
1779
|
}
|
|
1912
1780
|
}
|
|
1913
|
-
unregisterMiddleware(middlewareCtor) {
|
|
1914
|
-
this.middlewares.unregister(middlewareCtor);
|
|
1915
|
-
}
|
|
1916
|
-
registerMiddleware(middlewareCtor, relativePath) {
|
|
1917
|
-
const registration = this.middlewares.register(middlewareCtor);
|
|
1918
|
-
if (!registration || !this.loading) return;
|
|
1919
|
-
const scope = registration.keys ? `${registration.priority}, ${registration.keys.join(", ")}` : registration.priority;
|
|
1920
|
-
this.loadedMiddlewares.push({
|
|
1921
|
-
name: `${middlewareCtor.name} (${String(scope)})`,
|
|
1922
|
-
from: formatFilePath(relativePath)
|
|
1923
|
-
});
|
|
1924
|
-
}
|
|
1925
1781
|
async runMiddlewares(eventName, args, dispatch, ran) {
|
|
1926
1782
|
for (const Middleware of this.middlewares.chainFor(eventName)) try {
|
|
1927
|
-
const middleware = new Middleware(args, this.core, dispatch
|
|
1783
|
+
const middleware = new Middleware(args, this.core, dispatch);
|
|
1928
1784
|
ran.push(middleware);
|
|
1929
1785
|
await middleware.execute();
|
|
1930
1786
|
} catch (caught) {
|
|
1931
1787
|
handleEventFault(caught, {
|
|
1932
|
-
eventName: String(eventName),
|
|
1933
1788
|
handlerName: Middleware.name,
|
|
1934
1789
|
args,
|
|
1935
1790
|
dispatch
|
|
@@ -1946,8 +1801,8 @@ var EventDispatcher = class {
|
|
|
1946
1801
|
if (typeof obj !== "function") return false;
|
|
1947
1802
|
return obj.prototype instanceof EventMiddleware && Reflect.hasMetadata(EventMiddlewareMetadataKey, obj);
|
|
1948
1803
|
}
|
|
1949
|
-
|
|
1950
|
-
const
|
|
1804
|
+
storeHandler(handlerClass) {
|
|
1805
|
+
const entries = Reflect.getMetadata(EventMetadataKey, handlerClass);
|
|
1951
1806
|
const register = (key, frequency) => {
|
|
1952
1807
|
let handlers = this.eventMap.get(key);
|
|
1953
1808
|
if (!handlers) {
|
|
@@ -1960,16 +1815,8 @@ var EventDispatcher = class {
|
|
|
1960
1815
|
});
|
|
1961
1816
|
if (this.isInitialized && !this.attachedEvents.has(key)) this.attachListener(key);
|
|
1962
1817
|
};
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
const names = isStringArray(raw) ? raw : typeof raw === "string" ? [raw] : [];
|
|
1966
|
-
for (const name of names) register(name, "on");
|
|
1967
|
-
}
|
|
1968
|
-
const from = formatFilePath(relativePath);
|
|
1969
|
-
if (this.loading) this.loadedHandlers.push({
|
|
1970
|
-
name: handlerClass.name,
|
|
1971
|
-
from
|
|
1972
|
-
});
|
|
1818
|
+
for (const entry of entries) register(entry.event, entry.frequency);
|
|
1819
|
+
return true;
|
|
1973
1820
|
}
|
|
1974
1821
|
attachToClient() {
|
|
1975
1822
|
for (const [eventName] of this.eventMap) this.attachListener(eventName);
|
|
@@ -1980,28 +1827,21 @@ var EventDispatcher = class {
|
|
|
1980
1827
|
const handlerEntries = this.eventMap.get(eventName);
|
|
1981
1828
|
this.logger.debug(`Attaching ${paint.sky.bold(eventName)} to the client with ${paint.mute(handlerEntries?.length ?? 0)} handler(s)`);
|
|
1982
1829
|
this.core.bot.client.on(eventName, (...args) => {
|
|
1983
|
-
|
|
1984
|
-
const run = this.processEvent(eventName, args).catch((caught) => {
|
|
1985
|
-
const error = asError(caught);
|
|
1986
|
-
this.logger.error(`[${paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
|
|
1987
|
-
this.core.bus[PublishDefault]("unhandledEventError", { error });
|
|
1988
|
-
});
|
|
1989
|
-
this.inFlight.track(run);
|
|
1830
|
+
this.inFlight.admit(() => this.processEvent(eventName, args).catch((caught) => this.reportUnhandled(caught)));
|
|
1990
1831
|
});
|
|
1991
1832
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
return this.inFlight.drain(timeoutMs);
|
|
1833
|
+
reportUnhandled(caught) {
|
|
1834
|
+
const error = asError(caught);
|
|
1835
|
+
this.logger.error(`[${paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
|
|
1836
|
+
this.core.bus[PublishDefault]("unhandledEventError", { error });
|
|
1997
1837
|
}
|
|
1998
1838
|
async processEvent(eventName, args) {
|
|
1999
1839
|
const startedAt = performance.now();
|
|
2000
1840
|
const handlerEntries = this.eventMap.get(eventName);
|
|
2001
1841
|
if (!handlerEntries || handlerEntries.length === 0) return;
|
|
2002
|
-
const handlersToExecute = handlerEntries.filter((entry) =>
|
|
1842
|
+
const handlersToExecute = handlerEntries.filter((entry) => !this.onceRuns.isSpent(entry, eventName));
|
|
2003
1843
|
if (handlersToExecute.length === 0) return;
|
|
2004
|
-
const dispatch = new DispatchContext(
|
|
1844
|
+
const dispatch = new DispatchContext("event", eventName);
|
|
2005
1845
|
reportEventDispatching(this.core, dispatch.id, eventName, args);
|
|
2006
1846
|
const ran = [];
|
|
2007
1847
|
const handlers = [];
|
|
@@ -2009,13 +1849,8 @@ var EventDispatcher = class {
|
|
|
2009
1849
|
try {
|
|
2010
1850
|
stopped = await this.runMiddlewares(eventName, args, dispatch, ran);
|
|
2011
1851
|
if (stopped) return;
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
if (this.executedOnceHandlers.has(entry.ctor)) continue;
|
|
2015
|
-
this.executedOnceHandlers.add(entry.ctor);
|
|
2016
|
-
}
|
|
2017
|
-
handlers.push(await this.processHandler(eventName, entry.ctor, args, dispatch));
|
|
2018
|
-
}
|
|
1852
|
+
const results = handlersToExecute.filter((entry) => this.onceRuns.claim(entry, eventName)).map((entry) => this.processHandler(eventName, entry.ctor, args, dispatch));
|
|
1853
|
+
handlers.push(...await Promise.all(results));
|
|
2019
1854
|
} finally {
|
|
2020
1855
|
const result = eventResultFor(stopped, handlers);
|
|
2021
1856
|
reportEventDispatched(this.core, dispatch.id, eventName, result, startedAt);
|
|
@@ -2024,8 +1859,8 @@ var EventDispatcher = class {
|
|
|
2024
1859
|
}
|
|
2025
1860
|
async processHandler(eventName, Ctor, args, dispatch) {
|
|
2026
1861
|
try {
|
|
2027
|
-
this.logger.
|
|
2028
|
-
const handler = new Ctor(args, this.core, dispatch
|
|
1862
|
+
this.logger.trace(`Processing ${paint.sky.bold(eventName)} with ${paint.mute(Ctor.name)}`);
|
|
1863
|
+
const handler = new Ctor(args, this.core, dispatch);
|
|
2029
1864
|
const eventCtx = eventGateContext(eventName, args, this.core, dispatch);
|
|
2030
1865
|
await runHandlerGates(Ctor, eventCtx);
|
|
2031
1866
|
await handler.execute();
|
|
@@ -2035,7 +1870,6 @@ var EventDispatcher = class {
|
|
|
2035
1870
|
};
|
|
2036
1871
|
} catch (caught) {
|
|
2037
1872
|
handleEventFault(caught, {
|
|
2038
|
-
eventName: String(eventName),
|
|
2039
1873
|
handlerName: Ctor.name,
|
|
2040
1874
|
args,
|
|
2041
1875
|
dispatch
|
|
@@ -2048,6 +1882,40 @@ var EventDispatcher = class {
|
|
|
2048
1882
|
}
|
|
2049
1883
|
};
|
|
2050
1884
|
|
|
1885
|
+
//#endregion
|
|
1886
|
+
//#region src/bot/autocompleteResponder.ts
|
|
1887
|
+
function autocompleteResponder(interaction, core, dispatch) {
|
|
1888
|
+
return new AutocompleteResponder({
|
|
1889
|
+
bus: core.bus,
|
|
1890
|
+
dispatch,
|
|
1891
|
+
interactionId: interaction.id
|
|
1892
|
+
}, (choices) => interaction.respond(choices));
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
//#endregion
|
|
1896
|
+
//#region src/bot/controllers/gatewayRunConfig.ts
|
|
1897
|
+
function gatewayRunConfig(run) {
|
|
1898
|
+
const { interaction, core, dispatch, HandlerCtor, middlewares, logger } = run;
|
|
1899
|
+
return {
|
|
1900
|
+
dispatch,
|
|
1901
|
+
core,
|
|
1902
|
+
event: interaction,
|
|
1903
|
+
HandlerCtor,
|
|
1904
|
+
middlewares,
|
|
1905
|
+
apiError: DiscordAPIError,
|
|
1906
|
+
logger,
|
|
1907
|
+
interactionId: interaction.id,
|
|
1908
|
+
userId: interaction.user.id,
|
|
1909
|
+
guildId: interaction.guildId,
|
|
1910
|
+
customId: "customId" in interaction ? interaction.customId : null,
|
|
1911
|
+
faultDetails: () => interactionFaultReport(interaction, dispatch),
|
|
1912
|
+
target: interaction.isAutocomplete() ? { responder: autocompleteResponder(interaction, core, dispatch) } : {
|
|
1913
|
+
gateContext: () => interactionGateContext(interaction, core, dispatch),
|
|
1914
|
+
sender: new ReplySender(interaction, dispatch, core.bus)
|
|
1915
|
+
}
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
|
|
2051
1919
|
//#endregion
|
|
2052
1920
|
//#region src/bot/defaults/UnhandledRepliable.ts
|
|
2053
1921
|
var UnhandledRepliable = class extends InteractionHandler {
|
|
@@ -2065,166 +1933,73 @@ var UnhandledAutocomplete = class extends AutocompleteHandler {
|
|
|
2065
1933
|
};
|
|
2066
1934
|
|
|
2067
1935
|
//#endregion
|
|
2068
|
-
//#region src/bot/
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
* @internal
|
|
2074
|
-
*/
|
|
2075
|
-
const HARMLESS_API_CODES = /* @__PURE__ */ new Set([
|
|
2076
|
-
RESTJSONErrorCodes.UnknownInteraction,
|
|
2077
|
-
RESTJSONErrorCodes.InteractionHasAlreadyBeenAcknowledged,
|
|
2078
|
-
RESTJSONErrorCodes.UnknownMessage
|
|
2079
|
-
]);
|
|
2080
|
-
|
|
2081
|
-
//#endregion
|
|
2082
|
-
//#region src/bot/handleInteractionFault.ts
|
|
2083
|
-
const logger = new Logger("Faults", { channel: "errors" });
|
|
2084
|
-
async function handleInteractionFault(caught, interaction, core, dispatch, sender) {
|
|
2085
|
-
const { routeId } = dispatch;
|
|
2086
|
-
if (caught instanceof Silence) {
|
|
2087
|
-
if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger.debug(`Silence: ${caught.reason}`);
|
|
2088
|
-
return;
|
|
2089
|
-
}
|
|
2090
|
-
const error = asError(caught);
|
|
2091
|
-
const ignore = new Set(core.config.errors?.ignoreApiCodes ?? []);
|
|
2092
|
-
if (error instanceof DiscordAPIError && ignore.has(error.code)) {
|
|
2093
|
-
logger.debug(`swallowed api code ${error.code}`);
|
|
2094
|
-
return;
|
|
2095
|
-
}
|
|
2096
|
-
if (interaction.isAutocomplete()) {
|
|
2097
|
-
extractErrorResponse(error, core, {
|
|
2098
|
-
origin: routeId,
|
|
2099
|
-
dispatch,
|
|
2100
|
-
guild: interaction.guild,
|
|
2101
|
-
user: interaction.user,
|
|
2102
|
-
metadata: interaction
|
|
2103
|
-
});
|
|
2104
|
-
await sendEmptyChoices(interaction, core, dispatch);
|
|
2105
|
-
return;
|
|
2106
|
-
}
|
|
2107
|
-
const { response } = extractErrorResponse(error, core, {
|
|
2108
|
-
interaction,
|
|
2109
|
-
origin: routeId,
|
|
2110
|
-
dispatch,
|
|
2111
|
-
guild: interaction.guild,
|
|
2112
|
-
user: interaction.user,
|
|
2113
|
-
metadata: interaction
|
|
2114
|
-
});
|
|
2115
|
-
await sendGuarded(sender ?? new ReplySender(interaction, dispatch, core.bus), response, error instanceof Notice ? error.ephemeral : true);
|
|
2116
|
-
}
|
|
2117
|
-
async function sendEmptyChoices(interaction, core, dispatch) {
|
|
2118
|
-
const telemetry = {
|
|
2119
|
-
bus: core.bus,
|
|
2120
|
-
dispatch,
|
|
2121
|
-
interactionId: interaction.id
|
|
2122
|
-
};
|
|
2123
|
-
try {
|
|
2124
|
-
await reportedWrite(telemetry, "respond", () => interaction.respond([]));
|
|
2125
|
-
} catch (error) {
|
|
2126
|
-
logger.debug(`autocomplete empty-choices send failed: ${String(error)}`);
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
async function sendGuarded(sender, response, ephemeral) {
|
|
2130
|
-
try {
|
|
2131
|
-
await sender.send(response, { ephemeral });
|
|
2132
|
-
} catch (error) {
|
|
2133
|
-
if (error instanceof DiscordAPIError && HARMLESS_API_CODES.has(error.code)) {
|
|
2134
|
-
logger.debug(`reply send hit harmless code ${error.code}`);
|
|
2135
|
-
return;
|
|
2136
|
-
}
|
|
2137
|
-
logger.error("reply send failed", error);
|
|
2138
|
-
}
|
|
1936
|
+
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
1937
|
+
function slashRouteOf(interaction) {
|
|
1938
|
+
const group = interaction.options.getSubcommandGroup(false) ?? void 0;
|
|
1939
|
+
const subcommand = interaction.options.getSubcommand(false) ?? void 0;
|
|
1940
|
+
return buildSlashRoute(interaction.commandName, subcommand, group);
|
|
2139
1941
|
}
|
|
2140
1942
|
|
|
2141
1943
|
//#endregion
|
|
2142
1944
|
//#region src/bot/controllers/InteractionDispatcher.ts
|
|
1945
|
+
const UNHANDLED = {
|
|
1946
|
+
repliable: UnhandledRepliable,
|
|
1947
|
+
autocomplete: UnhandledAutocomplete
|
|
1948
|
+
};
|
|
2143
1949
|
var InteractionDispatcher = class {
|
|
2144
1950
|
core;
|
|
1951
|
+
inFlight;
|
|
2145
1952
|
logger = new Logger("Interactions", { channel: "interactions" });
|
|
2146
1953
|
isInitialized = false;
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
[InteractionKind.StringMenu]: /* @__PURE__ */ new Map(),
|
|
2152
|
-
[InteractionKind.UserMenu]: /* @__PURE__ */ new Map(),
|
|
2153
|
-
[InteractionKind.RoleMenu]: /* @__PURE__ */ new Map(),
|
|
2154
|
-
[InteractionKind.ChannelMenu]: /* @__PURE__ */ new Map(),
|
|
2155
|
-
[InteractionKind.MentionableMenu]: /* @__PURE__ */ new Map(),
|
|
2156
|
-
[InteractionKind.MessageContextMenu]: /* @__PURE__ */ new Map(),
|
|
2157
|
-
[InteractionKind.UserContextMenu]: /* @__PURE__ */ new Map(),
|
|
2158
|
-
[InteractionKind.Autocomplete]: /* @__PURE__ */ new Map()
|
|
2159
|
-
};
|
|
2160
|
-
handlerFiles = /* @__PURE__ */ new Map();
|
|
1954
|
+
routes = new RouteRegistry();
|
|
1955
|
+
get maps() {
|
|
1956
|
+
return this.routes.maps;
|
|
1957
|
+
}
|
|
2161
1958
|
keysToIgnore = /* @__PURE__ */ new Set();
|
|
2162
1959
|
middlewares = new MiddlewareRegistry(interactionMiddleware);
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
loadedHandlers = [];
|
|
2166
|
-
loadedMiddlewares = [];
|
|
2167
|
-
hmrHandler;
|
|
2168
|
-
constructor(core) {
|
|
1960
|
+
loader;
|
|
1961
|
+
constructor(core, inFlight) {
|
|
2169
1962
|
this.core = core;
|
|
1963
|
+
this.inFlight = inFlight;
|
|
2170
1964
|
const ignoredKeysFromConfig = hasKeys(this.core.config.bot.interactions, ["ignoreCustomIds"]) ? this.core.config.bot.interactions.ignoreCustomIds : void 0;
|
|
2171
1965
|
if (ignoredKeysFromConfig) for (const ignoredKey of ignoredKeysFromConfig) this.keysToIgnore.add(ignoredKey);
|
|
2172
1966
|
this.keysToIgnore.add(CONFIRM_DEF);
|
|
2173
1967
|
const interactionsDir = this.core.config.bot.interactions.path;
|
|
2174
1968
|
if (!interactionsDir) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["InteractionDispatcher", "interactions"]);
|
|
2175
|
-
|
|
2176
|
-
this.hmrHandler = new HmrModuleHandler$1({
|
|
1969
|
+
this.loader = new HandlerLoader({
|
|
2177
1970
|
handlersDir: interactionsDir,
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
logger: this.logger
|
|
1971
|
+
middlewaresDir: hasKeys(this.core.config.bot.interactions, ["middlewares"]) ? this.core.config.bot.interactions.middlewares : void 0,
|
|
1972
|
+
middlewares: this.middlewares,
|
|
1973
|
+
logger: this.logger,
|
|
1974
|
+
isHandler: (value) => this.isHandlerClass(value),
|
|
1975
|
+
isMiddleware: (value) => this.isMiddlewareClass(value),
|
|
1976
|
+
storeHandler: (ctor, from) => this.routes.register(ctor, from),
|
|
1977
|
+
removeHandler: (ctor, routes) => this.routes.unregister(ctor, routes),
|
|
1978
|
+
artifactsOf: (ctor) => this.routes.routesOf(ctor)
|
|
2187
1979
|
});
|
|
2188
1980
|
}
|
|
2189
1981
|
warnUnhandledRoutes(commandLeaves) {
|
|
2190
|
-
|
|
1982
|
+
this.routes.warnUnhandledRoutes(this.logger, commandLeaves);
|
|
2191
1983
|
}
|
|
2192
1984
|
warnUnhandledContextMenuRoutes(leaves) {
|
|
2193
|
-
|
|
2194
|
-
for (const name of leaves.message) if (!this.maps[InteractionKind.MessageContextMenu].has(name)) this.logger.warn(`Message context menu ${paint.sky.bold(name)} has no registered ${paint.bold("@ContextMenuRoute")} handler.`);
|
|
2195
|
-
}
|
|
2196
|
-
getArtifacts(handlerClass) {
|
|
2197
|
-
return interactionRoutesOf(handlerClass).map(([routeType, routes]) => ({
|
|
2198
|
-
routeType,
|
|
2199
|
-
routes
|
|
2200
|
-
}));
|
|
1985
|
+
this.routes.warnUnhandledContextMenuRoutes(this.logger, leaves);
|
|
2201
1986
|
}
|
|
2202
1987
|
async init() {
|
|
2203
1988
|
if (this.isInitialized) return;
|
|
2204
1989
|
this.isInitialized = true;
|
|
2205
|
-
|
|
2206
|
-
if (!handlersDir) return;
|
|
2207
|
-
const middlewareDir = hasKeys(this.core.config.bot.interactions, ["middlewares"]) ? this.core.config.bot.interactions.middlewares : void 0;
|
|
2208
|
-
this.loading = true;
|
|
2209
|
-
this.loadedHandlers.length = 0;
|
|
2210
|
-
this.loadedMiddlewares.length = 0;
|
|
2211
|
-
try {
|
|
2212
|
-
if (middlewareDir) await this.loadMiddlewares(middlewareDir);
|
|
2213
|
-
await this.loadHandlers(handlersDir);
|
|
2214
|
-
} finally {
|
|
2215
|
-
this.loading = false;
|
|
2216
|
-
}
|
|
1990
|
+
await this.loader.load();
|
|
2217
1991
|
this.attachToClient();
|
|
2218
1992
|
this.reportLoad();
|
|
2219
1993
|
}
|
|
2220
1994
|
reportLoad() {
|
|
2221
1995
|
const { utils } = this.logger;
|
|
1996
|
+
const { loadedHandlers, loadedMiddlewares } = this.loader;
|
|
2222
1997
|
utils.summary("Loaded", {
|
|
2223
|
-
"interaction handlers":
|
|
2224
|
-
middlewares:
|
|
1998
|
+
"interaction handlers": loadedHandlers.length,
|
|
1999
|
+
middlewares: loadedMiddlewares.length
|
|
2225
2000
|
}, "debug");
|
|
2226
|
-
if (
|
|
2227
|
-
utils.block("Loaded interaction handlers", [...utils.entries(
|
|
2001
|
+
if (loadedMiddlewares.length > 0) utils.block("Loaded middlewares", utils.entries(loadedMiddlewares), "debug");
|
|
2002
|
+
utils.block("Loaded interaction handlers", [...utils.entries(loadedHandlers), ...utils.counts({
|
|
2228
2003
|
slash: this.maps[InteractionKind.Slash].size,
|
|
2229
2004
|
buttons: this.maps[InteractionKind.Button].size,
|
|
2230
2005
|
modals: this.maps[InteractionKind.Modal].size,
|
|
@@ -2238,29 +2013,6 @@ var InteractionDispatcher = class {
|
|
|
2238
2013
|
autocomplete: this.maps[InteractionKind.Autocomplete].size
|
|
2239
2014
|
})], "debug");
|
|
2240
2015
|
}
|
|
2241
|
-
async loadHandlers(dir) {
|
|
2242
|
-
for await (const { fullPath, relativePath, imported } of importFiles(dir)) for (const val of Object.values(imported)) {
|
|
2243
|
-
if (!this.isHandlerClass(val)) continue;
|
|
2244
|
-
this.registerHandler(val, relativePath);
|
|
2245
|
-
this.hmrHandler?.trackHandler(fullPath, val);
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
|
-
async loadMiddlewares(dir) {
|
|
2249
|
-
for await (const { fullPath, relativePath, imported } of importFiles(dir)) for (const val of Object.values(imported)) {
|
|
2250
|
-
if (!this.isMiddlewareClass(val)) continue;
|
|
2251
|
-
this.registerMiddleware(val, relativePath);
|
|
2252
|
-
this.hmrHandler?.trackMiddleware(fullPath, val);
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
registerMiddleware(middlewareCtor, relativePath) {
|
|
2256
|
-
const metadata = this.middlewares.register(middlewareCtor);
|
|
2257
|
-
if (!metadata || !this.loading) return;
|
|
2258
|
-
const scope = metadata.keys ? `${metadata.priority}, ${metadata.keys.join(", ")}` : metadata.priority;
|
|
2259
|
-
this.loadedMiddlewares.push({
|
|
2260
|
-
name: `${middlewareCtor.name} (${String(scope)})`,
|
|
2261
|
-
from: formatFilePath(relativePath)
|
|
2262
|
-
});
|
|
2263
|
-
}
|
|
2264
2016
|
isHandlerClass(obj) {
|
|
2265
2017
|
if (typeof obj !== "function") return false;
|
|
2266
2018
|
return obj.prototype instanceof InteractionHandler && Reflect.hasMetadata(InteractionMetadataKey, obj) || obj.prototype instanceof AutocompleteHandler && Reflect.hasMetadata(InteractionMetadataKey, obj);
|
|
@@ -2269,239 +2021,47 @@ var InteractionDispatcher = class {
|
|
|
2269
2021
|
if (typeof obj !== "function") return false;
|
|
2270
2022
|
return obj.prototype instanceof InteractionMiddleware && Reflect.hasMetadata(InteractionMiddlewareMetadataKey, obj);
|
|
2271
2023
|
}
|
|
2272
|
-
registerHandler(handlerClass, relativePath) {
|
|
2273
|
-
const from = formatFilePath(relativePath);
|
|
2274
|
-
const writes = [];
|
|
2275
|
-
for (const [kind, routes] of interactionRoutesOf(handlerClass)) for (const route of routes) {
|
|
2276
|
-
const existing = this.maps[kind].get(route);
|
|
2277
|
-
if (existing && existing !== handlerClass) throw new SeedcordTypeError(SeedcordErrorCode.CoreDuplicateName, [
|
|
2278
|
-
"route",
|
|
2279
|
-
`${kind}:${route}`,
|
|
2280
|
-
[`${paint.bold(existing.name)} (${paint.path(this.handlerFiles.get(existing) ?? "unknown file")})`, `${paint.bold(handlerClass.name)} (${paint.path(from)})`]
|
|
2281
|
-
]);
|
|
2282
|
-
writes.push({
|
|
2283
|
-
kind,
|
|
2284
|
-
route
|
|
2285
|
-
});
|
|
2286
|
-
}
|
|
2287
|
-
if (writes.length === 0) return;
|
|
2288
|
-
for (const { kind, route } of writes) this.maps[kind].set(route, handlerClass);
|
|
2289
|
-
this.handlerFiles.set(handlerClass, from);
|
|
2290
|
-
if (this.loading) this.loadedHandlers.push({
|
|
2291
|
-
name: handlerClass.name,
|
|
2292
|
-
from
|
|
2293
|
-
});
|
|
2294
|
-
}
|
|
2295
2024
|
async onHmr(event) {
|
|
2296
|
-
await this.
|
|
2297
|
-
}
|
|
2298
|
-
unregisterHandler(handlerClass, artifacts) {
|
|
2299
|
-
this.handlerFiles.delete(handlerClass);
|
|
2300
|
-
for (const { routeType, routes } of artifacts ?? this.getArtifacts(handlerClass)) for (const route of routes) this.maps[routeType].delete(route);
|
|
2301
|
-
}
|
|
2302
|
-
unregisterMiddleware(middlewareCtor) {
|
|
2303
|
-
this.middlewares.unregister(middlewareCtor);
|
|
2025
|
+
await this.loader.onHmr(event);
|
|
2304
2026
|
}
|
|
2305
2027
|
attachToClient() {
|
|
2306
2028
|
this.core.bot.client.on(Events.InteractionCreate, (interaction) => {
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2029
|
+
this.inFlight.admit(() => {
|
|
2030
|
+
this.core.bus[PublishDefault]("anyInteraction", { interaction });
|
|
2031
|
+
return this.handleInteraction(interaction).catch((caught) => {
|
|
2032
|
+
const error = asError(caught);
|
|
2033
|
+
this.logger.error(`[${paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
|
|
2034
|
+
this.core.bus[PublishDefault]("unhandledInteractionError", { error });
|
|
2035
|
+
});
|
|
2313
2036
|
});
|
|
2314
|
-
this.inFlight.track(run);
|
|
2315
2037
|
});
|
|
2316
2038
|
}
|
|
2317
|
-
stopAccepting() {
|
|
2318
|
-
this.inFlight.close();
|
|
2319
|
-
}
|
|
2320
|
-
drain(timeoutMs) {
|
|
2321
|
-
return this.inFlight.drain(timeoutMs);
|
|
2322
|
-
}
|
|
2323
2039
|
async handleCustomIdInteraction(interaction, kind) {
|
|
2324
2040
|
if ([...this.keysToIgnore].some((matcher) => matcher.owns(interaction.customId))) return;
|
|
2325
|
-
|
|
2326
|
-
if (!prefix) this.logger.warn(`${paint.sky.bold(kind)} has invalid customId: ${paint.mute(interaction.customId)}`);
|
|
2327
|
-
await this.processInteraction(interaction, kind, () => prefix);
|
|
2041
|
+
await this.processInteraction(interaction, kind, prefixOf(interaction.customId));
|
|
2328
2042
|
}
|
|
2329
|
-
async processInteraction(interaction, kind,
|
|
2330
|
-
const
|
|
2331
|
-
const
|
|
2332
|
-
const queuedMs = queuedMsFor(interaction.id);
|
|
2333
|
-
const matched = this.maps[kind].get(key);
|
|
2334
|
-
const HandlerCtor = matched ?? fallback;
|
|
2335
|
-
const dispatch = new DispatchContext(`${kind}:${key || "unrouted"}`);
|
|
2336
|
-
const report = this.reporterFor({
|
|
2043
|
+
async processInteraction(interaction, kind, key) {
|
|
2044
|
+
const { fallback, ctor: HandlerCtor } = matchRoute(this.maps, kind, key, UNHANDLED);
|
|
2045
|
+
const config = gatewayRunConfig({
|
|
2337
2046
|
interaction,
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
routeId: dispatch.routeId
|
|
2047
|
+
core: this.core,
|
|
2048
|
+
dispatch: new DispatchContext(kind, key, fallback),
|
|
2049
|
+
HandlerCtor,
|
|
2050
|
+
middlewares: this.middlewares,
|
|
2051
|
+
logger: this.logger
|
|
2344
2052
|
});
|
|
2345
|
-
|
|
2346
|
-
const ran = [];
|
|
2347
|
-
let result = { outcome: "handled" };
|
|
2348
|
-
try {
|
|
2349
|
-
const handler = this.buildHandler(HandlerCtor, interaction, dispatch, key, !matched);
|
|
2350
|
-
if (handler instanceof RepliableHandler) sender = handler.sender;
|
|
2351
|
-
const refusal = await this.refusalBeforeHandler({
|
|
2352
|
-
HandlerCtor,
|
|
2353
|
-
kind,
|
|
2354
|
-
interaction,
|
|
2355
|
-
dispatch,
|
|
2356
|
-
sender,
|
|
2357
|
-
ran,
|
|
2358
|
-
declaredRoute: matched ? dispatch.routeId : void 0
|
|
2359
|
-
});
|
|
2360
|
-
if (refusal) {
|
|
2361
|
-
result = resultFor(refusal.caught);
|
|
2362
|
-
await this.answer(refusal.caught, interaction, dispatch, sender, report);
|
|
2363
|
-
return;
|
|
2364
|
-
}
|
|
2365
|
-
await handler.execute();
|
|
2366
|
-
report("handled");
|
|
2367
|
-
} catch (caught) {
|
|
2368
|
-
if (result.outcome === "handled") result = resultFor(caught);
|
|
2369
|
-
await this.answer(caught, interaction, dispatch, sender, report);
|
|
2370
|
-
} finally {
|
|
2371
|
-
await runAfter(ran, result, this.logger);
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
async answer(caught, interaction, dispatch, sender, report) {
|
|
2375
|
-
try {
|
|
2376
|
-
await handleInteractionFault(caught, interaction, this.core, dispatch, sender);
|
|
2377
|
-
} finally {
|
|
2378
|
-
report(outcomeFor(caught));
|
|
2379
|
-
}
|
|
2380
|
-
}
|
|
2381
|
-
buildHandler(HandlerCtor, interaction, dispatch, key, isFallback) {
|
|
2382
|
-
if (isFallback) this.logger.warn(`No handler found for key ${paint.sky.bold(key)}. Falling back to ${HandlerCtor.name}.`);
|
|
2383
|
-
this.logger.debug(`Processing ${paint.sky.bold(key)} with ${paint.mute(HandlerCtor.name)}`);
|
|
2384
|
-
return new HandlerCtor(interaction, this.core, dispatch);
|
|
2385
|
-
}
|
|
2386
|
-
reporterFor(row) {
|
|
2387
|
-
let reported = false;
|
|
2388
|
-
return (outcome) => {
|
|
2389
|
-
if (reported) return;
|
|
2390
|
-
reported = true;
|
|
2391
|
-
reportDispatch(this.core.bus, {
|
|
2392
|
-
dispatchId: row.dispatchId,
|
|
2393
|
-
routeId: row.routeId,
|
|
2394
|
-
interactionId: row.interaction.id,
|
|
2395
|
-
kind: row.kind,
|
|
2396
|
-
outcome,
|
|
2397
|
-
startedAt: row.startedAt,
|
|
2398
|
-
fallback: row.fallback,
|
|
2399
|
-
userId: row.interaction.user.id,
|
|
2400
|
-
guildId: row.interaction.guildId,
|
|
2401
|
-
queuedMs: row.queuedMs
|
|
2402
|
-
});
|
|
2403
|
-
};
|
|
2404
|
-
}
|
|
2405
|
-
async refusalBeforeHandler(step) {
|
|
2406
|
-
const { HandlerCtor, kind, interaction, dispatch, sender, ran, declaredRoute } = step;
|
|
2407
|
-
if (kind !== InteractionKind.Autocomplete && sender) try {
|
|
2408
|
-
await this.runMiddlewares(kind, interaction, dispatch, sender, ran);
|
|
2409
|
-
} catch (caught) {
|
|
2410
|
-
return { caught };
|
|
2411
|
-
}
|
|
2412
|
-
if (interaction.isAutocomplete()) return null;
|
|
2413
|
-
return this.gateRefusal(HandlerCtor, interaction, dispatch, declaredRoute);
|
|
2414
|
-
}
|
|
2415
|
-
async runMiddlewares(kind, interaction, dispatch, sender, ran) {
|
|
2416
|
-
for (const Middleware of this.middlewares.chainFor(kind)) {
|
|
2417
|
-
const middleware = new Middleware(interaction, this.core, dispatch, sender);
|
|
2418
|
-
ran.push(middleware);
|
|
2419
|
-
await middleware.execute();
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
async gateRefusal(HandlerCtor, interaction, dispatch, declaredRoute) {
|
|
2423
|
-
const monitor = slowGateMonitor();
|
|
2424
|
-
try {
|
|
2425
|
-
await runHandlerGates(HandlerCtor, interactionGateContext(interaction, this.core, dispatch), declaredRoute, monitor?.observe);
|
|
2426
|
-
return null;
|
|
2427
|
-
} catch (caught) {
|
|
2428
|
-
return { caught };
|
|
2429
|
-
} finally {
|
|
2430
|
-
monitor?.report(declaredRoute ?? null);
|
|
2431
|
-
}
|
|
2053
|
+
await (await new InteractionRun(config).prepare())?.();
|
|
2432
2054
|
}
|
|
2433
2055
|
async handleInteraction(interaction) {
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
break;
|
|
2444
|
-
case interaction.isStringSelectMenu():
|
|
2445
|
-
await this.handleStringSelectMenu(interaction);
|
|
2446
|
-
break;
|
|
2447
|
-
case interaction.isUserSelectMenu():
|
|
2448
|
-
await this.handleUserSelectMenu(interaction);
|
|
2449
|
-
break;
|
|
2450
|
-
case interaction.isRoleSelectMenu():
|
|
2451
|
-
await this.handleRoleSelectMenu(interaction);
|
|
2452
|
-
break;
|
|
2453
|
-
case interaction.isChannelSelectMenu():
|
|
2454
|
-
await this.handleChannelSelectMenu(interaction);
|
|
2455
|
-
break;
|
|
2456
|
-
case interaction.isMentionableSelectMenu():
|
|
2457
|
-
await this.handleMentionableSelectMenu(interaction);
|
|
2458
|
-
break;
|
|
2459
|
-
case interaction.isMessageContextMenuCommand():
|
|
2460
|
-
await this.handleMessageContextMenu(interaction);
|
|
2461
|
-
break;
|
|
2462
|
-
case interaction.isUserContextMenuCommand():
|
|
2463
|
-
await this.handleUserContextMenu(interaction);
|
|
2464
|
-
break;
|
|
2465
|
-
case interaction.isAutocomplete():
|
|
2466
|
-
await this.handleAutocomplete(interaction);
|
|
2467
|
-
break;
|
|
2468
|
-
default: this.logger.warn(`Unhandled interaction type: ${interaction.type}`);
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
async handleSlashCommand(interaction) {
|
|
2472
|
-
const route = slashRouteOf(interaction);
|
|
2473
|
-
await this.processInteraction(interaction, InteractionKind.Slash, () => route);
|
|
2474
|
-
}
|
|
2475
|
-
async handleButton(interaction) {
|
|
2476
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.Button);
|
|
2477
|
-
}
|
|
2478
|
-
async handleModal(interaction) {
|
|
2479
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.Modal);
|
|
2480
|
-
}
|
|
2481
|
-
async handleStringSelectMenu(interaction) {
|
|
2482
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.StringMenu);
|
|
2483
|
-
}
|
|
2484
|
-
async handleUserSelectMenu(interaction) {
|
|
2485
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.UserMenu);
|
|
2486
|
-
}
|
|
2487
|
-
async handleRoleSelectMenu(interaction) {
|
|
2488
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.RoleMenu);
|
|
2489
|
-
}
|
|
2490
|
-
async handleChannelSelectMenu(interaction) {
|
|
2491
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.ChannelMenu);
|
|
2492
|
-
}
|
|
2493
|
-
async handleMentionableSelectMenu(interaction) {
|
|
2494
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.MentionableMenu);
|
|
2495
|
-
}
|
|
2496
|
-
async handleMessageContextMenu(interaction) {
|
|
2497
|
-
await this.processInteraction(interaction, InteractionKind.MessageContextMenu, () => interaction.commandName);
|
|
2498
|
-
}
|
|
2499
|
-
async handleUserContextMenu(interaction) {
|
|
2500
|
-
await this.processInteraction(interaction, InteractionKind.UserContextMenu, () => interaction.commandName);
|
|
2501
|
-
}
|
|
2502
|
-
async handleAutocomplete(interaction) {
|
|
2503
|
-
const route = slashRouteOf(interaction);
|
|
2504
|
-
await this.processInteraction(interaction, InteractionKind.Autocomplete, () => route, UnhandledAutocomplete);
|
|
2056
|
+
if (interaction.isChatInputCommand()) await this.processInteraction(interaction, InteractionKind.Slash, slashRouteOf(interaction));
|
|
2057
|
+
else if (interaction.isAutocomplete()) await this.processInteraction(interaction, InteractionKind.Autocomplete, slashRouteOf(interaction));
|
|
2058
|
+
else if (interaction.isModalSubmit()) await this.handleCustomIdInteraction(interaction, InteractionKind.Modal);
|
|
2059
|
+
else if (interaction.isButton() || interaction.isAnySelectMenu()) {
|
|
2060
|
+
const kind = componentKindOf(interaction.componentType);
|
|
2061
|
+
if (kind) await this.handleCustomIdInteraction(interaction, kind);
|
|
2062
|
+
} else if (interaction.isMessageContextMenuCommand()) await this.processInteraction(interaction, InteractionKind.MessageContextMenu, interaction.commandName);
|
|
2063
|
+
else if (interaction.isUserContextMenuCommand()) await this.processInteraction(interaction, InteractionKind.UserContextMenu, interaction.commandName);
|
|
2064
|
+
else this.logger.warn(`Unhandled interaction type: ${interaction.type}`);
|
|
2505
2065
|
}
|
|
2506
2066
|
};
|
|
2507
2067
|
|
|
@@ -2535,6 +2095,7 @@ var Bot = class {
|
|
|
2535
2095
|
#logger = new Logger("Bot", { channel: "bot" });
|
|
2536
2096
|
/** @internal */
|
|
2537
2097
|
[loggerSlot] = this.#logger;
|
|
2098
|
+
#inFlight = new InFlight(this.#logger, "Dispatch");
|
|
2538
2099
|
#isInitialized = false;
|
|
2539
2100
|
#readyToDeploy = Promise.withResolvers();
|
|
2540
2101
|
#intents;
|
|
@@ -2553,8 +2114,8 @@ var Bot = class {
|
|
|
2553
2114
|
constructor(core) {
|
|
2554
2115
|
this.#intents = core.config.bot.clientOptions.intents;
|
|
2555
2116
|
this.#client = new Client(core.config.bot.clientOptions);
|
|
2556
|
-
if (core.config.bot.interactions.path) this.#interactions = new InteractionDispatcher(core);
|
|
2557
|
-
if (core.config.bot.events.path) this.#events = new EventDispatcher(core);
|
|
2117
|
+
if (core.config.bot.interactions.path) this.#interactions = new InteractionDispatcher(core, this.#inFlight);
|
|
2118
|
+
if (core.config.bot.events.path) this.#events = new EventDispatcher(core, this.#inFlight);
|
|
2558
2119
|
if (core.config.bot.commands.path) this.#commandRegistry = new CommandRegistry(core);
|
|
2559
2120
|
this.#emojiInjector = new EmojiInjector(core);
|
|
2560
2121
|
this.#registerStartupTasks(core);
|
|
@@ -2575,39 +2136,36 @@ var Bot = class {
|
|
|
2575
2136
|
return application.id;
|
|
2576
2137
|
}
|
|
2577
2138
|
#registerStartupTasks(core) {
|
|
2139
|
+
const interactions = this.#interactions;
|
|
2140
|
+
if (interactions) core.startup.addTask(StartupPhase.Configuration, "interactions-initialization", () => interactions.init());
|
|
2141
|
+
const events = this.#events;
|
|
2142
|
+
if (events) core.startup.addTask(StartupPhase.Configuration, "events-initialization", () => events.init());
|
|
2578
2143
|
core.startup.addTask(StartupPhase.Login, "bot-initialization", async (signal) => {
|
|
2579
2144
|
this.#logger.utils.initialization("Bot", "start");
|
|
2580
2145
|
await this.#init(signal);
|
|
2581
2146
|
this.#logger.utils.initialization("Bot", "end");
|
|
2582
2147
|
});
|
|
2583
2148
|
const registry = this.#commandRegistry;
|
|
2584
|
-
if (
|
|
2585
|
-
core.startup.addTask(StartupPhase.
|
|
2149
|
+
if (registry) core.startup.addTask(StartupPhase.Login, "command-deploy", (signal) => this.#deployCommands(registry, signal), COMMAND_DEPLOY_TIMEOUT_MS);
|
|
2150
|
+
core.startup.addTask(StartupPhase.Ready, "start-dispatch", () => {
|
|
2151
|
+
this.#inFlight.open();
|
|
2152
|
+
return Promise.resolve();
|
|
2153
|
+
});
|
|
2586
2154
|
}
|
|
2587
2155
|
#registerShutdownTasks(core) {
|
|
2588
2156
|
core.shutdown.addTask(ShutdownPhase.Unbind, "stop-dispatch", () => {
|
|
2589
|
-
this.#
|
|
2157
|
+
this.#inFlight.close();
|
|
2590
2158
|
return Promise.resolve();
|
|
2591
2159
|
}, UNBIND_TIMEOUT_MS);
|
|
2592
|
-
core.shutdown.addTask(ShutdownPhase.Drain, "drain-dispatch", () => this.#drain(), DRAIN_TASK_TIMEOUT_MS);
|
|
2160
|
+
core.shutdown.addTask(ShutdownPhase.Drain, "drain-dispatch", () => this.#inFlight.drain(DRAIN_WINDOW_MS), DRAIN_TASK_TIMEOUT_MS);
|
|
2593
2161
|
core.shutdown.addTask(ShutdownPhase.Logout, "stop-bot", async () => await this.#stop(), LOGOUT_TIMEOUT_MS);
|
|
2594
2162
|
}
|
|
2595
|
-
#stopAccepting() {
|
|
2596
|
-
this.#interactions?.stopAccepting();
|
|
2597
|
-
this.#events?.stopAccepting();
|
|
2598
|
-
}
|
|
2599
|
-
async #drain() {
|
|
2600
|
-
await Promise.allSettled([this.#interactions?.drain(DRAIN_WINDOW_MS), this.#events?.drain(DRAIN_WINDOW_MS)]);
|
|
2601
|
-
}
|
|
2602
2163
|
async #init(signal) {
|
|
2603
2164
|
if (this.#isInitialized) return;
|
|
2604
2165
|
this.#isInitialized = true;
|
|
2605
2166
|
signal.addEventListener("abort", () => this.#readyToDeploy.resolve(false), { once: true });
|
|
2606
2167
|
try {
|
|
2607
|
-
|
|
2608
|
-
if (this.#interactions) await this.#interactions.init();
|
|
2609
|
-
if (this.#events) await this.#events.init();
|
|
2610
|
-
await this.#login(token);
|
|
2168
|
+
await this.#login(this.botToken);
|
|
2611
2169
|
await this.#emojiInjector.init();
|
|
2612
2170
|
} catch (error) {
|
|
2613
2171
|
this.#readyToDeploy.resolve(false);
|
|
@@ -2629,8 +2187,7 @@ var Bot = class {
|
|
|
2629
2187
|
async #login(token) {
|
|
2630
2188
|
const ready = Promise.withResolvers();
|
|
2631
2189
|
this.#client.once(Events.ClientReady, () => ready.resolve());
|
|
2632
|
-
this.#client.login(token);
|
|
2633
|
-
await ready.promise;
|
|
2190
|
+
await Promise.all([this.#client.login(token), ready.promise]);
|
|
2634
2191
|
this.#logger.info(`Logged in as ${paint.sky.bold(this.#client.user?.username)}!`);
|
|
2635
2192
|
return this;
|
|
2636
2193
|
}
|
|
@@ -2646,7 +2203,7 @@ var Bot = class {
|
|
|
2646
2203
|
//#endregion
|
|
2647
2204
|
//#region src/version.ts
|
|
2648
2205
|
/** Package version */
|
|
2649
|
-
const version = "0.
|
|
2206
|
+
const version = "0.9.0-next.2";
|
|
2650
2207
|
|
|
2651
2208
|
//#endregion
|
|
2652
2209
|
//#region src/Seedcord.ts
|
|
@@ -2664,8 +2221,6 @@ var Seedcord = class extends ServerHost {
|
|
|
2664
2221
|
[HostVersion] = version;
|
|
2665
2222
|
#healthCheck;
|
|
2666
2223
|
#hmrManager;
|
|
2667
|
-
/** @see {@link Bus} */
|
|
2668
|
-
bus;
|
|
2669
2224
|
#subscribers;
|
|
2670
2225
|
/** @see {@link Bot} */
|
|
2671
2226
|
bot;
|
|
@@ -2675,24 +2230,20 @@ var Seedcord = class extends ServerHost {
|
|
|
2675
2230
|
get applicationId() {
|
|
2676
2231
|
return this.bot.applicationId;
|
|
2677
2232
|
}
|
|
2678
|
-
/** @see {@link IRateLimiter} */
|
|
2679
|
-
rateLimiter;
|
|
2680
2233
|
/**
|
|
2681
2234
|
* @param config - Bot configuration including paths and Discord client options
|
|
2682
2235
|
* @throws A **SeedcordError** when another `Seedcord` exists in this process and has not shut down.
|
|
2683
2236
|
* @throws A **SeedcordError** when the running Node or Bun is older than the range in `engines`.
|
|
2684
2237
|
*/
|
|
2685
2238
|
constructor(config) {
|
|
2686
|
-
super("gateway", new CoordinatedShutdown(config.lifecycle?.shutdownDeadline), new CoordinatedStartup());
|
|
2239
|
+
super("gateway", new CoordinatedShutdown(config.lifecycle?.shutdownDeadline), new CoordinatedStartup(), config.kv);
|
|
2687
2240
|
this.config = config;
|
|
2688
2241
|
bindBotColor(() => this.config.botColor);
|
|
2689
2242
|
this.#hmrManager = new HmrManager();
|
|
2690
2243
|
this.#hmrManager.init(connectDevChannel());
|
|
2691
|
-
this.bus = new Bus(this);
|
|
2692
2244
|
this.#subscribers = new SubscriberLoader(this.bus, config.subscribers.path);
|
|
2693
2245
|
this.bot = new Bot(this);
|
|
2694
2246
|
this.rest = this.bot.client.rest;
|
|
2695
|
-
this.rateLimiter = config.store ?? new MemoryRateLimiter();
|
|
2696
2247
|
this.#healthCheck = HealthCheck.fromOption(shutdownOf(this), config.healthCheck);
|
|
2697
2248
|
this.#registerStartupTasks();
|
|
2698
2249
|
}
|
|
@@ -2735,5 +2286,5 @@ var Seedcord = class extends ServerHost {
|
|
|
2735
2286
|
};
|
|
2736
2287
|
|
|
2737
2288
|
//#endregion
|
|
2738
|
-
export { ArraySource, AutocompleteHandler, BaseHandler, ButtonHandler, ChannelMenuHandler, ComponentHandler, CursorSource, Emojis, EventHandler, EventMiddleware, Gated, GatewayEmoji, HmrModuleHandler, IgnoreBots, InteractionHandler, InteractionMiddleware, MentionableMenuHandler, MessageContextMenuHandler, ModalHandler, Nsfw, Paginator, Plugin, RegisterEvent, RegisterEventMiddleware, RepliableHandler, ReplySender, RoleMenuHandler, Seedcord, SelectMenuHandler, SlashHandler, StringMenuHandler, Subscriber, UserContextMenuHandler, UserMenuHandler, WebhookLog, WinstonConsoleSink, WinstonFileSink, checkBotPermissions, checkPermissions, getConfirmation, hasPermsToAssign, version };
|
|
2289
|
+
export { ArraySource, AutocompleteHandler, BaseHandler, ButtonHandler, ChannelMenuHandler, ComponentHandler, CursorSource, Emojis, EventHandler, EventMiddleware, Gated, GatewayEmoji, HmrModuleHandler, IgnoreBots, InteractionHandler, InteractionMiddleware, MentionPrefix, MentionableMenuHandler, MessageContextMenuHandler, ModalHandler, Nsfw, Paginator, Plugin, RegisterEvent, RegisterEventMiddleware, RepliableHandler, ReplySender, RoleMenuHandler, Seedcord, SelectMenuHandler, SlashHandler, StringMenuHandler, Subscriber, UserContextMenuHandler, UserMenuHandler, WebhookLog, WinstonConsoleSink, WinstonFileSink, checkBotPermissions, checkPermissions, getConfirmation, hasPermsToAssign, version };
|
|
2739
2290
|
//# sourceMappingURL=index.mjs.map
|