@seedcord/gateway 0.9.0-next.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/dist/index.mjs CHANGED
@@ -1,22 +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, FixedScope, GateNotice, GatedMetadataKey, HmrManager, InteractionMetadataKey, InteractionMiddlewareMetadataKey, MiddlewareRegistry, MissingPermissions, NoticeCard, OnceTracker, PermissionNames, PublishDefault, SubscriberLoader, accessorStore, attachmentsOf, bindBotColor, busLoggerOf, clearStore, connectDevChannel, decodeComponentRoute, deferFlags, eventResultFor, guardedAccessor, interactionMiddleware, interactionRoutesOf, isEmojiTuple, logUnknownFault, outcomeFor, pickNotice, queuedMsFor, readBotToken, reportDispatch, reportedWrite, resultFor, runAfter, runHandlerGates, sendFlags, slowGateMonitor } from "@seedcord/core/internal";
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, CursorSourceBase, CustomId, DispatchContext, Fault, InteractionKind, Notice, PaginatorBase, RepliableHandler as RepliableHandler$1, ResolvedEmoji, RowComponent, Silence, Subscriber as Subscriber$1, WebhookLog as WebhookLog$1, assertPermissions, defineGate } from "@seedcord/core";
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
13
  import { HostAugmentTarget, HostVersion, SeedcordBrand } from "@seedcord/types/internal";
15
14
  import { Envapter } from "envapt";
16
- import { HmrModuleHandler, HmrModuleHandler as HmrModuleHandler$1 } from "@seedcord/core/hmr";
17
- import { formatFilePath, hasKeys, importFiles } from "@seedcord/utils";
18
- import * as crypto from "node:crypto";
15
+ import { hasKeys } from "@seedcord/utils";
16
+ import { asError, buildSlashRoute } from "@seedcord/utils/internal";
19
17
  import { prefixOf } from "@seedcord/custom-id";
18
+ import { HmrModuleHandler } from "@seedcord/core/hmr";
20
19
 
21
20
  export * from "@seedcord/errors"
22
21
 
@@ -79,12 +78,12 @@ installNodeConsole();
79
78
  */
80
79
  function RegisterEvent(...defs) {
81
80
  return function(constructor) {
82
- const existing = Reflect.getMetadata(EventMetadataKey, constructor) ?? [];
83
81
  const entries = defs.map(([event, options]) => ({
84
82
  event,
85
83
  frequency: options?.frequency ?? "on"
86
84
  }));
87
- Reflect.defineMetadata(EventMetadataKey, [...existing, ...entries], constructor);
85
+ assertUniqueKeys("@RegisterEvent", constructor, entries.map((entry) => entry.event));
86
+ Reflect.defineMetadata(EventMetadataKey, entries, constructor);
88
87
  };
89
88
  }
90
89
 
@@ -97,7 +96,7 @@ function RegisterEvent(...defs) {
97
96
  * handler) is a compile error at this line, and the error message shows the gate and the handler kind. Place it
98
97
  * above the route decorator.
99
98
  *
100
- * Combine gates with {@link and} or {@link or} to build a single gate from multiple arms.
99
+ * Combine gates into a single gate with {@link and} or {@link or}.
101
100
  *
102
101
  * @example
103
102
  * ```ts
@@ -190,19 +189,55 @@ const IgnoreBots = defineGate("IgnoreBots", (ctx) => {
190
189
  if (ctx.user?.bot) throw new Silence("actor is a bot");
191
190
  });
192
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
+
193
228
  //#endregion
194
229
  //#region src/bot/notices/index.ts
195
- var NotNsfw = class extends GateNotice {
196
- constructor(message = "This can only be used in an age-restricted channel.") {
197
- super(message);
230
+ var NotNsfw = class extends Notice {
231
+ constructor() {
232
+ super("This can only be used in an age-restricted channel.");
198
233
  this.summary = "be in an age-restricted channel";
199
234
  }
200
235
  };
201
236
  var RoleHigherThanMe = class extends Notice {
202
237
  role;
203
238
  botRole;
204
- constructor(message, role, botRole) {
205
- super(message);
239
+ constructor(role, botRole) {
240
+ super("Role is higher than me");
206
241
  this.role = role;
207
242
  this.botRole = botRole;
208
243
  }
@@ -211,11 +246,13 @@ var RoleHigherThanMe = class extends Notice {
211
246
  }
212
247
  };
213
248
  var CannotAssignBotRole = class extends Notice {
214
- constructor(message = "I cannot assign a managed role.") {
215
- super(message);
249
+ role;
250
+ constructor(role) {
251
+ super(`Cannot assign managed role ${role.name}`);
252
+ this.role = role;
216
253
  }
217
254
  render() {
218
- return { components: [new NoticeCard("I cannot assign a managed role.").component] };
255
+ return { components: [new NoticeCard(`I cannot assign <@&${this.role.id}>, a managed role.`).component] };
219
256
  }
220
257
  };
221
258
 
@@ -227,11 +264,11 @@ function channelIsNsfw(channel) {
227
264
  return "nsfw" in channel ? channel.nsfw : false;
228
265
  }
229
266
  /**
230
- * Requires an age-restricted channel, else refuses. Pass {@link GateNoticeOptions} to reword or replace the refusal.
267
+ * Requires an age-restricted channel, else refuses. Pass a Notice class as `notice` to replace the refusal.
231
268
  *
232
269
  * A thread inherits its parent channel's nsfw flag. ModalSubmit has no reliable channel, so it is excluded.
233
270
  *
234
- * @param options - Reword the default refusal with `message`, or replace it with `notice`.
271
+ * @param options - Replace the refusal with a Notice class as `notice`.
235
272
  *
236
273
  * @see {@link Gated}
237
274
  *
@@ -249,7 +286,7 @@ function channelIsNsfw(channel) {
249
286
  function Nsfw(options) {
250
287
  return defineGate("Nsfw", (ctx) => {
251
288
  if (channelIsNsfw(ctx.interaction.channel)) return;
252
- throw pickNotice(options, (message) => new NotNsfw(message));
289
+ throw new ((options?.notice) ?? NotNsfw)();
253
290
  });
254
291
  }
255
292
 
@@ -350,57 +387,68 @@ function mentionFor(subject) {
350
387
 
351
388
  //#endregion
352
389
  //#region src/bot/utilities/permissions/checkPermissions.ts
353
- function checkPermissions(a, b, c, d, e) {
354
- const { for: pFor, in: pIn, scope, inverse = false, missingNotice, dangerousNotice } = a instanceof Role || a instanceof GuildMember ? {
355
- for: a,
356
- in: b,
357
- scope: c,
358
- inverse: d ?? false,
359
- ...e
360
- } : a;
361
- const perms = pIn instanceof Guild ? pFor.permissions : pIn.permissionsFor(pFor, true);
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;
362
414
  assertPermissions({
363
- subject: mentionFor(pFor),
364
- permissions: perms.bitfield,
415
+ subject: mentionFor(target),
416
+ permissions: held.bitfield,
365
417
  scope,
366
418
  inverse,
367
- ...missingNotice && { missingNotice },
368
- ...dangerousNotice && { dangerousNotice }
419
+ notice
369
420
  });
370
421
  }
371
422
 
372
423
  //#endregion
373
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
+ }
374
430
  /**
375
- * Checks the bot's own permissions in a {@link Guild} or {@link TextChannel}. Refuses when a permission in
376
- * `scope` is missing, or with `inverse` when one is present. When the bot member is uncached, the refusal
377
- * reports every permission in `scope` as missing.
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.
378
436
  *
379
437
  * @example
380
438
  * ```ts
381
439
  * import { PermissionFlagsBits } from 'discord.js';
382
440
  * import { checkBotPermissions } from '@seedcord/gateway';
383
441
  *
384
- * checkBotPermissions(textChannel, [PermissionFlagsBits.SendMessages, PermissionFlagsBits.ViewChannel]);
442
+ * checkBotPermissions({ in: channel, scope: [PermissionFlagsBits.SendMessages, PermissionFlagsBits.ViewChannel] });
385
443
  * ```
386
444
  */
387
- function checkBotPermissions(target, scope, inverse = false, errors) {
388
- if (target instanceof Guild) {
389
- const me = target.members.me;
390
- if (!me) {
391
- const names = scope.map((bit) => PermissionNames.get(bit) ?? String(bit));
392
- throw new ((errors?.missingNotice) ?? MissingPermissions)(void 0, "The bot", names);
393
- }
394
- checkPermissions(me, target, scope, inverse, errors);
395
- return;
396
- }
397
- const me = target.guild.members.me;
398
- if (!me) {
399
- new Logger("checkBotPermissions", { channel: "errors" }).warn(`Bot member is unavailable in guild ${target.guild.id} while checking permissions in channel ${target.id}`);
400
- const names = scope.map((bit) => PermissionNames.get(bit) ?? String(bit));
401
- throw new ((errors?.missingNotice) ?? MissingPermissions)(void 0, "The bot", names);
402
- }
403
- 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
+ });
404
452
  }
405
453
 
406
454
  //#endregion
@@ -411,42 +459,31 @@ function botRoleOf(guild) {
411
459
  return botRole;
412
460
  }
413
461
  /**
414
- * Validates that the bot can assign a target role. Refuses when the target role is above the bot's, is managed, or the bot lacks Manage Roles.
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.
415
464
  *
416
- * @param roleOrOptions - Target role or complete options for the check
417
465
  * @throws A {@link Notice} when the check refuses.
418
- * @throws A **SeedcordError** when the bot has no role in the guild's role cache.
466
+ * @throws A **SeedcordError** when the guild's caches don't hold the bot's role or member.
419
467
  *
420
468
  * @example
421
469
  * ```ts
422
470
  * import { hasPermsToAssign } from '@seedcord/gateway';
423
471
  *
424
- * // Check if the bot can assign a specific role
425
- * hasPermsToAssign(role);
472
+ * hasPermsToAssign({ role });
426
473
  *
427
- * // Check with custom error handling
428
- * hasPermsToAssign({
429
- * targetRole: role,
430
- * noticeOverrides: {
431
- * higherNotice: CustomHigherNotice,
432
- * managedNotice: CustomManagedNotice,
433
- * missingNotice: CustomMissingPermissionsNotice
434
- * }
435
- * });
474
+ * // with your own Notice classes
475
+ * hasPermsToAssign({ role, higher: RoleTooHigh, managed: RoleIsManaged, missing: CannotManageRoles });
436
476
  * ```
437
477
  */
438
- function hasPermsToAssign(roleOrOptions) {
439
- const { targetRole, noticeOverrides: errors } = roleOrOptions instanceof Role ? {
440
- targetRole: roleOrOptions,
441
- noticeOverrides: void 0
442
- } : roleOrOptions;
443
- const HigherNotice = errors?.higherNotice ?? RoleHigherThanMe;
444
- const ManagedNotice = errors?.managedNotice ?? CannotAssignBotRole;
445
- const MissingNotice = errors?.missingNotice ?? MissingPermissions;
446
- const botRole = botRoleOf(targetRole.guild);
447
- if (targetRole.comparePositionTo(botRole) >= 0) throw new HigherNotice("Role is higher than me", targetRole, botRole);
448
- if (targetRole.managed) throw new ManagedNotice(`Cannot assign managed role ${targetRole.name}`);
449
- 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
+ });
450
487
  }
451
488
 
452
489
  //#endregion
@@ -511,126 +548,6 @@ async function getConfirmation(handler, prompt, options) {
511
548
  return confirmed;
512
549
  }
513
550
 
514
- //#endregion
515
- //#region src/bot/ReplySender.ts
516
- function isReplyFile(file) {
517
- return typeof file === "object" && "data" in file && ArrayBuffer.isView(file.data);
518
- }
519
- function toDjsFile(file) {
520
- if (!isReplyFile(file)) return file;
521
- return {
522
- attachment: Buffer.from(file.data),
523
- name: file.name,
524
- ...file.description !== void 0 && { description: file.description },
525
- ...file.title !== void 0 && { title: file.title }
526
- };
527
- }
528
- function seedState(interaction) {
529
- if (interaction.deferred && interaction.ephemeral === null) return "deferred-update";
530
- if (interaction.replied) return "replied";
531
- if (interaction.deferred) return "deferred-reply";
532
- return "unacked";
533
- }
534
- /** Writes interaction responses through discord.js. The repliable handler bases and the dispatcher construct it. */
535
- var ReplySender = class extends BaseReplySender {
536
- interaction;
537
- constructor(interaction, dispatch, bus) {
538
- super({
539
- bus,
540
- dispatch,
541
- interactionId: interaction.id
542
- }, seedState(interaction));
543
- this.interaction = interaction;
544
- }
545
- async writeReply(response, opts) {
546
- const result = await this.interaction.reply({
547
- ...this.replyOptions(response, sendFlags(opts)),
548
- withResponse: true
549
- });
550
- return this.createdMessage(result);
551
- }
552
- async writeDefer(opts) {
553
- await this.interaction.deferReply({ flags: deferFlags(opts) });
554
- }
555
- async writeDeferUpdate() {
556
- await this.sourceInteraction("deferUpdate").deferUpdate();
557
- }
558
- async writeUpdate(response) {
559
- const result = await this.sourceInteraction("update").update({
560
- ...this.updateOptions(response),
561
- withResponse: true
562
- });
563
- return this.createdMessage(result);
564
- }
565
- async writeFollowUp(response, opts) {
566
- return await this.interaction.followUp(this.replyOptions(response, sendFlags(opts)));
567
- }
568
- async writeEditOriginal(response) {
569
- return await this.interaction.editReply(this.editOptions(response));
570
- }
571
- async writeEditTarget(targetId, response) {
572
- return await this.interaction.webhook.editMessage(targetId, this.editOptions(response));
573
- }
574
- async writeDeleteOriginal() {
575
- await this.interaction.deleteReply();
576
- }
577
- async writeDeleteTarget(targetId) {
578
- await this.interaction.deleteReply(targetId);
579
- }
580
- async writeModal(data) {
581
- const { interaction } = this;
582
- if (interaction.isModalSubmit()) this.rejectModalSubmit();
583
- await interaction.showModal(data);
584
- }
585
- guardModalSource() {
586
- if (this.interaction.isModalSubmit()) this.rejectModalSubmit();
587
- }
588
- rejectModalSubmit() {
589
- throw new SeedcordError(SeedcordErrorCode.ReplyIllegalAckState, [
590
- "showModal",
591
- "this interaction is a modal submit",
592
- "A modal cannot open another modal.",
593
- this.routeId
594
- ]);
595
- }
596
- sourceInteraction(method) {
597
- const { interaction } = this;
598
- if (interaction.isMessageComponent()) return interaction;
599
- if (interaction.isModalSubmit() && interaction.isFromMessage()) return interaction;
600
- throw new SeedcordError(SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
601
- }
602
- replyOptions(response, flags) {
603
- const reply = this.serialize(response);
604
- return {
605
- components: reply.components,
606
- flags,
607
- ...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
608
- ...reply.files && { files: reply.files.map(toDjsFile) }
609
- };
610
- }
611
- editOptions(response) {
612
- const reply = this.serialize(response);
613
- return {
614
- components: reply.components,
615
- flags: MessageFlags.IsComponentsV2,
616
- ...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
617
- ...reply.files && { files: reply.files.map(toDjsFile) }
618
- };
619
- }
620
- updateOptions(response) {
621
- const reply = this.serialize(response);
622
- return {
623
- components: reply.components,
624
- flags: MessageFlags.IsComponentsV2,
625
- ...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
626
- ...reply.files && { files: reply.files.map(toDjsFile) }
627
- };
628
- }
629
- createdMessage(result) {
630
- return result.resource?.message ?? void 0;
631
- }
632
- };
633
-
634
551
  //#endregion
635
552
  //#region src/handlers/RepliableHandler.ts
636
553
  /**
@@ -647,8 +564,9 @@ var RepliableHandler = class extends RepliableHandler$1 {
647
564
  getEvent() {
648
565
  return this.event;
649
566
  }
650
- buildSender(event, core, dispatch) {
651
- return new ReplySender(event, dispatch, core.bus);
567
+ /** The user who sent this interaction. */
568
+ get user() {
569
+ return this.event.user;
652
570
  }
653
571
  };
654
572
 
@@ -691,8 +609,8 @@ var InteractionMiddleware = class extends RepliableHandler {
691
609
  * @typeParam Repliable - The interaction type this handler processes
692
610
  */
693
611
  var InteractionHandler = class extends RepliableHandler {
694
- constructor(event, core, dispatch) {
695
- super(event, core, dispatch);
612
+ constructor(event, core, dispatch, sender) {
613
+ super(event, core, dispatch, sender);
696
614
  }
697
615
  /**
698
616
  * Open a modal. Must be the initial response to this interaction. The modal kind rejects this call at
@@ -703,14 +621,6 @@ var InteractionHandler = class extends RepliableHandler {
703
621
  }
704
622
  };
705
623
 
706
- //#endregion
707
- //#region src/bot/utilities/miscellaneous/slashRouteOf.ts
708
- function slashRouteOf(interaction) {
709
- const group = interaction.options.getSubcommandGroup(false) ?? void 0;
710
- const subcommand = interaction.options.getSubcommand(false) ?? void 0;
711
- return buildSlashRoute(interaction.commandName, subcommand, group);
712
- }
713
-
714
624
  //#endregion
715
625
  //#region src/handlers/interaction/SlashHandler.ts
716
626
  /**
@@ -718,7 +628,7 @@ function slashRouteOf(interaction) {
718
628
  *
719
629
  * Pass the route from the generated registry as the generic, the same string as `@SlashRoute`, then read
720
630
  * `this.options` for a single command or `this.match` for several. Command authoring stays plain
721
- * discord.js, `seedcord codegen` reads its `toJSON()` to type these options.
631
+ * discord.js. `seedcord codegen` reads its `toJSON()` to type these options.
722
632
  *
723
633
  * @typeParam Route - One or more route keys from {@link SlashRegistry}, e.g. `'ban'` or `'ban' | 'kick'`.
724
634
  * @typeParam Cache - The interaction cache state. The command's contexts set it.
@@ -735,36 +645,32 @@ function slashRouteOf(interaction) {
735
645
  * ```
736
646
  */
737
647
  var SlashHandler = class extends InteractionHandler {
738
- /** The selected command's path, such as `search/docs`. */
739
- get route() {
740
- return slashRouteOf(this.event);
741
- }
742
648
  /**
743
649
  * The typed options for this command's route. Only the getters for kinds this command declares appear.
744
650
  * Each getter's name argument is restricted to that command's options of its kind.
745
651
  *
746
- * - A required option drops the null.
652
+ * - A required option's getter never returns `null`.
747
653
  *
748
654
  * - Choices narrow to their literal union.
749
655
  *
750
656
  * - A channel option declared with `addChannelTypes` narrows to those types.
751
657
  *
752
658
  * Use `this.event.options` directly for anything outside this view, such as `getSubcommand()`.
753
- * However, for the common case, the methods in this would suffice.
754
659
  */
755
660
  get options() {
756
661
  return this.event.options;
757
662
  }
758
663
  /**
759
- * Run the arm for the selected command. Use this only when the handler is registered for several
664
+ * Run the case for the selected command. Use this only when the handler is registered for several
760
665
  * routes.
761
666
  *
762
- * Provide one arm per registered route, keyed by its route string, and each arm receives that route's
763
- * own narrowed options. The arms must cover every route in the generic, a missing route or an unknown
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
764
669
  * key is a compile error.
765
670
  *
766
- * @param arms - One handler per registered route, keyed by route string.
767
- * @returns The result of the arm that ran.
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.
768
674
  *
769
675
  * @example
770
676
  * ```ts
@@ -779,29 +685,13 @@ var SlashHandler = class extends InteractionHandler {
779
685
  * }
780
686
  * ```
781
687
  */
782
- async match(arms) {
783
- const route = this.route;
784
- const arm = Object.hasOwn(arms, route) ? arms[route] : void 0;
785
- if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
786
- this.constructor.name,
787
- "command route",
788
- route
789
- ]);
790
- return await arm(this.options);
688
+ async match(cases) {
689
+ return await runCase(this, cases, "command route", this.route, this.options);
791
690
  }
792
691
  };
793
692
 
794
693
  //#endregion
795
694
  //#region src/handlers/interaction/ContextMenuHandler.ts
796
- function armFor(handler, arms, name) {
797
- const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
798
- if (typeof arm !== "function") throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
799
- handler.constructor.name,
800
- "context menu command",
801
- name
802
- ]);
803
- return arm;
804
- }
805
695
  /**
806
696
  * Base class for a user context-menu command handler (right-click a user).
807
697
  *
@@ -829,21 +719,18 @@ var UserContextMenuHandler = class extends InteractionHandler {
829
719
  get targetMember() {
830
720
  return this.event.targetMember;
831
721
  }
832
- /** The selected context-menu command's name, without the interaction-kind prefix. */
833
- get route() {
834
- return this.event.commandName;
835
- }
836
722
  /**
837
- * Run the arm for the selected command. Use this only when the handler is registered for several
723
+ * Run the case for the selected command. Use this only when the handler is registered for several
838
724
  * names.
839
725
  *
840
- * Provide one arm per registered name, keyed by the name Discord shows in the right-click menu. Each
841
- * arm receives the clicked user and that user's guild member, both narrowed to the cache state of that
842
- * one command. The arms must cover every name in the generic. A missing name or an unknown key is a
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
843
729
  * compile error.
844
730
  *
845
- * @param arms - One handler per registered command name.
846
- * @returns The result of the arm that ran.
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.
847
734
  *
848
735
  * @example
849
736
  * ```ts
@@ -858,8 +745,8 @@ var UserContextMenuHandler = class extends InteractionHandler {
858
745
  * }
859
746
  * ```
860
747
  */
861
- async match(arms) {
862
- return await armFor(this, arms, this.route)(this.target, this.targetMember);
748
+ async match(cases) {
749
+ return await runCase(this, cases, "context menu command", this.route, this.target, this.targetMember);
863
750
  }
864
751
  };
865
752
  /**
@@ -885,20 +772,17 @@ var MessageContextMenuHandler = class extends InteractionHandler {
885
772
  get target() {
886
773
  return this.event.targetMessage;
887
774
  }
888
- /** The selected context-menu command's name, without the interaction-kind prefix. */
889
- get route() {
890
- return this.event.commandName;
891
- }
892
775
  /**
893
- * Run the arm for the selected command. Use this only when the handler is registered for several
776
+ * Run the case for the selected command. Use this only when the handler is registered for several
894
777
  * names.
895
778
  *
896
- * Provide one arm per registered name, keyed by the name Discord shows in the right-click menu. Each
897
- * arm receives the clicked message, narrowed to the cache state of that one command. The arms must
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
898
781
  * cover every name in the generic. A missing name or an unknown key is a compile error.
899
782
  *
900
- * @param arms - One handler per registered command name.
901
- * @returns The result of the arm that ran.
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.
902
786
  *
903
787
  * @example
904
788
  * ```ts
@@ -913,8 +797,8 @@ var MessageContextMenuHandler = class extends InteractionHandler {
913
797
  * }
914
798
  * ```
915
799
  */
916
- async match(arms) {
917
- return await armFor(this, arms, this.route)(this.target);
800
+ async match(cases) {
801
+ return await runCase(this, cases, "context menu command", this.route, this.target);
918
802
  }
919
803
  };
920
804
 
@@ -955,8 +839,14 @@ var BaseHandler = class extends BaseHandler$1 {
955
839
  * ```
956
840
  */
957
841
  var AutocompleteHandler = class extends BaseHandler {
958
- constructor(event, core, dispatch) {
842
+ constructor(event, core, dispatch, responder) {
959
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;
960
850
  }
961
851
  decodedFocused;
962
852
  /**
@@ -974,16 +864,17 @@ var AutocompleteHandler = class extends BaseHandler {
974
864
  return focused;
975
865
  }
976
866
  /**
977
- * Run the arm for the focused field. Because an autocomplete always dispatches by which field is
867
+ * Run the case for the focused field. Because an autocomplete always dispatches by which field is
978
868
  * focused, match is how you read it. There is no single-field shortcut the way slash/component handlers have one.
979
869
  *
980
- * Provide one arm per autocompletable field across the registered commands, keyed by field name. Each
981
- * arm receives the focused partial value and a `respond` typed to that field's choice value. The
982
- * arms must cover every autocompletable field. A missing field or an unknown key is a compile error.
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.
983
873
  *
984
- * @param arms - One handler per autocompletable field, keyed by field name.
985
- * @returns The result of the arm that ran.
986
- * @throws A **SeedcordTypeError** with `MatchArmMissing` when the focused field has no arm. Only a stale-deployed command can send one.
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.
987
878
  *
988
879
  * @example
989
880
  * ```ts
@@ -998,28 +889,14 @@ var AutocompleteHandler = class extends BaseHandler {
998
889
  * }
999
890
  * ```
1000
891
  */
1001
- async match(arms) {
892
+ async match(cases) {
1002
893
  const { name, value } = this.focused;
1003
894
  const respond = (choices) => this.respond(choices);
1004
- const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
1005
- if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
1006
- this.constructor.name,
1007
- "focused field",
1008
- name
1009
- ]);
1010
- return await arm(value, respond);
895
+ return await runCase(this, cases, "focused field", name, value, respond);
1011
896
  }
1012
897
  /** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
1013
- async respond(choices) {
1014
- await reportedWrite({
1015
- bus: this.core.bus,
1016
- dispatch: this.dispatch,
1017
- interactionId: this.event.id
1018
- }, "respond", () => this.event.respond(choices));
1019
- }
1020
- /** The selected command's path, such as `search/docs`, without the interaction-kind prefix. */
1021
- get route() {
1022
- return slashRouteOf(this.event);
898
+ respond(choices) {
899
+ return this.#responder.respond(choices);
1023
900
  }
1024
901
  /**
1025
902
  * The already-entered options on this command, restricted to the kinds Discord resolves during autocomplete
@@ -1057,15 +934,6 @@ var ComponentHandler = class extends InteractionHandler {
1057
934
  return this.decoded;
1058
935
  }
1059
936
  /**
1060
- * The matched custom-ID prefix, such as `approve`, without encoded params.
1061
- *
1062
- * @throws A **StaleCustomId** when the wire was encoded under an older shape.
1063
- * @throws An **InvalidCustomId** when the wire is corrupt.
1064
- */
1065
- get route() {
1066
- return this.decodedRoute.prefix;
1067
- }
1068
- /**
1069
937
  * The decoded params of the single route this handler is registered for.
1070
938
  *
1071
939
  * Reading this decodes `this.event.customId` once and caches the result. On a handler registered
@@ -1078,17 +946,18 @@ var ComponentHandler = class extends InteractionHandler {
1078
946
  return this.decodedRoute.params;
1079
947
  }
1080
948
  /**
1081
- * Run the arm for whichever route the component was minted from. Use this only when the handler is
1082
- * registered for several routes. A single-route handler reads `this.params` directly. On a multi-route
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
1083
951
  * handler `this.params` is `never`, leaving match as the only way to read the decoded params.
1084
952
  *
1085
- * Provide one arm per registered route, keyed by its prefix, and each arm receives that route's own
1086
- * decoded params. The compiler checks that the arms cover every registered route prefix. `match`
1087
- * decodes the wire before it runs an arm.
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.
1088
956
  *
1089
- * @param arms - One callback per registered route, keyed by prefix.
1090
- * @returns The result of the arm that ran.
1091
- * @throws A **SeedcordTypeError** with `MatchArmMissing` when the wire's prefix has no arm.
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.
1092
961
  * @throws A **StaleCustomId** when the wire was encoded under an older shape.
1093
962
  * @throws An **InvalidCustomId** when the wire is corrupt.
1094
963
  *
@@ -1105,15 +974,9 @@ var ComponentHandler = class extends InteractionHandler {
1105
974
  * }
1106
975
  * ```
1107
976
  */
1108
- async match(arms) {
977
+ async match(cases) {
1109
978
  const { prefix, params } = this.decodedRoute;
1110
- const arm = Object.hasOwn(arms, prefix) ? arms[prefix] : void 0;
1111
- if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
1112
- this.constructor.name,
1113
- "component route",
1114
- prefix
1115
- ]);
1116
- return await arm(params);
979
+ return await runCase(this, cases, "component route", prefix, params);
1117
980
  }
1118
981
  };
1119
982
 
@@ -1348,8 +1211,8 @@ var MentionableMenuHandler = class extends SelectMenuHandler {
1348
1211
  /**
1349
1212
  * Base class for a Discord client event handler.
1350
1213
  *
1351
- * Pass the event name(s) as the generic, the same one(s) as `@RegisterEvent`. A single-event handler reads
1352
- * `this.event` (the payload tuple) directly. A handler registered for several events branches with `this.match`,
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`,
1353
1216
  * keyed by event name, since the union of payload tuples is not directly readable.
1354
1217
  *
1355
1218
  * @typeParam Names - One or more `ClientEvents` keys, e.g. `Events.MessageCreate` or `Events.MessageCreate | Events.MessageUpdate`.
@@ -1368,22 +1231,21 @@ var MentionableMenuHandler = class extends SelectMenuHandler {
1368
1231
  * ```
1369
1232
  */
1370
1233
  var EventHandler = class extends BaseHandler {
1371
- firedEvent;
1372
- constructor(event, core, dispatch, eventName) {
1234
+ constructor(event, core, dispatch) {
1373
1235
  super(event, core, dispatch, "events");
1374
- this.firedEvent = eventName;
1375
1236
  }
1376
1237
  /**
1377
- * Run the arm for the current event. Use this only when the handler is registered for several
1378
- * events. A single-event handler reads `this.event` directly. On a multi-event handler `this.event` is
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
1379
1240
  * `never`, so match is the only way to read the payload.
1380
1241
  *
1381
- * Provide one arm per registered event, keyed by its name, and each arm receives that event's own
1382
- * payload tuple. The arms must cover every event in the generic, a missing event or an unknown key is a
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
1383
1244
  * compile error.
1384
1245
  *
1385
- * @param arms - One handler per registered event, keyed by event name.
1386
- * @returns The result of the arm that ran.
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.
1387
1249
  *
1388
1250
  * @example
1389
1251
  * ```ts
@@ -1398,16 +1260,8 @@ var EventHandler = class extends BaseHandler {
1398
1260
  * }
1399
1261
  * ```
1400
1262
  */
1401
- async match(arms) {
1402
- const name = this.firedEvent;
1403
- if (name === void 0) throw new SeedcordError(SeedcordErrorCode.EventNameUnavailable, [this.constructor.name, "match()"]);
1404
- const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
1405
- if (typeof arm !== "function") throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
1406
- this.constructor.name,
1407
- "event",
1408
- name
1409
- ]);
1410
- return await arm(...this.getEvent());
1263
+ async match(cases) {
1264
+ return await runCase(this, cases, "event", this.route, ...this.getEvent());
1411
1265
  }
1412
1266
  };
1413
1267
 
@@ -1418,26 +1272,135 @@ var EventHandler = class extends BaseHandler {
1418
1272
  *
1419
1273
  * Middleware runs before event handlers and can stop the event by throwing a `Silence`. Unlike `EventHandler`, it
1420
1274
  * runs the SAME for every event it is registered for, so it has no `match`. Specify a single event in the
1421
- * generic and `{ events }` to read `this.event` fully typed. Span several events (or omit `{ events }` for a
1422
- * catchall) and `this.event` narrows to `never`, read `this.eventName` and do work that does not depend on the
1423
- * payload shape. A middleware that needs each event's payload is written one event per class.
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.
1424
1278
  *
1425
1279
  * @typeParam EventName - One or more `ClientEvents` keys. Defaults to every event (catchall).
1426
1280
  */
1427
1281
  var EventMiddleware = class extends BaseHandler {
1428
- firedEvent;
1429
- constructor(event, core, dispatch, eventName) {
1282
+ constructor(event, core, dispatch) {
1430
1283
  super(event, core, dispatch, "events");
1431
- this.firedEvent = eventName;
1432
1284
  }
1433
- /**
1434
- * The name of the event being processed. Read it on a catchall or multi-event middleware, where `this.event`
1435
- * is `never`, to do work that does not depend on the payload shape. A single-event middleware reads
1436
- * `this.event` directly and does not need this.
1437
- */
1438
- get eventName() {
1439
- if (this.firedEvent === void 0) throw new SeedcordError(SeedcordErrorCode.EventNameUnavailable, [this.constructor.name, "this.eventName"]);
1440
- return this.firedEvent;
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;
1441
1404
  }
1442
1405
  };
1443
1406
 
@@ -1530,14 +1493,14 @@ var CursorSource = class extends CursorSourceBase {};
1530
1493
  /**
1531
1494
  * Base class for a gateway subscriber, binding `this.core` to the gateway {@link Core}.
1532
1495
  *
1533
- * @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`
1534
1497
  */
1535
1498
  var Subscriber = class extends Subscriber$1 {};
1536
1499
  /**
1537
1500
  * Base class for a gateway subscriber that delivers its event to a Discord webhook, binding
1538
1501
  * `this.core` to the gateway {@link Core}.
1539
1502
  *
1540
- * @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`
1541
1504
  */
1542
1505
  var WebhookLog = class extends WebhookLog$1 {};
1543
1506
 
@@ -1550,8 +1513,8 @@ const GATEWAY_SCOPE = {
1550
1513
  /**
1551
1514
  * Base class for a gateway plugin, binding `this.core` to the gateway {@link Core}.
1552
1515
  *
1553
- * A plugin that runs on either transport extends the base from `@seedcord/core/plugin`, whose
1554
- * `this.core` is typed to the shared members only.
1516
+ * To run on either transport, extend the base from `@seedcord/core/plugin`. Its `this.core` carries
1517
+ * only the shared members.
1555
1518
  *
1556
1519
  * @typeParam Opts - The transport and runtime the plugin declares.
1557
1520
  */
@@ -1562,15 +1525,23 @@ var Plugin = class extends Plugin$1 {
1562
1525
 
1563
1526
  //#endregion
1564
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
+ }
1565
1536
  function deriveEventActor(args) {
1566
1537
  const tuple = Array.isArray(args) ? args : [args];
1538
+ let partial;
1567
1539
  for (const arg of tuple) {
1568
- if (arg instanceof Message) return {
1569
- guild: arg.guild,
1570
- member: arg.member,
1571
- user: arg.author,
1572
- channelId: arg.channelId
1573
- };
1540
+ if (arg instanceof Message) {
1541
+ const message = arg;
1542
+ if (!message.partial) return actorOf(message);
1543
+ partial ??= message;
1544
+ }
1574
1545
  if (arg instanceof GuildMember) return {
1575
1546
  guild: arg.guild,
1576
1547
  member: arg,
@@ -1584,7 +1555,7 @@ function deriveEventActor(args) {
1584
1555
  channelId: null
1585
1556
  };
1586
1557
  }
1587
- return {
1558
+ return partial ? actorOf(partial) : {
1588
1559
  guild: null,
1589
1560
  member: null,
1590
1561
  user: null,
@@ -1593,7 +1564,8 @@ function deriveEventActor(args) {
1593
1564
  }
1594
1565
 
1595
1566
  //#endregion
1596
- //#region src/bot/gates/runGates.ts
1567
+ //#region src/bot/gates/context.ts
1568
+ const NO_PERMISSIONS = 0n;
1597
1569
  function interactionGateContext(interaction, core, dispatch) {
1598
1570
  const rawMember = interaction.member;
1599
1571
  const memberRoleIds = rawMember instanceof GuildMember ? [...rawMember.roles.cache.keys()].filter((id) => id !== interaction.guildId) : rawMember?.roles ?? [];
@@ -1612,8 +1584,7 @@ function interactionGateContext(interaction, core, dispatch) {
1612
1584
  memberPermissions: interaction.memberPermissions?.bitfield ?? null,
1613
1585
  appPermissions: interaction.appPermissions.bitfield,
1614
1586
  memberGuildPermissions: rawMember instanceof GuildMember ? rawMember.permissions.bitfield : null,
1615
- appGuildPermissions: interaction.guild?.members.me?.permissions.bitfield ?? null,
1616
- declaredRoute: null
1587
+ appGuildPermissions: interaction.guild ? interaction.guild.members.me?.permissions.bitfield ?? null : NO_PERMISSIONS
1617
1588
  };
1618
1589
  }
1619
1590
  function eventGateContext(eventName, args, core, dispatch) {
@@ -1632,161 +1603,76 @@ function eventGateContext(eventName, args, core, dispatch) {
1632
1603
  channelId: actor.channelId,
1633
1604
  memberRoleIds: actor.member ? [...actor.member.roles.cache.keys()].filter((id) => id !== actor.guild?.id) : [],
1634
1605
  memberPermissions: actor.member?.permissions.bitfield ?? null,
1635
- appPermissions: null,
1636
1606
  memberGuildPermissions: actor.member?.permissions.bitfield ?? null,
1637
- appGuildPermissions: actor.guild?.members.me?.permissions.bitfield ?? null,
1638
- declaredRoute: null
1607
+ appGuildPermissions: actor.guild?.members.me?.permissions.bitfield ?? null
1639
1608
  };
1640
1609
  }
1641
1610
 
1642
1611
  //#endregion
1643
- //#region src/miscellaneous/extractErrorResponse.ts
1644
- const logger$2 = new Logger("ErrorsHandling", { channel: "errors" });
1645
- function extractErrorResponse(error, core, fault) {
1646
- const uuid = crypto.randomUUID();
1647
- const { dispatch } = fault;
1648
- const developerUsername = core.config.notifications?.developerUsername;
1649
- const ctx = developerUsername === void 0 ? {
1650
- uuid,
1651
- dispatch
1652
- } : {
1653
- uuid,
1654
- developerUsername,
1655
- dispatch
1656
- };
1657
- if (error instanceof Notice) {
1658
- if (error.report) reportFault(error, core, fault, uuid);
1659
- return {
1660
- uuid,
1661
- response: error.render(ctx)
1662
- };
1663
- }
1664
- reportRawFault(error, core, fault, uuid);
1665
- const Override = core.config.errors?.defaultError;
1612
+ //#region src/miscellaneous/faultReports.ts
1613
+ function interactionFaultReport(interaction, dispatch) {
1666
1614
  return {
1667
- uuid,
1668
- response: Override ? new Override(uuid).render(ctx) : new Fault().render(ctx)
1669
- };
1670
- }
1671
- function reportFault(denial, core, fault, uuid) {
1672
- logger$2.error(`${denial.name}: ${uuid}`, denial);
1673
- if (fault.interaction) core.bus[PublishDefault]("handledException", {
1674
- denial,
1675
- uuid,
1676
- dispatchId: fault.dispatch.id,
1677
- origin: fault.origin,
1678
- source: buildInteractionSource(fault.interaction)
1679
- });
1680
- else if (fault.event) core.bus[PublishDefault]("handledException", {
1681
- denial,
1682
- uuid,
1683
- dispatchId: fault.dispatch.id,
1684
- origin: fault.origin,
1685
- source: buildEventSource(fault.event, fault)
1686
- });
1687
- else core.bus[PublishDefault]("unknownException", {
1688
- uuid,
1689
- dispatchId: fault.dispatch.id,
1690
- error: denial,
1691
- origin: fault.origin,
1692
- ...scalarActors(fault),
1693
- metadata: metadataFor(fault)
1694
- });
1695
- }
1696
- function reportRawFault(error, core, fault, uuid) {
1697
- logUnknownFault(logger$2, uuid, error, core.config.errors);
1698
- core.bus[PublishDefault]("unknownException", {
1699
- uuid,
1700
- dispatchId: fault.dispatch.id,
1701
- error,
1702
- origin: fault.origin,
1703
- ...scalarActors(fault),
1704
- metadata: metadataFor(fault)
1705
- });
1706
- }
1707
- function scalarActors(fault) {
1708
- return {
1709
- guild: fault.guild ? {
1710
- id: fault.guild.id,
1711
- name: fault.guild.name
1712
- } : void 0,
1713
- user: fault.user ? {
1714
- id: fault.user.id,
1715
- username: fault.user.username
1716
- } : void 0
1717
- };
1718
- }
1719
- function metadataFor(fault) {
1720
- if (fault.event) return {
1721
- eventName: fault.event.name,
1722
- handler: fault.event.handler,
1723
- 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
1724
1626
  };
1725
- return fault.metadata;
1726
1627
  }
1727
- function buildEventSource(event, fault) {
1628
+ function eventFaultReport(event) {
1728
1629
  return {
1729
- kind: "event",
1730
- eventName: event.name,
1731
- handler: event.handler,
1732
- userId: fault.user?.id ?? null,
1733
- guildId: fault.guild?.id ?? null,
1734
- channelId: event.channelId,
1735
- raw: event.args
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
+ }
1736
1646
  };
1737
1647
  }
1738
- function buildInteractionSource(interaction) {
1739
- const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
1740
- const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
1648
+ function scalarActors(guild, user) {
1741
1649
  return {
1742
- kind: "interaction",
1743
- interactionKind: interactionKind(interaction),
1744
- command,
1745
- customId,
1746
- userId: interaction.user.id,
1747
- guildId: interaction.guildId,
1748
- channelId: interaction.channelId,
1749
- interactionId: interaction.id,
1750
- 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
1751
1658
  };
1752
1659
  }
1753
- function interactionKind(interaction) {
1754
- if (interaction.isChatInputCommand()) return "slash";
1755
- if (interaction.isContextMenuCommand()) return "context-menu";
1756
- if (interaction.isButton()) return "button";
1757
- if (interaction.isAnySelectMenu()) return "select";
1758
- return "modal";
1759
- }
1760
1660
 
1761
1661
  //#endregion
1762
1662
  //#region src/bot/handleEventFault.ts
1763
- const logger$1 = new Logger("Faults", { channel: "errors" });
1764
1663
  function handleEventFault(caught, fault, core) {
1765
- const { eventName, handlerName, args, dispatch } = fault;
1766
- if (caught instanceof Silence) {
1767
- if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger$1.debug(`Silence: ${caught.reason}`);
1768
- return;
1769
- }
1770
- const error = asError(caught);
1771
- const ignore = new Set(core.config.errors?.ignoreEventApiCodes ?? []);
1772
- if (error instanceof DiscordAPIError && ignore.has(error.code)) {
1773
- logger$1.debug(`swallowed api code ${error.code}`);
1774
- return;
1775
- }
1776
- if (error instanceof Notice && !error.report) return;
1777
- const actor = deriveEventActor(args);
1778
- extractErrorResponse(error, core, {
1779
- event: {
1780
- 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,
1781
1669
  handler: handlerName,
1782
1670
  args,
1783
- channelId: actor.channelId
1784
- },
1785
- origin: `event:${eventName}:${handlerName}`,
1786
- dispatch,
1787
- guild: actor.guild,
1788
- user: actor.user
1789
- });
1671
+ channelId,
1672
+ guild,
1673
+ user
1674
+ });
1675
+ }).report(caught);
1790
1676
  }
1791
1677
 
1792
1678
  //#endregion
@@ -1835,27 +1721,22 @@ var EventDispatcher = class {
1835
1721
  middlewares = new MiddlewareRegistry(eventMiddleware);
1836
1722
  onceRuns = new OnceTracker();
1837
1723
  attachedEvents = /* @__PURE__ */ new Set();
1838
- loading = false;
1839
- loadedHandlers = [];
1840
- loadedMiddlewares = [];
1841
- hmrHandler;
1724
+ loader;
1842
1725
  constructor(core, inFlight) {
1843
1726
  this.core = core;
1844
1727
  this.inFlight = inFlight;
1845
1728
  const eventsDir = this.core.config.bot.events.path;
1846
1729
  if (!eventsDir) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["EventDispatcher", "events"]);
1847
- if (!Envapter.isDevelopment && !Envapter.isTest) return;
1848
- this.hmrHandler = new HmrModuleHandler$1({
1730
+ this.loader = new HandlerLoader({
1849
1731
  handlersDir: eventsDir,
1850
- ...hasKeys(this.core.config.bot.events, ["middlewares"]) && { middlewaresDir: this.core.config.bot.events.middlewares },
1851
- isHandler: this.isEventHandlerClass.bind(this),
1852
- isMiddleware: this.isMiddlewareClass.bind(this),
1853
- registerHandler: this.registerHandler.bind(this),
1854
- registerMiddleware: this.registerMiddleware.bind(this),
1855
- unregisterHandler: this.unregisterHandler.bind(this),
1856
- unregisterMiddleware: this.unregisterMiddleware.bind(this),
1857
- getArtifacts: this.getArtifacts.bind(this),
1858
- 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)
1859
1740
  });
1860
1741
  }
1861
1742
  getArtifacts(ctor) {
@@ -1866,49 +1747,25 @@ var EventDispatcher = class {
1866
1747
  async init() {
1867
1748
  if (this.isInitialized) return;
1868
1749
  this.isInitialized = true;
1869
- const handlersDir = this.core.config.bot.events.path;
1870
- if (!handlersDir) return;
1871
- const middlewareDir = hasKeys(this.core.config.bot.events, ["middlewares"]) ? this.core.config.bot.events.middlewares : void 0;
1872
- this.loading = true;
1873
- this.loadedHandlers.length = 0;
1874
- this.loadedMiddlewares.length = 0;
1875
- try {
1876
- if (middlewareDir) await this.loadMiddlewares(middlewareDir);
1877
- await this.loadHandlers(handlersDir);
1878
- } finally {
1879
- this.loading = false;
1880
- }
1750
+ await this.loader.load();
1881
1751
  this.attachToClient();
1882
1752
  this.reportLoad();
1883
1753
  }
1884
1754
  reportLoad() {
1885
1755
  const { utils } = this.logger;
1756
+ const { loadedHandlers, loadedMiddlewares } = this.loader;
1886
1757
  utils.summary("Loaded", {
1887
- "event handlers": this.loadedHandlers.length,
1888
- middlewares: this.loadedMiddlewares.length
1758
+ "event handlers": loadedHandlers.length,
1759
+ middlewares: loadedMiddlewares.length
1889
1760
  }, "debug");
1890
- if (this.loadedMiddlewares.length > 0) utils.block("Loaded event middlewares", utils.entries(this.loadedMiddlewares), "debug");
1761
+ if (loadedMiddlewares.length > 0) utils.block("Loaded event middlewares", utils.entries(loadedMiddlewares), "debug");
1891
1762
  const perEvent = {};
1892
1763
  for (const [event, handlers] of this.eventMap) perEvent[event] = handlers.length;
1893
- utils.block("Loaded event handlers", [...utils.entries(this.loadedHandlers), ...utils.counts(perEvent)], "debug");
1894
- }
1895
- async loadHandlers(dir) {
1896
- for await (const { fullPath, relativePath, imported } of importFiles(dir)) for (const val of Object.values(imported)) {
1897
- if (!this.isEventHandlerClass(val)) continue;
1898
- this.registerHandler(val, relativePath);
1899
- this.hmrHandler?.trackHandler(fullPath, val);
1900
- }
1901
- }
1902
- async loadMiddlewares(dir) {
1903
- for await (const { fullPath, relativePath, imported } of importFiles(dir)) for (const val of Object.values(imported)) {
1904
- if (!this.isMiddlewareClass(val)) continue;
1905
- this.registerMiddleware(val, relativePath);
1906
- this.hmrHandler?.trackMiddleware(fullPath, val);
1907
- }
1764
+ utils.block("Loaded event handlers", [...utils.entries(loadedHandlers), ...utils.counts(perEvent)], "debug");
1908
1765
  }
1909
1766
  /** @internal */
1910
1767
  async onHmr(event) {
1911
- await this.hmrHandler?.handle(event);
1768
+ await this.loader.onHmr(event);
1912
1769
  }
1913
1770
  unregisterHandler(handlerClass, artifacts) {
1914
1771
  const events = artifacts ?? [...this.eventMap.keys()];
@@ -1921,26 +1778,13 @@ var EventDispatcher = class {
1921
1778
  if (handlers.length === 0) this.eventMap.delete(event);
1922
1779
  }
1923
1780
  }
1924
- unregisterMiddleware(middlewareCtor) {
1925
- this.middlewares.unregister(middlewareCtor);
1926
- }
1927
- registerMiddleware(middlewareCtor, relativePath) {
1928
- const registration = this.middlewares.register(middlewareCtor);
1929
- if (!registration || !this.loading) return;
1930
- const scope = registration.keys ? `${registration.priority}, ${registration.keys.join(", ")}` : registration.priority;
1931
- this.loadedMiddlewares.push({
1932
- name: `${middlewareCtor.name} (${String(scope)})`,
1933
- from: formatFilePath(relativePath)
1934
- });
1935
- }
1936
1781
  async runMiddlewares(eventName, args, dispatch, ran) {
1937
1782
  for (const Middleware of this.middlewares.chainFor(eventName)) try {
1938
- const middleware = new Middleware(args, this.core, dispatch, eventName);
1783
+ const middleware = new Middleware(args, this.core, dispatch);
1939
1784
  ran.push(middleware);
1940
1785
  await middleware.execute();
1941
1786
  } catch (caught) {
1942
1787
  handleEventFault(caught, {
1943
- eventName: String(eventName),
1944
1788
  handlerName: Middleware.name,
1945
1789
  args,
1946
1790
  dispatch
@@ -1957,8 +1801,8 @@ var EventDispatcher = class {
1957
1801
  if (typeof obj !== "function") return false;
1958
1802
  return obj.prototype instanceof EventMiddleware && Reflect.hasMetadata(EventMiddlewareMetadataKey, obj);
1959
1803
  }
1960
- registerHandler(handlerClass, relativePath) {
1961
- const raw = Reflect.getMetadata(EventMetadataKey, handlerClass);
1804
+ storeHandler(handlerClass) {
1805
+ const entries = Reflect.getMetadata(EventMetadataKey, handlerClass);
1962
1806
  const register = (key, frequency) => {
1963
1807
  let handlers = this.eventMap.get(key);
1964
1808
  if (!handlers) {
@@ -1971,16 +1815,8 @@ var EventDispatcher = class {
1971
1815
  });
1972
1816
  if (this.isInitialized && !this.attachedEvents.has(key)) this.attachListener(key);
1973
1817
  };
1974
- if (Array.isArray(raw)) for (const entry of raw) register(entry.event, entry.frequency);
1975
- else {
1976
- const names = isStringArray(raw) ? raw : typeof raw === "string" ? [raw] : [];
1977
- for (const name of names) register(name, "on");
1978
- }
1979
- const from = formatFilePath(relativePath);
1980
- if (this.loading) this.loadedHandlers.push({
1981
- name: handlerClass.name,
1982
- from
1983
- });
1818
+ for (const entry of entries) register(entry.event, entry.frequency);
1819
+ return true;
1984
1820
  }
1985
1821
  attachToClient() {
1986
1822
  for (const [eventName] of this.eventMap) this.attachListener(eventName);
@@ -2005,7 +1841,7 @@ var EventDispatcher = class {
2005
1841
  if (!handlerEntries || handlerEntries.length === 0) return;
2006
1842
  const handlersToExecute = handlerEntries.filter((entry) => !this.onceRuns.isSpent(entry, eventName));
2007
1843
  if (handlersToExecute.length === 0) return;
2008
- const dispatch = new DispatchContext(`event:${String(eventName)}`);
1844
+ const dispatch = new DispatchContext("event", eventName);
2009
1845
  reportEventDispatching(this.core, dispatch.id, eventName, args);
2010
1846
  const ran = [];
2011
1847
  const handlers = [];
@@ -2024,7 +1860,7 @@ var EventDispatcher = class {
2024
1860
  async processHandler(eventName, Ctor, args, dispatch) {
2025
1861
  try {
2026
1862
  this.logger.trace(`Processing ${paint.sky.bold(eventName)} with ${paint.mute(Ctor.name)}`);
2027
- const handler = new Ctor(args, this.core, dispatch, eventName);
1863
+ const handler = new Ctor(args, this.core, dispatch);
2028
1864
  const eventCtx = eventGateContext(eventName, args, this.core, dispatch);
2029
1865
  await runHandlerGates(Ctor, eventCtx);
2030
1866
  await handler.execute();
@@ -2033,16 +1869,11 @@ var EventDispatcher = class {
2033
1869
  outcome: "handled"
2034
1870
  };
2035
1871
  } catch (caught) {
2036
- try {
2037
- handleEventFault(caught, {
2038
- eventName: String(eventName),
2039
- handlerName: Ctor.name,
2040
- args,
2041
- dispatch
2042
- }, this.core);
2043
- } catch (reportFailed) {
2044
- this.reportUnhandled(reportFailed);
2045
- }
1872
+ handleEventFault(caught, {
1873
+ handlerName: Ctor.name,
1874
+ args,
1875
+ dispatch
1876
+ }, this.core);
2046
1877
  return {
2047
1878
  handler: Ctor.name,
2048
1879
  ...resultFor(caught)
@@ -2051,6 +1882,40 @@ var EventDispatcher = class {
2051
1882
  }
2052
1883
  };
2053
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
+
2054
1919
  //#endregion
2055
1920
  //#region src/bot/defaults/UnhandledRepliable.ts
2056
1921
  var UnhandledRepliable = class extends InteractionHandler {
@@ -2068,103 +1933,31 @@ var UnhandledAutocomplete = class extends AutocompleteHandler {
2068
1933
  };
2069
1934
 
2070
1935
  //#endregion
2071
- //#region src/bot/harmlessApiCodes.ts
2072
- /**
2073
- * Discord API error codes that mean a dead end on an interaction, a dead token, a double ack, or a gone
2074
- * message. Nothing to show the user and no bug to report, so the boundary's fault-card send drops them.
2075
- *
2076
- * @internal
2077
- */
2078
- const HARMLESS_API_CODES = /* @__PURE__ */ new Set([
2079
- RESTJSONErrorCodes.UnknownInteraction,
2080
- RESTJSONErrorCodes.InteractionHasAlreadyBeenAcknowledged,
2081
- RESTJSONErrorCodes.UnknownMessage
2082
- ]);
2083
-
2084
- //#endregion
2085
- //#region src/bot/handleInteractionFault.ts
2086
- const logger = new Logger("Faults", { channel: "errors" });
2087
- async function handleInteractionFault(caught, interaction, core, dispatch, sender) {
2088
- const { routeId } = dispatch;
2089
- if (caught instanceof Silence) {
2090
- if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger.debug(`Silence: ${caught.reason}`);
2091
- return;
2092
- }
2093
- const error = asError(caught);
2094
- const ignore = new Set(core.config.errors?.ignoreApiCodes ?? []);
2095
- if (error instanceof DiscordAPIError && ignore.has(error.code)) {
2096
- logger.debug(`swallowed api code ${error.code}`);
2097
- return;
2098
- }
2099
- if (interaction.isAutocomplete()) {
2100
- extractErrorResponse(error, core, {
2101
- origin: routeId,
2102
- dispatch,
2103
- guild: interaction.guild,
2104
- user: interaction.user,
2105
- metadata: interaction
2106
- });
2107
- await sendEmptyChoices(interaction, core, dispatch);
2108
- return;
2109
- }
2110
- const { response } = extractErrorResponse(error, core, {
2111
- interaction,
2112
- origin: routeId,
2113
- dispatch,
2114
- guild: interaction.guild,
2115
- user: interaction.user,
2116
- metadata: interaction
2117
- });
2118
- const liveSender = sender ?? new ReplySender(interaction, dispatch, core.bus);
2119
- await sendGuarded(() => liveSender.send(response, { ephemeral: error instanceof Notice ? error.ephemeral : true }));
2120
- }
2121
- async function sendEmptyChoices(interaction, core, dispatch) {
2122
- const telemetry = {
2123
- bus: core.bus,
2124
- dispatch,
2125
- interactionId: interaction.id
2126
- };
2127
- await sendGuarded(() => reportedWrite(telemetry, "respond", () => interaction.respond([])));
2128
- }
2129
- async function sendGuarded(send) {
2130
- try {
2131
- await send();
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("fault response 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;
2145
1951
  inFlight;
2146
1952
  logger = new Logger("Interactions", { channel: "interactions" });
2147
1953
  isInitialized = false;
2148
- maps = {
2149
- [InteractionKind.Slash]: /* @__PURE__ */ new Map(),
2150
- [InteractionKind.Button]: /* @__PURE__ */ new Map(),
2151
- [InteractionKind.Modal]: /* @__PURE__ */ new Map(),
2152
- [InteractionKind.StringMenu]: /* @__PURE__ */ new Map(),
2153
- [InteractionKind.UserMenu]: /* @__PURE__ */ new Map(),
2154
- [InteractionKind.RoleMenu]: /* @__PURE__ */ new Map(),
2155
- [InteractionKind.ChannelMenu]: /* @__PURE__ */ new Map(),
2156
- [InteractionKind.MentionableMenu]: /* @__PURE__ */ new Map(),
2157
- [InteractionKind.MessageContextMenu]: /* @__PURE__ */ new Map(),
2158
- [InteractionKind.UserContextMenu]: /* @__PURE__ */ new Map(),
2159
- [InteractionKind.Autocomplete]: /* @__PURE__ */ new Map()
2160
- };
2161
- handlerFiles = /* @__PURE__ */ new Map();
1954
+ routes = new RouteRegistry();
1955
+ get maps() {
1956
+ return this.routes.maps;
1957
+ }
2162
1958
  keysToIgnore = /* @__PURE__ */ new Set();
2163
1959
  middlewares = new MiddlewareRegistry(interactionMiddleware);
2164
- loading = false;
2165
- loadedHandlers = [];
2166
- loadedMiddlewares = [];
2167
- hmrHandler;
1960
+ loader;
2168
1961
  constructor(core, inFlight) {
2169
1962
  this.core = core;
2170
1963
  this.inFlight = inFlight;
@@ -2173,59 +1966,40 @@ var InteractionDispatcher = class {
2173
1966
  this.keysToIgnore.add(CONFIRM_DEF);
2174
1967
  const interactionsDir = this.core.config.bot.interactions.path;
2175
1968
  if (!interactionsDir) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["InteractionDispatcher", "interactions"]);
2176
- if (!Envapter.isDevelopment && !Envapter.isTest) return;
2177
- this.hmrHandler = new HmrModuleHandler$1({
1969
+ this.loader = new HandlerLoader({
2178
1970
  handlersDir: interactionsDir,
2179
- ...hasKeys(this.core.config.bot.interactions, ["middlewares"]) && this.core.config.bot.interactions.middlewares && { middlewaresDir: this.core.config.bot.interactions.middlewares },
2180
- isHandler: this.isHandlerClass.bind(this),
2181
- isMiddleware: this.isMiddlewareClass.bind(this),
2182
- registerHandler: this.registerHandler.bind(this),
2183
- registerMiddleware: this.registerMiddleware.bind(this),
2184
- unregisterHandler: this.unregisterHandler.bind(this),
2185
- unregisterMiddleware: this.unregisterMiddleware.bind(this),
2186
- getArtifacts: this.getArtifacts.bind(this),
2187
- 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)
2188
1979
  });
2189
1980
  }
2190
1981
  warnUnhandledRoutes(commandLeaves) {
2191
- for (const route of commandLeaves) if (!this.maps[InteractionKind.Slash].has(route)) this.logger.warn(`Slash route ${paint.sky.bold(route)} has no registered ${paint.bold("@SlashRoute")} handler.`);
1982
+ this.routes.warnUnhandledRoutes(this.logger, commandLeaves);
2192
1983
  }
2193
1984
  warnUnhandledContextMenuRoutes(leaves) {
2194
- for (const name of leaves.user) if (!this.maps[InteractionKind.UserContextMenu].has(name)) this.logger.warn(`User context menu ${paint.sky.bold(name)} has no registered ${paint.bold("@ContextMenuRoute")} handler.`);
2195
- 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.`);
2196
- }
2197
- getArtifacts(handlerClass) {
2198
- return interactionRoutesOf(handlerClass).map(([routeType, routes]) => ({
2199
- routeType,
2200
- routes
2201
- }));
1985
+ this.routes.warnUnhandledContextMenuRoutes(this.logger, leaves);
2202
1986
  }
2203
1987
  async init() {
2204
1988
  if (this.isInitialized) return;
2205
1989
  this.isInitialized = true;
2206
- const handlersDir = this.core.config.bot.interactions.path;
2207
- if (!handlersDir) return;
2208
- const middlewareDir = hasKeys(this.core.config.bot.interactions, ["middlewares"]) ? this.core.config.bot.interactions.middlewares : void 0;
2209
- this.loading = true;
2210
- this.loadedHandlers.length = 0;
2211
- this.loadedMiddlewares.length = 0;
2212
- try {
2213
- if (middlewareDir) await this.loadMiddlewares(middlewareDir);
2214
- await this.loadHandlers(handlersDir);
2215
- } finally {
2216
- this.loading = false;
2217
- }
1990
+ await this.loader.load();
2218
1991
  this.attachToClient();
2219
1992
  this.reportLoad();
2220
1993
  }
2221
1994
  reportLoad() {
2222
1995
  const { utils } = this.logger;
1996
+ const { loadedHandlers, loadedMiddlewares } = this.loader;
2223
1997
  utils.summary("Loaded", {
2224
- "interaction handlers": this.loadedHandlers.length,
2225
- middlewares: this.loadedMiddlewares.length
1998
+ "interaction handlers": loadedHandlers.length,
1999
+ middlewares: loadedMiddlewares.length
2226
2000
  }, "debug");
2227
- if (this.loadedMiddlewares.length > 0) utils.block("Loaded middlewares", utils.entries(this.loadedMiddlewares), "debug");
2228
- utils.block("Loaded interaction handlers", [...utils.entries(this.loadedHandlers), ...utils.counts({
2001
+ if (loadedMiddlewares.length > 0) utils.block("Loaded middlewares", utils.entries(loadedMiddlewares), "debug");
2002
+ utils.block("Loaded interaction handlers", [...utils.entries(loadedHandlers), ...utils.counts({
2229
2003
  slash: this.maps[InteractionKind.Slash].size,
2230
2004
  buttons: this.maps[InteractionKind.Button].size,
2231
2005
  modals: this.maps[InteractionKind.Modal].size,
@@ -2239,29 +2013,6 @@ var InteractionDispatcher = class {
2239
2013
  autocomplete: this.maps[InteractionKind.Autocomplete].size
2240
2014
  })], "debug");
2241
2015
  }
2242
- async loadHandlers(dir) {
2243
- for await (const { fullPath, relativePath, imported } of importFiles(dir)) for (const val of Object.values(imported)) {
2244
- if (!this.isHandlerClass(val)) continue;
2245
- this.registerHandler(val, relativePath);
2246
- this.hmrHandler?.trackHandler(fullPath, val);
2247
- }
2248
- }
2249
- async loadMiddlewares(dir) {
2250
- for await (const { fullPath, relativePath, imported } of importFiles(dir)) for (const val of Object.values(imported)) {
2251
- if (!this.isMiddlewareClass(val)) continue;
2252
- this.registerMiddleware(val, relativePath);
2253
- this.hmrHandler?.trackMiddleware(fullPath, val);
2254
- }
2255
- }
2256
- registerMiddleware(middlewareCtor, relativePath) {
2257
- const metadata = this.middlewares.register(middlewareCtor);
2258
- if (!metadata || !this.loading) return;
2259
- const scope = metadata.keys ? `${metadata.priority}, ${metadata.keys.join(", ")}` : metadata.priority;
2260
- this.loadedMiddlewares.push({
2261
- name: `${middlewareCtor.name} (${String(scope)})`,
2262
- from: formatFilePath(relativePath)
2263
- });
2264
- }
2265
2016
  isHandlerClass(obj) {
2266
2017
  if (typeof obj !== "function") return false;
2267
2018
  return obj.prototype instanceof InteractionHandler && Reflect.hasMetadata(InteractionMetadataKey, obj) || obj.prototype instanceof AutocompleteHandler && Reflect.hasMetadata(InteractionMetadataKey, obj);
@@ -2270,38 +2021,8 @@ var InteractionDispatcher = class {
2270
2021
  if (typeof obj !== "function") return false;
2271
2022
  return obj.prototype instanceof InteractionMiddleware && Reflect.hasMetadata(InteractionMiddlewareMetadataKey, obj);
2272
2023
  }
2273
- registerHandler(handlerClass, relativePath) {
2274
- const from = formatFilePath(relativePath);
2275
- const writes = [];
2276
- for (const [kind, routes] of interactionRoutesOf(handlerClass)) for (const route of routes) {
2277
- const existing = this.maps[kind].get(route);
2278
- if (existing && existing !== handlerClass) throw new SeedcordTypeError(SeedcordErrorCode.CoreDuplicateName, [
2279
- "route",
2280
- `${kind}:${route}`,
2281
- [`${paint.bold(existing.name)} (${paint.path(this.handlerFiles.get(existing) ?? "unknown file")})`, `${paint.bold(handlerClass.name)} (${paint.path(from)})`]
2282
- ]);
2283
- writes.push({
2284
- kind,
2285
- route
2286
- });
2287
- }
2288
- if (writes.length === 0) return;
2289
- for (const { kind, route } of writes) this.maps[kind].set(route, handlerClass);
2290
- this.handlerFiles.set(handlerClass, from);
2291
- if (this.loading) this.loadedHandlers.push({
2292
- name: handlerClass.name,
2293
- from
2294
- });
2295
- }
2296
2024
  async onHmr(event) {
2297
- await this.hmrHandler?.handle(event);
2298
- }
2299
- unregisterHandler(handlerClass, artifacts) {
2300
- this.handlerFiles.delete(handlerClass);
2301
- for (const { routeType, routes } of artifacts ?? this.getArtifacts(handlerClass)) for (const route of routes) this.maps[routeType].delete(route);
2302
- }
2303
- unregisterMiddleware(middlewareCtor) {
2304
- this.middlewares.unregister(middlewareCtor);
2025
+ await this.loader.onHmr(event);
2305
2026
  }
2306
2027
  attachToClient() {
2307
2028
  this.core.bot.client.on(Events.InteractionCreate, (interaction) => {
@@ -2317,186 +2038,30 @@ var InteractionDispatcher = class {
2317
2038
  }
2318
2039
  async handleCustomIdInteraction(interaction, kind) {
2319
2040
  if ([...this.keysToIgnore].some((matcher) => matcher.owns(interaction.customId))) return;
2320
- const prefix = prefixOf(interaction.customId);
2321
- if (!prefix) this.logger.warn(`${paint.sky.bold(kind)} has invalid customId: ${paint.mute(interaction.customId)}`);
2322
- await this.processInteraction(interaction, kind, () => prefix);
2041
+ await this.processInteraction(interaction, kind, prefixOf(interaction.customId));
2323
2042
  }
2324
- async processInteraction(interaction, kind, extractKey, fallback = UnhandledRepliable) {
2325
- const key = extractKey(interaction);
2326
- const startedAt = performance.now();
2327
- const queuedMs = queuedMsFor(interaction.id);
2328
- const matched = this.maps[kind].get(key);
2329
- const HandlerCtor = matched ?? fallback;
2330
- const dispatch = new DispatchContext(`${kind}:${key || "unrouted"}`);
2331
- 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({
2332
2046
  interaction,
2333
- kind,
2334
- fallback: !matched,
2335
- startedAt,
2336
- queuedMs,
2337
- dispatchId: dispatch.id,
2338
- routeId: dispatch.routeId
2047
+ core: this.core,
2048
+ dispatch: new DispatchContext(kind, key, fallback),
2049
+ HandlerCtor,
2050
+ middlewares: this.middlewares,
2051
+ logger: this.logger
2339
2052
  });
2340
- let sender;
2341
- const ran = [];
2342
- let result = { outcome: "handled" };
2343
- try {
2344
- const handler = this.buildHandler(HandlerCtor, interaction, dispatch, key, !matched);
2345
- if (handler instanceof RepliableHandler) sender = handler.sender;
2346
- const refusal = await this.refusalBeforeHandler({
2347
- HandlerCtor,
2348
- kind,
2349
- interaction,
2350
- dispatch,
2351
- sender,
2352
- ran,
2353
- declaredRoute: matched ? dispatch.routeId : void 0
2354
- });
2355
- if (refusal) {
2356
- result = resultFor(refusal.caught);
2357
- await this.answer(refusal.caught, interaction, dispatch, sender, report);
2358
- return;
2359
- }
2360
- await handler.execute();
2361
- report("handled");
2362
- } catch (caught) {
2363
- if (result.outcome === "handled") result = resultFor(caught);
2364
- await this.answer(caught, interaction, dispatch, sender, report);
2365
- } finally {
2366
- await runAfter(ran, result, this.logger);
2367
- }
2368
- }
2369
- async answer(caught, interaction, dispatch, sender, report) {
2370
- try {
2371
- await handleInteractionFault(caught, interaction, this.core, dispatch, sender);
2372
- } finally {
2373
- report(outcomeFor(caught));
2374
- }
2375
- }
2376
- buildHandler(HandlerCtor, interaction, dispatch, key, isFallback) {
2377
- if (isFallback) this.logger.warn(`No handler found for key ${paint.sky.bold(key)}. Falling back to ${HandlerCtor.name}.`);
2378
- this.logger.trace(`Processing ${paint.sky.bold(key)} with ${paint.mute(HandlerCtor.name)}`);
2379
- return new HandlerCtor(interaction, this.core, dispatch);
2380
- }
2381
- reporterFor(row) {
2382
- let reported = false;
2383
- return (outcome) => {
2384
- if (reported) return;
2385
- reported = true;
2386
- reportDispatch(this.core.bus, {
2387
- dispatchId: row.dispatchId,
2388
- routeId: row.routeId,
2389
- interactionId: row.interaction.id,
2390
- kind: row.kind,
2391
- outcome,
2392
- startedAt: row.startedAt,
2393
- fallback: row.fallback,
2394
- userId: row.interaction.user.id,
2395
- guildId: row.interaction.guildId,
2396
- queuedMs: row.queuedMs
2397
- });
2398
- };
2399
- }
2400
- async refusalBeforeHandler(step) {
2401
- const { HandlerCtor, kind, interaction, dispatch, sender, ran, declaredRoute } = step;
2402
- if (kind !== InteractionKind.Autocomplete && sender) try {
2403
- await this.runMiddlewares(kind, interaction, dispatch, sender, ran);
2404
- } catch (caught) {
2405
- return { caught };
2406
- }
2407
- if (interaction.isAutocomplete()) return null;
2408
- return this.gateRefusal(HandlerCtor, interaction, dispatch, declaredRoute);
2409
- }
2410
- async runMiddlewares(kind, interaction, dispatch, sender, ran) {
2411
- for (const Middleware of this.middlewares.chainFor(kind)) {
2412
- const middleware = new Middleware(interaction, this.core, dispatch, sender);
2413
- ran.push(middleware);
2414
- await middleware.execute();
2415
- }
2416
- }
2417
- async gateRefusal(HandlerCtor, interaction, dispatch, declaredRoute) {
2418
- const monitor = slowGateMonitor();
2419
- try {
2420
- await runHandlerGates(HandlerCtor, interactionGateContext(interaction, this.core, dispatch), declaredRoute, monitor?.observe);
2421
- return null;
2422
- } catch (caught) {
2423
- return { caught };
2424
- } finally {
2425
- monitor?.report(declaredRoute ?? null);
2426
- }
2053
+ await (await new InteractionRun(config).prepare())?.();
2427
2054
  }
2428
2055
  async handleInteraction(interaction) {
2429
- switch (true) {
2430
- case interaction.isChatInputCommand():
2431
- await this.handleSlashCommand(interaction);
2432
- break;
2433
- case interaction.isButton():
2434
- await this.handleButton(interaction);
2435
- break;
2436
- case interaction.isModalSubmit():
2437
- await this.handleModal(interaction);
2438
- break;
2439
- case interaction.isStringSelectMenu():
2440
- await this.handleStringSelectMenu(interaction);
2441
- break;
2442
- case interaction.isUserSelectMenu():
2443
- await this.handleUserSelectMenu(interaction);
2444
- break;
2445
- case interaction.isRoleSelectMenu():
2446
- await this.handleRoleSelectMenu(interaction);
2447
- break;
2448
- case interaction.isChannelSelectMenu():
2449
- await this.handleChannelSelectMenu(interaction);
2450
- break;
2451
- case interaction.isMentionableSelectMenu():
2452
- await this.handleMentionableSelectMenu(interaction);
2453
- break;
2454
- case interaction.isMessageContextMenuCommand():
2455
- await this.handleMessageContextMenu(interaction);
2456
- break;
2457
- case interaction.isUserContextMenuCommand():
2458
- await this.handleUserContextMenu(interaction);
2459
- break;
2460
- case interaction.isAutocomplete():
2461
- await this.handleAutocomplete(interaction);
2462
- break;
2463
- default: this.logger.warn(`Unhandled interaction type: ${interaction.type}`);
2464
- }
2465
- }
2466
- async handleSlashCommand(interaction) {
2467
- const route = slashRouteOf(interaction);
2468
- await this.processInteraction(interaction, InteractionKind.Slash, () => route);
2469
- }
2470
- async handleButton(interaction) {
2471
- await this.handleCustomIdInteraction(interaction, InteractionKind.Button);
2472
- }
2473
- async handleModal(interaction) {
2474
- await this.handleCustomIdInteraction(interaction, InteractionKind.Modal);
2475
- }
2476
- async handleStringSelectMenu(interaction) {
2477
- await this.handleCustomIdInteraction(interaction, InteractionKind.StringMenu);
2478
- }
2479
- async handleUserSelectMenu(interaction) {
2480
- await this.handleCustomIdInteraction(interaction, InteractionKind.UserMenu);
2481
- }
2482
- async handleRoleSelectMenu(interaction) {
2483
- await this.handleCustomIdInteraction(interaction, InteractionKind.RoleMenu);
2484
- }
2485
- async handleChannelSelectMenu(interaction) {
2486
- await this.handleCustomIdInteraction(interaction, InteractionKind.ChannelMenu);
2487
- }
2488
- async handleMentionableSelectMenu(interaction) {
2489
- await this.handleCustomIdInteraction(interaction, InteractionKind.MentionableMenu);
2490
- }
2491
- async handleMessageContextMenu(interaction) {
2492
- await this.processInteraction(interaction, InteractionKind.MessageContextMenu, () => interaction.commandName);
2493
- }
2494
- async handleUserContextMenu(interaction) {
2495
- await this.processInteraction(interaction, InteractionKind.UserContextMenu, () => interaction.commandName);
2496
- }
2497
- async handleAutocomplete(interaction) {
2498
- const route = slashRouteOf(interaction);
2499
- 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}`);
2500
2065
  }
2501
2066
  };
2502
2067
 
@@ -2638,7 +2203,7 @@ var Bot = class {
2638
2203
  //#endregion
2639
2204
  //#region src/version.ts
2640
2205
  /** Package version */
2641
- const version = "0.9.0-next.0";
2206
+ const version = "0.9.0-next.2";
2642
2207
 
2643
2208
  //#endregion
2644
2209
  //#region src/Seedcord.ts
@@ -2721,5 +2286,5 @@ var Seedcord = class extends ServerHost {
2721
2286
  };
2722
2287
 
2723
2288
  //#endregion
2724
- 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 };
2725
2290
  //# sourceMappingURL=index.mjs.map