@seedcord/gateway 0.1.4 → 0.2.0

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.cjs DELETED
@@ -1,2486 +0,0 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- //#region \0rolldown/runtime.js
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") {
11
- for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
- key = keys[i];
13
- if (!__hasOwnProp.call(to, key) && key !== except) {
14
- __defProp(to, key, {
15
- get: ((k) => from[k]).bind(null, key),
16
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
17
- });
18
- }
19
- }
20
- }
21
- return to;
22
- };
23
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
24
- value: mod,
25
- enumerable: true
26
- }) : target, mod));
27
-
28
- //#endregion
29
- require("reflect-metadata");
30
- let _seedcord_logger_node = require("@seedcord/logger/node");
31
- let _seedcord_core_internal = require("@seedcord/core/internal");
32
- let _seedcord_errors = require("@seedcord/errors");
33
- let _seedcord_errors_internal = require("@seedcord/errors/internal");
34
- let _seedcord_core = require("@seedcord/core");
35
- let _seedcord_logger = require("@seedcord/logger");
36
- let discord_js = require("discord.js");
37
- let _discordjs_builders = require("@discordjs/builders");
38
- let _seedcord_utils_internal = require("@seedcord/utils/internal");
39
- let _seedcord_core_plugin = require("@seedcord/core/plugin");
40
- let _seedcord_core_node_internal = require("@seedcord/core/node/internal");
41
- let _seedcord_rate_limiter = require("@seedcord/rate-limiter");
42
- let _seedcord_types_internal = require("@seedcord/types/internal");
43
- let envapt = require("envapt");
44
- let chalk = require("chalk");
45
- chalk = __toESM(chalk, 1);
46
- let envapt_legacy = require("envapt/legacy");
47
- let _seedcord_core_hmr = require("@seedcord/core/hmr");
48
- let _seedcord_utils = require("@seedcord/utils");
49
- let _seedcord_utils_node = require("@seedcord/utils/node");
50
- let node_crypto = require("node:crypto");
51
- node_crypto = __toESM(node_crypto, 1);
52
-
53
- //#region src/logger-bootstrap.ts
54
- (0, _seedcord_logger_node.installNodeDefaults)();
55
-
56
- //#endregion
57
- //#region src/bot/decorators/Events.ts
58
- /**
59
- * Registers an event handler class with one or more Discord.js events.
60
- *
61
- * Associates the decorated class with the provided Discord client events for automatic registration and execution when those events are emitted.
62
- *
63
- * Supply any number of event tuples. Each tuple can include per event options.
64
- *
65
- * @typeParam EventKey - Union of Discord.js ClientEvents keys to register for
66
- * @param defs - One or more tuples of [event, options]
67
- * @decorator
68
- * @example
69
- * ```ts
70
- * \@RegisterEvent([Events.MessageCreate])
71
- * class MessageHandler extends EventHandler<Events.MessageCreate> {
72
- * async execute() {
73
- * // Handle message creation
74
- * }
75
- * }
76
- * ```
77
- *
78
- * @example
79
- * ```ts
80
- * \@RegisterEvent([Events.MessageCreate], [Events.MessageUpdate])
81
- * class MessageHandler extends EventHandler<Events.MessageCreate | Events.MessageUpdate> {
82
- * async execute() {
83
- * // Handle message creation or update
84
- * }
85
- * }
86
- * ```
87
- *
88
- * @example
89
- * ```ts
90
- * \@RegisterEvent([Events.MessageCreate, { frequency: 'once' }], [Events.MessageUpdate])
91
- * class OneTimeMessageHandler extends EventHandler<Events.MessageCreate | Events.MessageUpdate> {
92
- * async execute() {
93
- * // Handle only the first message creation, and message update normally
94
- * }
95
- * }
96
- * ```
97
- */
98
- function RegisterEvent(...defs) {
99
- return function(constructor) {
100
- const existing = Reflect.getMetadata(_seedcord_core_internal.EventMetadataKey, constructor) ?? [];
101
- const entries = defs.map(([event, options]) => ({
102
- event,
103
- frequency: options?.frequency ?? "on"
104
- }));
105
- Reflect.defineMetadata(_seedcord_core_internal.EventMetadataKey, [...existing, ...entries], constructor);
106
- };
107
- }
108
-
109
- //#endregion
110
- //#region src/bot/decorators/Gated.ts
111
- /**
112
- * Attaches gates to a handler. The gates run before `execute`, and a gate refusing stops the handler with
113
- * the reply or drop the gate threw. Multiple gates are ANDed, so each must pass. A gate that requires a
114
- * context the handler does not provide (a button gate on a slash handler, an interaction gate on an event
115
- * handler) is a compile error at this line, and the error names the gate and the handler kind. Place it
116
- * above the route decorator.
117
- *
118
- * Combine gates with {@link and} or {@link or} to build a single gate from multiple arms.
119
- *
120
- * @example
121
- * ```ts
122
- * import { PermissionFlagsBits } from 'discord.js';
123
- *
124
- * \@Gated(GuildOnly(), RequirePermissions([PermissionFlagsBits.Administrator]))
125
- * \@SlashRoute('maintenance')
126
- * class Maintenance extends SlashHandler<'maintenance'> {
127
- * async execute() {
128
- * // both gates passed, so this runs in a guild with an admin caller
129
- * }
130
- * }
131
- * ```
132
- *
133
- * @typeParam Gates - The non-empty tuple of gates to run, in order, against the handler's context.
134
- * @typeParam TCtor - The decorated handler's constructor, checked against every gate's required context.
135
- * @param gates - One or more gates to attach, each ANDed so the handler runs only when all pass.
136
- * @param ctor - The handler class being decorated, rejected at compile time when a gate needs a context it lacks.
137
- *
138
- * @see {@link defineGate}
139
- * @see {@link defineEffectGate}
140
- * @decorator
141
- */
142
- function Gated(...gates) {
143
- return function(ctor) {
144
- const existing = Reflect.getMetadata(_seedcord_core_internal.GatedMetadataKey, ctor) ?? [];
145
- Reflect.defineMetadata(_seedcord_core_internal.GatedMetadataKey, [...existing, ...gates], ctor);
146
- };
147
- }
148
-
149
- //#endregion
150
- //#region src/bot/decorators/Middlewares.ts
151
- /**
152
- * Middleware types supported by Seedcord
153
- */
154
- let MiddlewareType = /* @__PURE__ */ function(MiddlewareType) {
155
- MiddlewareType["Interaction"] = "interaction";
156
- MiddlewareType["Event"] = "event";
157
- return MiddlewareType;
158
- }({});
159
- /**
160
- * Decorator used to register middleware with priority ordering. The lower the priority number, the earlier it runs.
161
- *
162
- * Interaction middleware cannot specify event filters.
163
- *
164
- * @param type - Middleware kind from {@link MiddlewareType}
165
- * @param priority - Ordering value where lower runs earlier. {@default `0` }
166
- * @param options - Additional registration options
167
- *
168
- * @decorator
169
- *
170
- * @example
171
- * ```ts
172
- * \@Middleware(MiddlewareType.Event, 10, { events: [Events.MessageCreate, Events.MessageUpdate] })
173
- * class MyEventMiddleware extends EventMiddleware {}
174
- * ```
175
- * @throws A **SeedcordTypeError** If priority is not a finite number
176
- * @throws A **SeedcordError** If interaction middleware specifies event filters
177
- */
178
- function Middleware(type, priority = 0, options = {}) {
179
- return (ctor) => {
180
- const normalizedPriority = Number(priority);
181
- if (!Number.isFinite(normalizedPriority)) throw new _seedcord_errors_internal.SeedcordTypeError(_seedcord_errors.SeedcordErrorCode.DecoratorInvalidMiddlewarePriority);
182
- if (type === "interaction" && Array.isArray(options.events) && options.events.length > 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.DecoratorInteractionEventFilter);
183
- const metadata = {
184
- priority: normalizedPriority,
185
- type,
186
- ...options.events && options.events.length > 0 && { events: options.events }
187
- };
188
- Reflect.defineMetadata(_seedcord_core_internal.MiddlewareMetadataKey, metadata, ctor);
189
- };
190
- }
191
-
192
- //#endregion
193
- //#region src/bot/gates/catalog/IgnoreBots.ts
194
- /**
195
- * Drops a client event whose actor is a bot, with a {@link Silence} so nothing is replied. Event-only, because a
196
- * Silence on an interaction would leave Discord's failed-interaction state. It rejects an interaction handler at
197
- * the decorator line. Takes no options, so attach it directly without calling it.
198
- *
199
- * @see {@link Gated}
200
- *
201
- * @example
202
- * ```ts
203
- * import { Events } from 'discord.js';
204
- *
205
- * \@Gated(IgnoreBots)
206
- * class OnMessage extends EventHandler<Events.MessageCreate> {
207
- * async execute() {
208
- * // ...
209
- * }
210
- * }
211
- * ```
212
- */
213
- const IgnoreBots = (0, _seedcord_core.defineGate)("IgnoreBots", (ctx) => {
214
- if (ctx.user?.bot) throw new _seedcord_core.Silence("actor is a bot");
215
- });
216
-
217
- //#endregion
218
- //#region src/bot/notices/index.ts
219
- var NotNsfw = class extends _seedcord_core_internal.GateNotice {
220
- constructor(message = "This can only be used in an age-restricted channel.") {
221
- super(message);
222
- }
223
- };
224
- var RoleHigherThanMe = class extends _seedcord_core.Notice {
225
- role;
226
- botRole;
227
- constructor(message, role, botRole) {
228
- super(message);
229
- this.role = role;
230
- this.botRole = botRole;
231
- }
232
- render() {
233
- return { components: [new _seedcord_core_internal.NoticeCard(`I cannot assign a role that is higher than me.\n\nThe role <@&${this.role.id}> is higher than my role <@&${this.botRole.id}> in the hierarchy.`).component] };
234
- }
235
- };
236
- var CannotAssignBotRole = class extends _seedcord_core.Notice {
237
- constructor(message = "I cannot assign a managed role.") {
238
- super(message);
239
- }
240
- render() {
241
- return { components: [new _seedcord_core_internal.NoticeCard("I cannot assign a managed role.").component] };
242
- }
243
- };
244
-
245
- //#endregion
246
- //#region src/bot/gates/catalog/Nsfw.ts
247
- function channelIsNsfw(channel) {
248
- if (!channel) return false;
249
- if (channel.isThread()) return channel.parent?.nsfw ?? false;
250
- return "nsfw" in channel ? channel.nsfw : false;
251
- }
252
- /**
253
- * Requires an age-restricted channel, else refuses. Pass {@link GateNoticeOptions} to reword or replace the refusal.
254
- *
255
- * A thread inherits its parent channel's nsfw flag. ModalSubmit has no reliable channel, so it is excluded.
256
- *
257
- * @param options - Reword the default refusal with `message`, or replace it with `notice`.
258
- *
259
- * @see {@link Gated}
260
- *
261
- * @example
262
- * ```ts
263
- * \@Gated(Nsfw())
264
- * \@SlashRoute('nsfwcmd')
265
- * class NsfwHandler extends SlashHandler<'nsfwcmd'> {
266
- * async execute() {
267
- * // ...
268
- * }
269
- * }
270
- * ```
271
- */
272
- function Nsfw(options) {
273
- return (0, _seedcord_core.defineGate)("Nsfw", (ctx) => {
274
- if (channelIsNsfw(ctx.interaction.channel)) return;
275
- throw (0, _seedcord_core_internal.pickNotice)(options, (message) => new NotNsfw(message));
276
- });
277
- }
278
-
279
- //#endregion
280
- //#region src/bot/injectors/EmojiInjector.ts
281
- const emojiStorage = (0, _seedcord_core_internal.accessorStore)();
282
- /**
283
- * The bot's resolved emojis, keyed by {@link EmojiMap}. Filled by {@link EmojiInjector} during startup.
284
- * A read before that throws.
285
- */
286
- const Emojis = (0, _seedcord_core_internal.guardedAccessor)("Emojis", emojiStorage);
287
- var EmojiInjector = class {
288
- core;
289
- logger = new _seedcord_logger.Logger("Emojis", { channel: "bot" });
290
- constructor(core) {
291
- this.core = core;
292
- }
293
- async init() {
294
- this.clearEmojis();
295
- if (!this.core.config.bot.emojis || Object.keys(this.core.config.bot.emojis).length === 0) {
296
- this.logger.debug("No emojis configured, skipping emoji injection.");
297
- return;
298
- }
299
- const configEmojis = this.core.config.bot.emojis;
300
- await this.core.bot.client.application?.emojis.fetch();
301
- const failures = [];
302
- const fetchedGuilds = /* @__PURE__ */ new Set();
303
- for (const [key, value] of Object.entries(configEmojis)) if ((0, _seedcord_core_internal.isEmojiTuple)(value)) await this.resolveTuple(key, value, failures, fetchedGuilds);
304
- else if (typeof value === "string") this.resolveString(key, value, failures);
305
- else failures.push(` - "${key}" has an invalid value (expected a name or [name, guildId])`);
306
- if (failures.length > 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigEmojiUnresolved, [failures.length, failures.join("\n")]);
307
- this.logger.utils.summary("Loaded emojis", { emojis: Object.keys(emojiStorage).length });
308
- }
309
- async resolveTuple(key, value, failures, fetchedGuilds) {
310
- const [emojiName, guildId] = value;
311
- const guild = this.core.bot.client.guilds.cache.get(guildId);
312
- if (!guild) {
313
- failures.push(` - "${emojiName}" for "${key}" targets guild ${guildId}, which is not available (check the Guilds intent)`);
314
- return;
315
- }
316
- if (!fetchedGuilds.has(guildId)) {
317
- await guild.emojis.fetch();
318
- fetchedGuilds.add(guildId);
319
- }
320
- const guildEmoji = guild.emojis.cache.find((e) => e.name === emojiName);
321
- if (!guildEmoji) {
322
- failures.push(` - "${emojiName}" for "${key}" was not found in guild ${guildId}`);
323
- return;
324
- }
325
- emojiStorage[key] = guildEmoji;
326
- }
327
- resolveString(key, emojiName, failures) {
328
- const appEmoji = this.core.bot.client.application?.emojis.cache.find((e) => e.name === emojiName);
329
- if (!appEmoji) {
330
- failures.push(` - "${emojiName}" for "${key}" was not found among the application emojis`);
331
- return;
332
- }
333
- emojiStorage[key] = appEmoji;
334
- }
335
- clearEmojis() {
336
- (0, _seedcord_core_internal.clearStore)(emojiStorage);
337
- }
338
- };
339
-
340
- //#endregion
341
- //#region src/bot/notices/utils.ts
342
- function mentionFor(subject) {
343
- if (subject instanceof discord_js.Role) return `<@&${subject.id}>`;
344
- return `<@${subject.id}>`;
345
- }
346
-
347
- //#endregion
348
- //#region src/bot/utilities/permissions/checkPermissions.ts
349
- function checkPermissions(a, b, c, d, e) {
350
- const { for: pFor, in: pIn, scope, inverse = false, missingNotice, dangerousNotice } = a instanceof discord_js.Role || a instanceof discord_js.GuildMember ? {
351
- for: a,
352
- in: b,
353
- scope: c,
354
- inverse: d ?? false,
355
- ...e
356
- } : a;
357
- const perms = pIn instanceof discord_js.Guild ? pFor.permissions : pIn.permissionsFor(pFor, true);
358
- (0, _seedcord_core.assertPermissions)({
359
- subject: mentionFor(pFor),
360
- permissions: perms.bitfield,
361
- scope,
362
- inverse,
363
- ...missingNotice && { missingNotice },
364
- ...dangerousNotice && { dangerousNotice }
365
- });
366
- }
367
-
368
- //#endregion
369
- //#region src/bot/utilities/permissions/checkBotPermissions.ts
370
- /**
371
- * Checks the bot's own permissions in a {@link Guild} or {@link TextChannel}. Refuses when a permission in
372
- * `scope` is missing, or with `inverse` when one is present. When the bot member is uncached, the refusal
373
- * reports every permission in `scope` as missing.
374
- *
375
- * @example
376
- * ```ts
377
- * import { PermissionFlagsBits } from 'discord.js';
378
- * import { checkBotPermissions } from '@seedcord/gateway';
379
- *
380
- * checkBotPermissions(textChannel, [PermissionFlagsBits.SendMessages, PermissionFlagsBits.ViewChannel]);
381
- * ```
382
- */
383
- function checkBotPermissions(target, scope, inverse = false, errors) {
384
- if (target instanceof discord_js.Guild) {
385
- const me = target.members.me;
386
- if (!me) {
387
- const names = scope.map((bit) => _seedcord_core_internal.PermissionNames.get(bit) ?? String(bit));
388
- throw new ((errors?.missingNotice) ?? _seedcord_core_internal.MissingPermissions)(void 0, "The bot", names);
389
- }
390
- checkPermissions(me, target, scope, inverse, errors);
391
- return;
392
- }
393
- const me = target.guild.members.me;
394
- if (!me) {
395
- new _seedcord_logger.Logger("checkBotPermissions", { channel: "errors" }).warn(`Bot member is unavailable in guild ${target.guild.id} while checking permissions in channel ${target.id}`);
396
- const names = scope.map((bit) => _seedcord_core_internal.PermissionNames.get(bit) ?? String(bit));
397
- throw new ((errors?.missingNotice) ?? _seedcord_core_internal.MissingPermissions)(void 0, "The bot", names);
398
- }
399
- checkPermissions(me, target, scope, inverse, errors);
400
- }
401
-
402
- //#endregion
403
- //#region src/bot/utilities/permissions/hasPermsToAssign.ts
404
- function botRoleOf(guild) {
405
- const botRole = guild.roles.botRoleFor(guild.client.user);
406
- if (!botRole) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreBotRoleMissing, [guild.id]);
407
- return botRole;
408
- }
409
- /**
410
- * 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.
411
- *
412
- * @param roleOrOptions - Target role or complete options for the check
413
- * @throws A `Notice` on refusal, or a `SeedcordError` when the bot has no role in the guild's role cache.
414
- *
415
- * @example
416
- * ```ts
417
- * import { hasPermsToAssign } from '@seedcord/gateway';
418
- *
419
- * // Check if the bot can assign a specific role
420
- * hasPermsToAssign(role);
421
- *
422
- * // Check with custom error handling
423
- * hasPermsToAssign({
424
- * targetRole: role,
425
- * noticeOverrides: {
426
- * higherNotice: CustomHigherNotice,
427
- * managedNotice: CustomManagedNotice,
428
- * missingNotice: CustomMissingPermissionsNotice
429
- * }
430
- * });
431
- * ```
432
- */
433
- function hasPermsToAssign(roleOrOptions) {
434
- const { targetRole, noticeOverrides: errors } = roleOrOptions instanceof discord_js.Role ? {
435
- targetRole: roleOrOptions,
436
- noticeOverrides: void 0
437
- } : roleOrOptions;
438
- const HigherNotice = errors?.higherNotice ?? RoleHigherThanMe;
439
- const ManagedNotice = errors?.managedNotice ?? CannotAssignBotRole;
440
- const MissingNotice = errors?.missingNotice ?? _seedcord_core_internal.MissingPermissions;
441
- const botRole = botRoleOf(targetRole.guild);
442
- if (targetRole.comparePositionTo(botRole) >= 0) throw new HigherNotice("Role is higher than me", targetRole, botRole);
443
- if (targetRole.managed) throw new ManagedNotice(`Cannot assign managed role ${targetRole.name}`);
444
- checkBotPermissions(targetRole.guild, [discord_js.PermissionFlagsBits.ManageRoles], false, { missingNotice: MissingNotice });
445
- }
446
-
447
- //#endregion
448
- //#region src/bot/confirm/reserved.ts
449
- const RESERVED_CONFIRM_PREFIX = "__seedcord_confirm";
450
- const CONFIRM_DEF = new _seedcord_core.CustomId(RESERVED_CONFIRM_PREFIX).oneOf("choice", ["confirm", "cancel"]);
451
-
452
- //#endregion
453
- //#region src/bot/confirm/getConfirmation.ts
454
- const DEFAULT_TIMEOUT_MS = 3e4;
455
- const CONFIRM_IDS = {
456
- confirm: CONFIRM_DEF.encode({ choice: "confirm" }),
457
- cancel: CONFIRM_DEF.encode({ choice: "cancel" })
458
- };
459
- var ConfirmButtons = class extends _seedcord_core.RowComponent {
460
- constructor(options) {
461
- super("button");
462
- this.instance.addComponents(new _discordjs_builders.ButtonBuilder().setCustomId(CONFIRM_IDS.confirm).setLabel(options?.confirmLabel ?? "Confirm").setStyle(options?.confirmStyle ?? discord_js.ButtonStyle.Danger), new _discordjs_builders.ButtonBuilder().setCustomId(CONFIRM_IDS.cancel).setLabel(options?.cancelLabel ?? "Cancel").setStyle(discord_js.ButtonStyle.Secondary));
463
- }
464
- };
465
- var ConfirmPromptCard = class extends _seedcord_core.BuilderComponent {
466
- constructor(text, options) {
467
- super("container");
468
- this.instance.addTextDisplayComponents(new _discordjs_builders.TextDisplayBuilder().setContent(text)).addActionRowComponents(new ConfirmButtons(options).component);
469
- }
470
- };
471
- function defaultPrompt(text, options) {
472
- return { components: [new ConfirmPromptCard(text, options).component] };
473
- }
474
- async function collectChoice(message, userId, timeoutMs) {
475
- try {
476
- return await message.awaitMessageComponent({
477
- componentType: discord_js.ComponentType.Button,
478
- time: timeoutMs,
479
- filter: (click) => click.user.id === userId && CONFIRM_DEF.owns(click.customId)
480
- });
481
- } catch {
482
- return null;
483
- }
484
- }
485
- async function settle(sender, message, outcome) {
486
- if (outcome === void 0) {
487
- await sender.delete(message).catch(() => void 0);
488
- return;
489
- }
490
- await sender.edit(message, typeof outcome === "function" ? await outcome() : outcome);
491
- }
492
- async function getConfirmation(handler, prompt, options) {
493
- const { ephemeral = true, timeoutMs = DEFAULT_TIMEOUT_MS } = options ?? {};
494
- const response = typeof prompt === "string" ? defaultPrompt(prompt, options) : await prompt(CONFIRM_IDS);
495
- const interaction = handler.getEvent();
496
- const sender = handler.getSender();
497
- const message = await sender.send(response, { ephemeral });
498
- const winner = await collectChoice(message, interaction.user.id, timeoutMs);
499
- if (!winner) {
500
- await settle(sender, message, options?.onTimeout);
501
- return false;
502
- }
503
- await winner.deferUpdate().catch(() => void 0);
504
- const confirmed = winner.customId === CONFIRM_IDS.confirm;
505
- await settle(sender, message, confirmed ? options?.onConfirm : options?.onCancel);
506
- return confirmed;
507
- }
508
-
509
- //#endregion
510
- //#region src/handlers/BaseHandler.ts
511
- /**
512
- * The gateway base, fixes the core base's `TCore` to the gateway {@link Core} and adds the `getEvent`
513
- * gate hook.
514
- */
515
- var BaseHandler = class extends _seedcord_core.BaseHandler {
516
- getEvent() {
517
- return this.event;
518
- }
519
- };
520
-
521
- //#endregion
522
- //#region src/handlers/interaction/InteractionMiddleware.ts
523
- /**
524
- * Base class for interaction middleware
525
- *
526
- * Middleware runs before interaction handlers and can modify behavior or block execution.
527
- * Unlike handlers, middleware should not send responses directly.
528
- *
529
- * @typeParam Repliable - The interaction type this middleware processes
530
- */
531
- var InteractionMiddleware = class extends BaseHandler {
532
- constructor(event, core) {
533
- super(event, core, void 0, "interactions");
534
- }
535
- };
536
-
537
- //#endregion
538
- //#region src/bot/ReplySender.ts
539
- function isReplyFile(file) {
540
- return typeof file === "object" && "data" in file && ArrayBuffer.isView(file.data);
541
- }
542
- function toDjsFile(file) {
543
- if (!isReplyFile(file)) return file;
544
- return {
545
- attachment: Buffer.from(file.data),
546
- name: file.name,
547
- ...file.description !== void 0 && { description: file.description },
548
- ...file.title !== void 0 && { title: file.title }
549
- };
550
- }
551
- function seedState(interaction) {
552
- if (interaction.deferred && interaction.ephemeral === null) return "deferred-update";
553
- if (interaction.replied) return "replied";
554
- if (interaction.deferred) return "deferred-reply";
555
- return "unacked";
556
- }
557
- /** Writes interaction responses through discord.js. The repliable handler bases and the dispatcher construct it. */
558
- var ReplySender = class extends _seedcord_core_internal.BaseReplySender {
559
- interaction;
560
- constructor(interaction, routeId, bus) {
561
- super(routeId, {
562
- bus,
563
- interactionId: interaction.id
564
- }, seedState(interaction));
565
- this.interaction = interaction;
566
- }
567
- async writeReply(response, opts) {
568
- const result = await this.interaction.reply({
569
- ...this.replyOptions(response, (0, _seedcord_core_internal.sendFlags)(opts)),
570
- withResponse: true
571
- });
572
- return this.createdMessage(result);
573
- }
574
- async writeDefer(opts) {
575
- await this.interaction.deferReply({ flags: (0, _seedcord_core_internal.deferFlags)(opts) });
576
- }
577
- async writeDeferUpdate() {
578
- await this.sourceInteraction("deferUpdate").deferUpdate();
579
- }
580
- async writeUpdate(response) {
581
- const result = await this.sourceInteraction("update").update({
582
- ...this.updateOptions(response),
583
- withResponse: true
584
- });
585
- return this.createdMessage(result);
586
- }
587
- async writeFollowUp(response, opts) {
588
- return await this.interaction.followUp(this.replyOptions(response, (0, _seedcord_core_internal.sendFlags)(opts)));
589
- }
590
- async writeEditOriginal(response) {
591
- return await this.interaction.editReply(this.editOptions(response));
592
- }
593
- async writeEditTarget(targetId, response) {
594
- return await this.interaction.webhook.editMessage(targetId, this.editOptions(response));
595
- }
596
- async writeDeleteOriginal() {
597
- await this.interaction.deleteReply();
598
- }
599
- async writeDeleteTarget(targetId) {
600
- await this.interaction.deleteReply(targetId);
601
- }
602
- async writeModal(data) {
603
- const { interaction } = this;
604
- if (interaction.isModalSubmit()) this.rejectModalSubmit();
605
- await interaction.showModal(data);
606
- }
607
- guardModalSource() {
608
- if (this.interaction.isModalSubmit()) this.rejectModalSubmit();
609
- }
610
- rejectModalSubmit() {
611
- throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ReplyIllegalAckState, [
612
- "showModal",
613
- "this interaction is a modal submit",
614
- "A modal cannot open another modal.",
615
- this.routeId
616
- ]);
617
- }
618
- sourceInteraction(method) {
619
- const { interaction } = this;
620
- if (interaction.isMessageComponent()) return interaction;
621
- if (interaction.isModalSubmit() && interaction.isFromMessage()) return interaction;
622
- throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
623
- }
624
- replyOptions(response, flags) {
625
- const reply = this.serialize(response);
626
- return {
627
- components: reply.components,
628
- flags,
629
- ...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
630
- ...reply.files && { files: reply.files.map(toDjsFile) }
631
- };
632
- }
633
- editOptions(response) {
634
- const reply = this.serialize(response);
635
- return {
636
- components: reply.components,
637
- flags: discord_js.MessageFlags.IsComponentsV2,
638
- ...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
639
- ...reply.files && { files: reply.files.map(toDjsFile) }
640
- };
641
- }
642
- updateOptions(response) {
643
- const reply = this.serialize(response);
644
- return {
645
- components: reply.components,
646
- flags: discord_js.MessageFlags.IsComponentsV2,
647
- ...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
648
- ...reply.files && { files: reply.files.map(toDjsFile) }
649
- };
650
- }
651
- createdMessage(result) {
652
- return result.resource?.message ?? void 0;
653
- }
654
- };
655
-
656
- //#endregion
657
- //#region src/handlers/RepliableHandler.ts
658
- /**
659
- * Shared base the repliable interaction handlers extend.
660
- *
661
- * Not a public entry point. Extend {@link SlashHandler}, {@link ButtonHandler}, {@link ModalHandler},
662
- * {@link SelectMenuHandler}, or {@link ContextMenuHandler} instead. This class binds the core reply base
663
- * to the gateway sender.
664
- *
665
- * @typeParam Event - The repliable interaction type this handler processes
666
- */
667
- var RepliableHandler = class extends _seedcord_core.RepliableHandler {
668
- /** @internal */
669
- getEvent() {
670
- return this.event;
671
- }
672
- buildSender(event, core, routeId) {
673
- return new ReplySender(event, routeId, core.bus);
674
- }
675
- };
676
-
677
- //#endregion
678
- //#region src/handlers/interaction/InteractionHandler.ts
679
- /**
680
- * Shared base the typed interaction handlers extend.
681
- *
682
- * Not a public entry point. Extend {@link SlashHandler}, {@link ButtonHandler}, {@link ModalHandler},
683
- * or {@link SelectMenuHandler} instead. This class adds `showModal` on top of the reply members those bases
684
- * share.
685
- *
686
- * @typeParam Repliable - The interaction type this handler processes
687
- */
688
- var InteractionHandler = class extends RepliableHandler {
689
- constructor(event, core, dispatch) {
690
- super(event, core, dispatch);
691
- }
692
- /**
693
- * Open a modal. Must be the initial response to this interaction. The modal kind rejects this call at
694
- * compile time (Discord forbids a modal in response to a modal).
695
- */
696
- showModal(modal) {
697
- return this.sender.showModal(modal);
698
- }
699
- };
700
-
701
- //#endregion
702
- //#region src/bot/utilities/miscellaneous/slashRouteOf.ts
703
- function slashRouteOf(interaction) {
704
- const group = interaction.options.getSubcommandGroup(false) ?? void 0;
705
- const subcommand = interaction.options.getSubcommand(false) ?? void 0;
706
- return (0, _seedcord_utils_internal.buildSlashRoute)(interaction.commandName, subcommand, group);
707
- }
708
-
709
- //#endregion
710
- //#region src/handlers/interaction/SlashHandler.ts
711
- /**
712
- * Base class for a chat-input (slash) command handler.
713
- *
714
- * Pass the route from the generated registry as the generic, the same string as `@SlashRoute`, then read
715
- * `this.options` for a single command or `this.match` for several. Command authoring stays plain
716
- * discord.js, `seedcord codegen` reads its `toJSON()` to type these options.
717
- *
718
- * @typeParam Route - One or more route keys from {@link SlashOptionRegistry}, e.g. `'ban'` or `'ban' | 'kick'`.
719
- * @typeParam Cache - The interaction cache state, `'cached'` by default.
720
- *
721
- * @example
722
- * ```ts
723
- * \@SlashRoute('ban')
724
- * class BanHandler extends SlashHandler<'ban'> {
725
- * async execute() {
726
- * const target = this.options.getUser('target'); // User
727
- * const reason = this.options.getString('reason'); // string | null
728
- * }
729
- * }
730
- * ```
731
- */
732
- var SlashHandler = class extends InteractionHandler {
733
- /**
734
- * The typed options for this command's route. Required options drop the null, choices narrow to their
735
- * literal union, and only the getters for kinds this command actually uses appear. Use `this.event.options`
736
- * directly for anything outside this view, such as narrowing a channel option by type.
737
- */
738
- get options() {
739
- return this.event.options;
740
- }
741
- /**
742
- * Run the arm for whichever command fired. Use this only when the handler is registered for several
743
- * routes.
744
- *
745
- * Provide one arm per registered route, keyed by its route string, and each arm receives that route's
746
- * own narrowed options. The arms must cover every route in the generic, a missing route or an unknown
747
- * key is a compile error.
748
- *
749
- * @param arms - One handler per registered route, keyed by route string.
750
- * @returns The result of the arm that ran.
751
- *
752
- * @example
753
- * ```ts
754
- * \@SlashRoute('ban', 'kick')
755
- * class ModHandler extends SlashHandler<'ban' | 'kick'> {
756
- * async execute() {
757
- * await this.match({
758
- * ban: (options) => this.event.guild?.members.ban(options.getUser('target', true).id),
759
- * kick: (options) => this.event.guild?.members.kick(options.getUser('member', true).id)
760
- * });
761
- * }
762
- * }
763
- * ```
764
- */
765
- async match(arms) {
766
- const route = slashRouteOf(this.event);
767
- const arm = Object.hasOwn(arms, route) ? arms[route] : void 0;
768
- if (!arm) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.SlashMatchArmMissing, [route]);
769
- return await arm(this.options);
770
- }
771
- };
772
-
773
- //#endregion
774
- //#region src/handlers/interaction/ContextMenuHandler.ts
775
- /**
776
- * Base class for a context-menu command handler (right-click a user or a message).
777
- *
778
- * Pass the kind from `discord.js`'s {@link ApplicationCommandType} as the generic, the same value as
779
- * `@ContextMenuRoute`. Read the right-clicked entity from `this.target`, a `User` for a user menu and a
780
- * `Message` for a message menu. Context menus carry no options, so a handler registered for several names
781
- * reads `this.target` uniformly with no per-name branch.
782
- *
783
- * @typeParam Kind - `ApplicationCommandType.User` or `ApplicationCommandType.Message`.
784
- * @typeParam Cache - The interaction cache state, `'cached'` by default.
785
- *
786
- * @example
787
- * ```ts
788
- * \@ContextMenuRoute(ApplicationCommandType.Message, 'Report Message')
789
- * class ReportMessage extends ContextMenuHandler<ApplicationCommandType.Message> {
790
- * async execute() {
791
- * const message = this.target;
792
- * }
793
- * }
794
- * ```
795
- */
796
- var ContextMenuHandler = class extends InteractionHandler {
797
- get target() {
798
- const event = this.event;
799
- return "targetUser" in event ? event.targetUser : event.targetMessage;
800
- }
801
- /**
802
- * The invoking guild member, resolved only on user menus. Reading it on a message menu is a compile
803
- * error since the type is `never` there.
804
- */
805
- get targetMember() {
806
- return this.event.targetMember;
807
- }
808
- };
809
-
810
- //#endregion
811
- //#region src/handlers/interaction/AutocompleteHandler.ts
812
- /**
813
- * Base class for a Discord autocomplete handler.
814
- *
815
- * Pass the command route(s) from the generated registry as the generic, the same string(s) as
816
- * `@AutocompleteRoute`. Branch on the focused field with `this.match`, read already-entered sibling options
817
- * with `this.options`, and find which command fired with `this.route`.
818
- *
819
- * @typeParam Route - One or more route keys from {@link SlashOptionRegistry}, e.g. `'search'` or `'search' | 'find'`.
820
- * @typeParam Cache - The interaction cache state, `'cached'` by default.
821
- *
822
- * @example
823
- * ```ts
824
- * \@AutocompleteRoute('search')
825
- * class SearchAutocomplete extends AutocompleteHandler<'search'> {
826
- * async execute() {
827
- * await this.match({
828
- * query: (value, respond) => respond([{ name: value, value }])
829
- * });
830
- * }
831
- * }
832
- * ```
833
- */
834
- var AutocompleteHandler = class extends BaseHandler {
835
- constructor(event, core, dispatch) {
836
- super(event, core, dispatch, "interactions");
837
- }
838
- decodedFocused;
839
- /**
840
- * The focused option for this interaction. `value` is always the raw partial string the user is typing,
841
- * even for an integer or number option, so coerce it yourself when you need a number.
842
- */
843
- get focused() {
844
- if (this.decodedFocused) return this.decodedFocused;
845
- const raw = this.event.options.getFocused(true);
846
- const focused = {
847
- name: raw.name,
848
- value: raw.value
849
- };
850
- this.decodedFocused = focused;
851
- return focused;
852
- }
853
- /**
854
- * Run the arm for the focused field. Because an autocomplete always dispatches by which field is
855
- * focused, match is how you read it. There is no single-field shortcut the way slash/component handlers have one.
856
- *
857
- * Provide one arm per autocompletable field across the registered commands, keyed by field name. Each
858
- * arm receives the focused partial value and a `respond` typed to that field's choice value. The
859
- * arms must cover every autocompletable field, a missing field or an unknown key is a compile error. A
860
- * focused field with no arm, only reachable from a stale-deployed command, throws at runtime.
861
- *
862
- * @param arms - One handler per autocompletable field, keyed by field name.
863
- * @returns The result of the arm that ran.
864
- *
865
- * @example
866
- * ```ts
867
- * \@AutocompleteRoute('search')
868
- * class SearchAutocomplete extends AutocompleteHandler<'search'> {
869
- * async execute() {
870
- * await this.match({
871
- * query: (value, respond) => respond(titles(value).map((s) => ({ name: s, value: s }))),
872
- * tag: (value, respond) => respond(tags(value).map((s) => ({ name: s, value: s })))
873
- * });
874
- * }
875
- * }
876
- * ```
877
- */
878
- async match(arms) {
879
- const { name, value } = this.focused;
880
- const respond = (choices) => this.respond(choices);
881
- const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
882
- if (!arm) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.AutocompleteMatchArmMissing, [name]);
883
- return await arm(value, respond);
884
- }
885
- /** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
886
- async respond(choices) {
887
- const routeId = this.dispatch?.routeId ?? "autocomplete";
888
- await (0, _seedcord_core_internal.reportedWrite)({
889
- bus: this.core.bus,
890
- interactionId: this.event.id
891
- }, routeId, "respond", () => this.event.respond(choices));
892
- }
893
- /** The firing command route, for a field whose completion differs per registered command. */
894
- get route() {
895
- return slashRouteOf(this.event);
896
- }
897
- /**
898
- * The already-entered options on this command, restricted to the kinds Discord resolves during autocomplete
899
- * (string, integer, number, boolean) with every read nullable, since a sibling is partial while the user
900
- * types the focused field. The focused field is on `this.focused`.
901
- */
902
- get options() {
903
- return this.event.options;
904
- }
905
- };
906
-
907
- //#endregion
908
- //#region src/handlers/interaction/components/ComponentHandler.ts
909
- /**
910
- * Shared base the customId-routed component handlers extend.
911
- *
912
- * Not a public entry point. Extend {@link ButtonHandler}, {@link SelectMenuHandler}, or {@link ModalHandler}
913
- * instead. This class defines the customId decode and the route matching those bases share.
914
- *
915
- * @typeParam Event - The component interaction type this handler processes
916
- * @typeParam Defs - The customId route definitions registered on the concrete handler
917
- */
918
- var ComponentHandler = class extends InteractionHandler {
919
- /** Rewrite the source message this component interaction came from. */
920
- update(response) {
921
- return this.sender.update(response);
922
- }
923
- /** Acknowledge the component without changing the source message. */
924
- deferUpdate() {
925
- return this.sender.deferUpdate();
926
- }
927
- decoded;
928
- get route() {
929
- this.decoded ??= (0, _seedcord_core_internal.decodeComponentRoute)(this.constructor, this.event.customId);
930
- return this.decoded;
931
- }
932
- /**
933
- * The decoded params of the single route this handler is registered for.
934
- *
935
- * Reading this decodes `this.event.customId` once (cached after the first read) and throws
936
- * `StaleCustomId` or `InvalidCustomId` when the wire no longer matches the current shape, which the
937
- * controller boundary turns into a reply. On a handler registered for several routes this is
938
- * `never`. Use {@link match} instead.
939
- */
940
- get params() {
941
- return this.route.params;
942
- }
943
- /**
944
- * Run the arm for whichever route the component was minted from. Use this only when the handler is
945
- * registered for several routes. A single-route handler reads `this.params` directly. On a multi-route
946
- * handler `this.params` is `never`, leaving match as the only way to read the decoded params.
947
- *
948
- * Provide one arm per registered route, keyed by its prefix, and each arm receives that route's own
949
- * decoded params. The arms cover every registered route prefix, checked at compile time, and a prefix
950
- * unmatched at runtime throws `CustomIdMatchArmMissing`. Decoding runs before any arm, so a stale or
951
- * corrupt wire throws before an arm body executes.
952
- *
953
- * @param arms - One callback per registered route, keyed by prefix.
954
- * @returns The result of the arm that ran.
955
- *
956
- * @example
957
- * ```ts
958
- * \@ButtonRoute(Approve, Reject)
959
- * class ReviewButtons extends ButtonHandler<[typeof Approve, typeof Reject]> {
960
- * async execute() {
961
- * await this.match({
962
- * approve: ({ userId }) => this.reply(`approved <@${userId}>`),
963
- * reject: ({ userId }) => this.reply(`rejected <@${userId}>`)
964
- * });
965
- * }
966
- * }
967
- * ```
968
- */
969
- async match(arms) {
970
- const { prefix, params } = this.route;
971
- const arm = Object.hasOwn(arms, prefix) ? arms[prefix] : void 0;
972
- if (!arm) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CustomIdMatchArmMissing, [prefix]);
973
- return await arm(params);
974
- }
975
- };
976
-
977
- //#endregion
978
- //#region src/handlers/interaction/components/ButtonHandler.ts
979
- /**
980
- * Base class for a button interaction handler.
981
- *
982
- * Register the customId definitions this handler decodes with `@ButtonRoute`, list the same ones in the
983
- * generic, then read `this.params` for a single route or `this.match` for several. Reply through the handler
984
- * members, or rewrite the source message with `this.update`. Passing different definitions to the decorator
985
- * and the generic is a compile error.
986
- *
987
- * @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof ApproveId]`.
988
- * @typeParam Cache - The interaction cache state, `'cached'` by default.
989
- *
990
- * @example
991
- * ```ts
992
- * \@ButtonRoute(ApproveId)
993
- * class ApproveButton extends ButtonHandler<[typeof ApproveId]> {
994
- * async execute() {
995
- * const { userId } = this.params;
996
- * await this.reply(`approved <@${userId}>`);
997
- * }
998
- * }
999
- * ```
1000
- */
1001
- var ButtonHandler = class extends ComponentHandler {};
1002
-
1003
- //#endregion
1004
- //#region src/handlers/interaction/components/ModalHandler.ts
1005
- /**
1006
- * Base class for a modal submit handler.
1007
- *
1008
- * Register the customId definitions this handler decodes with `@ModalRoute`, list the same ones in the
1009
- * generic, then read `this.params` for a single route or `this.match` for several. Read the submitted
1010
- * inputs from `this.event.fields`, and reply through the handler members. Discord forbids opening a modal in
1011
- * response to a modal, so `showModal` fails compilation on this kind.
1012
- *
1013
- * @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof ConfigId]`.
1014
- * @typeParam Cache - The interaction cache state, `'cached'` by default.
1015
- *
1016
- * @example
1017
- * ```ts
1018
- * \@ModalRoute(ConfigId)
1019
- * class ConfigModal extends ModalHandler<[typeof ConfigId]> {
1020
- * async execute() {
1021
- * const { guildId } = this.params;
1022
- * const name = this.event.fields.getTextInputValue('name');
1023
- * await this.reply(`saved ${name} for ${guildId}`);
1024
- * }
1025
- * }
1026
- * ```
1027
- */
1028
- var ModalHandler = class extends ComponentHandler {
1029
- /** Rewrite the message this modal was opened from. Throws when a command opened the modal. */
1030
- update(response) {
1031
- this.ensureSourceMessage("update");
1032
- return super.update(response);
1033
- }
1034
- /** Acknowledge the submit without changing the source message. Throws when a command opened the modal. */
1035
- deferUpdate() {
1036
- this.ensureSourceMessage("deferUpdate");
1037
- return super.deferUpdate();
1038
- }
1039
- ensureSourceMessage(method) {
1040
- if (this.event.isFromMessage()) return;
1041
- throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
1042
- }
1043
- };
1044
-
1045
- //#endregion
1046
- //#region src/handlers/interaction/components/SelectMenuHandler.ts
1047
- /**
1048
- * Base class for a select menu handler.
1049
- *
1050
- * Pass the select kind first and the customId definitions second, the same order as `@SelectMenuRoute`,
1051
- * so `this.event` and `this.event.values` are narrowed to that kind. Read `this.params` for a single
1052
- * route or `this.match` for several, and reply through the handler members or rewrite the source message
1053
- * with `this.update`.
1054
- *
1055
- * @typeParam Kind - The select kind from {@link SelectMenuKind}, e.g. `SelectMenuKind.User`.
1056
- * @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof AssignId]`.
1057
- * @typeParam Cache - The interaction cache state, `'cached'` by default.
1058
- *
1059
- * @example
1060
- * ```ts
1061
- * \@SelectMenuRoute(SelectMenuKind.User, AssignId)
1062
- * class AssignSelect extends SelectMenuHandler<SelectMenuKind.User, [typeof AssignId]> {
1063
- * async execute() {
1064
- * const { roleId } = this.params;
1065
- * await this.reply(`assigning ${this.event.values.length} member(s) to <@&${roleId}>`);
1066
- * }
1067
- * }
1068
- * ```
1069
- */
1070
- var SelectMenuHandler = class extends ComponentHandler {};
1071
-
1072
- //#endregion
1073
- //#region src/handlers/event/EventHandler.ts
1074
- /**
1075
- * Base class for a Discord client event handler.
1076
- *
1077
- * Pass the event name(s) as the generic, the same one(s) as `@RegisterEvent`. A single-event handler reads
1078
- * `this.event` (the payload tuple) directly. A handler registered for several events branches with `this.match`,
1079
- * keyed by event name, since the union of payload tuples is not directly readable.
1080
- *
1081
- * @typeParam Names - One or more `ClientEvents` keys, e.g. `Events.MessageCreate` or `Events.MessageCreate | Events.MessageUpdate`.
1082
- *
1083
- * @example
1084
- * ```ts
1085
- * \@RegisterEvent([Events.MessageCreate], [Events.MessageUpdate])
1086
- * class PingPong extends EventHandler<Events.MessageCreate | Events.MessageUpdate> {
1087
- * async execute() {
1088
- * await this.match({
1089
- * [Events.MessageCreate]: (message) => message.reply('pong'),
1090
- * [Events.MessageUpdate]: (_oldMessage, edited) => edited.reply('pong')
1091
- * });
1092
- * }
1093
- * }
1094
- * ```
1095
- */
1096
- var EventHandler = class extends BaseHandler {
1097
- firedEvent;
1098
- constructor(event, core, eventName) {
1099
- super(event, core, void 0, "events");
1100
- this.firedEvent = eventName;
1101
- }
1102
- /**
1103
- * Run the arm for whichever event fired. Use this only when the handler is registered for several
1104
- * events. A single-event handler reads `this.event` directly. On a multi-event handler `this.event` is
1105
- * `never`, so match is the only way to read the payload.
1106
- *
1107
- * Provide one arm per registered event, keyed by its name, and each arm receives that event's own
1108
- * payload tuple. The arms must cover every event in the generic, a missing event or an unknown key is a
1109
- * compile error.
1110
- *
1111
- * @param arms - One handler per registered event, keyed by event name.
1112
- * @returns The result of the arm that ran.
1113
- *
1114
- * @example
1115
- * ```ts
1116
- * \@RegisterEvent([Events.MessageCreate], [Events.MessageUpdate])
1117
- * class MessageWatcher extends EventHandler<Events.MessageCreate | Events.MessageUpdate> {
1118
- * async execute() {
1119
- * await this.match({
1120
- * [Events.MessageCreate]: (message) => this.scan(message),
1121
- * [Events.MessageUpdate]: (_old, updated) => this.scan(updated)
1122
- * });
1123
- * }
1124
- * }
1125
- * ```
1126
- */
1127
- async match(arms) {
1128
- const name = this.firedEvent;
1129
- if (name === void 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.EventMatchArmMissing, ["<no event>"]);
1130
- const arm = arms[name];
1131
- if (typeof arm !== "function") throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.EventMatchArmMissing, [name]);
1132
- return await arm(...this.getEvent());
1133
- }
1134
- };
1135
-
1136
- //#endregion
1137
- //#region src/handlers/event/EventMiddleware.ts
1138
- /**
1139
- * Base class for Discord event middleware.
1140
- *
1141
- * Middleware runs before event handlers and can stop the event by throwing a `Silence`. Unlike `EventHandler`, it
1142
- * runs the SAME for every event it is registered for, so it has no `match`. Specify a single event in the
1143
- * generic and `{ events }` to read `this.event` fully typed. Span several events (or omit `{ events }` for a
1144
- * catchall) and `this.event` narrows to `never`, read `this.eventName` and do work that does not depend on the
1145
- * payload shape. A middleware that needs each event's payload is written one event per class.
1146
- *
1147
- * @typeParam EventName - One or more `ClientEvents` keys. Defaults to every event (catchall).
1148
- */
1149
- var EventMiddleware = class extends BaseHandler {
1150
- firedEvent;
1151
- constructor(event, core, eventName) {
1152
- super(event, core, void 0, "events");
1153
- this.firedEvent = eventName;
1154
- }
1155
- /**
1156
- * The event that fired this middleware. Read it on a catchall or multi-event middleware, where `this.event`
1157
- * is `never`, to do work that does not depend on the payload shape. A single-event middleware reads
1158
- * `this.event` directly and does not need this.
1159
- */
1160
- get eventName() {
1161
- if (this.firedEvent === void 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.EventMiddlewareNameUnavailable);
1162
- return this.firedEvent;
1163
- }
1164
- };
1165
-
1166
- //#endregion
1167
- //#region src/pagination/Paginator.ts
1168
- function contextOf(interaction, core) {
1169
- return {
1170
- interaction,
1171
- user: interaction.user,
1172
- guild: interaction.guild,
1173
- core
1174
- };
1175
- }
1176
- /**
1177
- * A restart-proof paginator. Each nav button's customId encodes its full target page, so clicks are
1178
- * idempotent and survive a restart. A persistent `@ButtonRoute` on `Handler` dispatches the clicks.
1179
- *
1180
- * @typeParam Item - The item type, inferred from the source.
1181
- * @typeParam Prefix - The route prefix, inferred from `config.prefix`.
1182
- *
1183
- * @example
1184
- * ```ts
1185
- * export const Bans = new Paginator({
1186
- * prefix: 'bans',
1187
- * source: new ArraySource((ctx) => ctx.guild.bans.fetch().then((b) => [...b.values()]), { perPage: 10 }),
1188
- * renderItem: (ban) => ban.user.tag
1189
- * });
1190
- *
1191
- * \@ButtonRoute(Bans.cursor)
1192
- * export class BansNav extends Bans.Handler {}
1193
- * ```
1194
- */
1195
- var Paginator = class extends _seedcord_core.PaginatorBase {
1196
- /** The nav handler base. Extend it with an empty body and decorate it, `@ButtonRoute(p.cursor)`. */
1197
- Handler;
1198
- constructor(config) {
1199
- super(config);
1200
- const loadPage = (ctx, n) => this.page(ctx, n);
1201
- this.Handler = class Nav extends ButtonHandler {
1202
- async execute() {
1203
- await this.deferUpdate();
1204
- const response = await loadPage(contextOf(this.event, this.core), this.params.page);
1205
- await this.update(response);
1206
- }
1207
- };
1208
- }
1209
- /**
1210
- * Render page 0 and send it through the handler's sender, which picks reply, edit, or followUp from its
1211
- * ack state.
1212
- *
1213
- * @param handler - The handler starting the paginator, normally `this`.
1214
- */
1215
- async start(handler) {
1216
- const interaction = handler.getEvent();
1217
- const response = await this.page(contextOf(interaction, handler.core), 0);
1218
- return handler.getSender().send(response, { ephemeral: this.config.ephemeral ?? false });
1219
- }
1220
- };
1221
-
1222
- //#endregion
1223
- //#region src/pagination/sources.ts
1224
- /**
1225
- * A source for a bounded list you can load whole. It loads the full list on every click, then slices the
1226
- * page, so the real total is known (a real last button, "Page X of Y"). Cache inside your loader if the
1227
- * load is expensive.
1228
- *
1229
- * @typeParam Item - The item type, inferred from the loader's return.
1230
- */
1231
- var ArraySource = class extends _seedcord_core.ArraySource {};
1232
- /**
1233
- * A source for a large or unknown-length set you fetch one page at a time (SQL LIMIT/OFFSET, a paged API).
1234
- * The fetcher receives the page index and the page size and reports whether a next page exists. A cursor
1235
- * source has no cheap total, so `totalPages` is undefined, the last button is omitted, and the indicator
1236
- * reads "Page X".
1237
- *
1238
- * @typeParam Item - The item type, inferred from the fetcher's slice.
1239
- */
1240
- var CursorSource = class extends _seedcord_core.CursorSource {};
1241
-
1242
- //#endregion
1243
- //#region src/subscribers/index.ts
1244
- /**
1245
- * Base class for a gateway subscriber, binding `this.core` to the gateway {@link Core}.
1246
- *
1247
- * @typeParam KeyOfSubscribers - The subscription key this handler receives
1248
- */
1249
- var Subscriber = class extends _seedcord_core.Subscriber {};
1250
- /**
1251
- * Base class for a gateway subscriber that delivers its event to a Discord webhook, binding
1252
- * `this.core` to the gateway {@link Core}.
1253
- *
1254
- * @typeParam KeyOfSubscribers - The subscription key this reporter receives
1255
- */
1256
- var WebhookLog = class extends _seedcord_core.WebhookLog {};
1257
-
1258
- //#endregion
1259
- //#region src/plugin.ts
1260
- /**
1261
- * Base class for a gateway plugin, binding `this.core` to the gateway {@link Core}.
1262
- *
1263
- * A plugin that runs on either transport extends the base from `@seedcord/core/plugin`, whose
1264
- * `this.core` is typed to the shared members only.
1265
- *
1266
- * @typeParam Opts - The transport and runtime the plugin declares.
1267
- */
1268
- var Plugin = class extends _seedcord_core_plugin.Plugin {};
1269
-
1270
- //#endregion
1271
- //#region src/miscellaneous/deriveEventActor.ts
1272
- function deriveEventActor(args) {
1273
- const tuple = Array.isArray(args) ? args : [args];
1274
- for (const arg of tuple) {
1275
- if (arg instanceof discord_js.Message) return {
1276
- guild: arg.guild,
1277
- member: arg.member,
1278
- user: arg.author,
1279
- channelId: arg.channelId
1280
- };
1281
- if (arg instanceof discord_js.GuildMember) return {
1282
- guild: arg.guild,
1283
- member: arg,
1284
- user: arg.user,
1285
- channelId: null
1286
- };
1287
- if (arg instanceof discord_js.User) return {
1288
- guild: null,
1289
- member: null,
1290
- user: arg,
1291
- channelId: null
1292
- };
1293
- }
1294
- return {
1295
- guild: null,
1296
- member: null,
1297
- user: null,
1298
- channelId: null
1299
- };
1300
- }
1301
-
1302
- //#endregion
1303
- //#region src/bot/gates/runGates.ts
1304
- function interactionGateContext(interaction, core) {
1305
- const rawMember = interaction.member;
1306
- const memberRoleIds = rawMember instanceof discord_js.GuildMember ? [...rawMember.roles.cache.keys()].filter((id) => id !== interaction.guildId) : rawMember?.roles ?? [];
1307
- return {
1308
- kind: "interaction",
1309
- interaction,
1310
- core,
1311
- user: interaction.user,
1312
- guild: interaction.guild,
1313
- member: rawMember instanceof discord_js.GuildMember ? rawMember : null,
1314
- userId: interaction.user.id,
1315
- guildId: interaction.guildId,
1316
- channelId: interaction.channelId,
1317
- memberRoleIds,
1318
- memberPermissions: interaction.memberPermissions?.bitfield ?? null,
1319
- appPermissions: interaction.appPermissions.bitfield,
1320
- memberGuildPermissions: rawMember instanceof discord_js.GuildMember ? rawMember.permissions.bitfield : null,
1321
- appGuildPermissions: interaction.guild?.members.me?.permissions.bitfield ?? null,
1322
- routeId: null
1323
- };
1324
- }
1325
- function eventGateContext(eventName, args, core) {
1326
- const actor = deriveEventActor(args);
1327
- return {
1328
- kind: "event",
1329
- core,
1330
- eventName,
1331
- payload: args,
1332
- user: actor.user,
1333
- guild: actor.guild,
1334
- member: actor.member,
1335
- userId: actor.user?.id ?? null,
1336
- guildId: actor.guild?.id ?? null,
1337
- channelId: actor.channelId,
1338
- memberRoleIds: actor.member ? [...actor.member.roles.cache.keys()].filter((id) => id !== actor.guild?.id) : [],
1339
- memberPermissions: actor.member?.permissions.bitfield ?? null,
1340
- appPermissions: null,
1341
- memberGuildPermissions: actor.member?.permissions.bitfield ?? null,
1342
- appGuildPermissions: actor.guild?.members.me?.permissions.bitfield ?? null,
1343
- routeId: null
1344
- };
1345
- }
1346
-
1347
- //#endregion
1348
- //#region src/miscellaneous/extractErrorResponse.ts
1349
- const logger$2 = new _seedcord_logger.Logger("ErrorsHandling", { channel: "errors" });
1350
- function extractErrorResponse(error, core, origin) {
1351
- const uuid = node_crypto.randomUUID();
1352
- const developerUsername = core.config.notifications?.developerUsername;
1353
- const ctx = developerUsername === void 0 ? { uuid } : {
1354
- uuid,
1355
- developerUsername
1356
- };
1357
- if (error instanceof _seedcord_core.Notice) {
1358
- if (error.report) reportFault(error, core, origin, uuid);
1359
- return {
1360
- uuid,
1361
- response: error.render(ctx)
1362
- };
1363
- }
1364
- reportRawFault(error, core, origin, uuid);
1365
- const override = core.config.errors?.defaultError;
1366
- return {
1367
- uuid,
1368
- response: override ? new override(uuid).render(ctx) : new _seedcord_core.Fault().render(ctx)
1369
- };
1370
- }
1371
- function withThrottle(core, origin, error, publish) {
1372
- const throttle = _seedcord_core_internal.FaultThrottle.for(core);
1373
- const key = faultKey(origin, error);
1374
- if (!throttle.shouldReport(key)) {
1375
- logger$2.debug(`throttled duplicate fault ${key}`);
1376
- return;
1377
- }
1378
- publish();
1379
- throttle.markReported(key);
1380
- }
1381
- function reportFault(denial, core, origin, uuid) {
1382
- logger$2.error(`${denial.name}: ${uuid}`, denial);
1383
- withThrottle(core, origin, denial, () => {
1384
- if (origin.interaction) core.bus[_seedcord_core_internal.PublishDefault]("handledException", {
1385
- denial,
1386
- uuid,
1387
- source: buildInteractionSource(origin.interaction)
1388
- });
1389
- else if (origin.event) core.bus[_seedcord_core_internal.PublishDefault]("handledException", {
1390
- denial,
1391
- uuid,
1392
- source: buildEventSource(origin.event, origin)
1393
- });
1394
- else core.bus[_seedcord_core_internal.PublishDefault]("unknownException", {
1395
- uuid,
1396
- error: denial,
1397
- ...scalarActors(origin),
1398
- metadata: metadataFor(origin)
1399
- });
1400
- });
1401
- }
1402
- function causeLine(error) {
1403
- const { cause } = error;
1404
- if (!Error.isError(cause)) return "";
1405
- const [first] = cause.message.split("\n");
1406
- return `\ncaused by ${cause.name}: ${first ?? cause.message}`;
1407
- }
1408
- function reportRawFault(error, core, origin, uuid) {
1409
- if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
1410
- else logger$2.error(`${uuid} | ${error.message}${causeLine(error)}`);
1411
- withThrottle(core, origin, error, () => {
1412
- core.bus[_seedcord_core_internal.PublishDefault]("unknownException", {
1413
- uuid,
1414
- error,
1415
- ...scalarActors(origin),
1416
- metadata: metadataFor(origin)
1417
- });
1418
- });
1419
- }
1420
- function scalarActors(origin) {
1421
- return {
1422
- guild: origin.guild ? {
1423
- id: origin.guild.id,
1424
- name: origin.guild.name
1425
- } : void 0,
1426
- user: origin.user ? {
1427
- id: origin.user.id,
1428
- username: origin.user.username
1429
- } : void 0
1430
- };
1431
- }
1432
- function metadataFor(origin) {
1433
- if (origin.event) return {
1434
- eventName: origin.event.name,
1435
- handler: origin.event.handler,
1436
- args: origin.event.args
1437
- };
1438
- return origin.metadata;
1439
- }
1440
- function buildEventSource(event, origin) {
1441
- return {
1442
- kind: "event",
1443
- eventName: event.name,
1444
- handler: event.handler,
1445
- userId: origin.user?.id ?? null,
1446
- guildId: origin.guild?.id ?? null,
1447
- channelId: event.channelId,
1448
- raw: event.args
1449
- };
1450
- }
1451
- function faultKey(origin, error) {
1452
- const name = error instanceof _seedcord_core.Notice ? error.name : error instanceof discord_js.DiscordAPIError ? String(error.code) : error.constructor.name;
1453
- if (origin.event) return `${origin.event.name}:${origin.event.handler}:${name}`;
1454
- if (origin.interaction) return `${interactionRoute(origin.interaction)}:${name}`;
1455
- if (origin.route) return `autocomplete:${origin.route}:${name}`;
1456
- return `autocomplete:${name}`;
1457
- }
1458
- function interactionRoute(interaction) {
1459
- if (interaction.isChatInputCommand()) return slashRouteOf(interaction);
1460
- if (interaction.isContextMenuCommand()) return interaction.commandName;
1461
- if (interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit()) return (0, _seedcord_core_internal.prefixOf)(interaction.customId) || interaction.customId;
1462
- return "interaction";
1463
- }
1464
- function buildInteractionSource(interaction) {
1465
- const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
1466
- const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
1467
- return {
1468
- kind: "interaction",
1469
- interactionKind: interactionKind(interaction),
1470
- command,
1471
- customId,
1472
- userId: interaction.user.id,
1473
- guildId: interaction.guildId,
1474
- channelId: interaction.channelId,
1475
- interactionId: interaction.id,
1476
- raw: interaction
1477
- };
1478
- }
1479
- function interactionKind(interaction) {
1480
- if (interaction.isChatInputCommand()) return "slash";
1481
- if (interaction.isContextMenuCommand()) return "context-menu";
1482
- if (interaction.isButton()) return "button";
1483
- if (interaction.isAnySelectMenu()) return "select";
1484
- return "modal";
1485
- }
1486
-
1487
- //#endregion
1488
- //#region src/bot/handleEventFault.ts
1489
- const logger$1 = new _seedcord_logger.Logger("Faults", { channel: "errors" });
1490
- function handleEventFault(caught, eventName, handlerName, args, core) {
1491
- if (caught instanceof _seedcord_core.Silence) {
1492
- if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger$1.debug(`Silence: ${caught.reason}`);
1493
- return;
1494
- }
1495
- const error = (0, _seedcord_core_internal.asError)(caught);
1496
- const ignore = new Set(core.config.errors?.ignoreEventApiCodes ?? []);
1497
- if (error instanceof discord_js.DiscordAPIError && ignore.has(error.code)) {
1498
- logger$1.debug(`swallowed api code ${error.code}`);
1499
- return;
1500
- }
1501
- if (error instanceof _seedcord_core.Notice && !error.report) return;
1502
- const actor = deriveEventActor(args);
1503
- extractErrorResponse(error, core, {
1504
- event: {
1505
- name: eventName,
1506
- handler: handlerName,
1507
- args,
1508
- channelId: actor.channelId
1509
- },
1510
- guild: actor.guild,
1511
- user: actor.user
1512
- });
1513
- }
1514
-
1515
- //#endregion
1516
- //#region src/bot/controllers/EventDispatcher.ts
1517
- /**
1518
- * Interactions route through InteractionDispatcher.
1519
- *
1520
- * @internal
1521
- */
1522
- var EventDispatcher = class {
1523
- core;
1524
- logger = new _seedcord_logger.Logger("Events", { channel: "events" });
1525
- isInitialized = false;
1526
- eventMap = /* @__PURE__ */ new Map();
1527
- middlewares = [];
1528
- executedOnceHandlers = /* @__PURE__ */ new Set();
1529
- attachedEvents = /* @__PURE__ */ new Set();
1530
- inFlight = /* @__PURE__ */ new Set();
1531
- draining = false;
1532
- loading = false;
1533
- loadedHandlers = [];
1534
- loadedMiddlewares = [];
1535
- hmrHandler;
1536
- constructor(core) {
1537
- this.core = core;
1538
- const eventsDir = this.core.config.bot.events.path;
1539
- if (!eventsDir) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreControllerPathMissing, ["EventDispatcher", "events"]);
1540
- if (!envapt.Envapter.isDevelopment && !envapt.Envapter.isTest) return;
1541
- this.hmrHandler = new _seedcord_core_hmr.HmrModuleHandler({
1542
- handlersDir: eventsDir,
1543
- ...(0, _seedcord_utils.hasKeys)(this.core.config.bot.events, ["middlewares"]) && { middlewaresDir: this.core.config.bot.events.middlewares },
1544
- isHandler: this.isEventHandlerClass.bind(this),
1545
- isMiddleware: this.isMiddlewareClass.bind(this),
1546
- registerHandler: this.registerHandler.bind(this),
1547
- registerMiddleware: this.registerMiddleware.bind(this),
1548
- unregisterHandler: this.unregisterHandler.bind(this),
1549
- unregisterMiddleware: this.unregisterMiddleware.bind(this),
1550
- getArtifacts: this.getArtifacts.bind(this),
1551
- logger: this.logger
1552
- });
1553
- }
1554
- getArtifacts(ctor) {
1555
- const events = [];
1556
- for (const [event, handlers] of this.eventMap.entries()) if (handlers.some((h) => h.ctor === ctor)) events.push(event);
1557
- return events;
1558
- }
1559
- async init() {
1560
- if (this.isInitialized) return;
1561
- this.isInitialized = true;
1562
- const handlersDir = this.core.config.bot.events.path;
1563
- if (!handlersDir) return;
1564
- const middlewareDir = (0, _seedcord_utils.hasKeys)(this.core.config.bot.events, ["middlewares"]) ? this.core.config.bot.events.middlewares : void 0;
1565
- this.loading = true;
1566
- this.loadedHandlers.length = 0;
1567
- this.loadedMiddlewares.length = 0;
1568
- try {
1569
- if (middlewareDir) await this.loadMiddlewares(middlewareDir);
1570
- await this.loadHandlers(handlersDir);
1571
- } finally {
1572
- this.loading = false;
1573
- }
1574
- this.attachToClient();
1575
- this.reportLoad();
1576
- }
1577
- reportLoad() {
1578
- const { utils } = this.logger;
1579
- utils.summary("Loaded", {
1580
- "event handlers": this.loadedHandlers.length,
1581
- middlewares: this.loadedMiddlewares.length
1582
- }, "debug");
1583
- if (this.loadedMiddlewares.length > 0) utils.block("Loaded event middlewares", utils.entries(this.loadedMiddlewares), "debug");
1584
- const perEvent = {};
1585
- for (const [event, handlers] of this.eventMap) perEvent[event] = handlers.length;
1586
- utils.block("Loaded event handlers", [...utils.entries(this.loadedHandlers), ...utils.counts(perEvent)], "debug");
1587
- }
1588
- async loadHandlers(dir) {
1589
- await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
1590
- for (const val of Object.values(imported)) {
1591
- if (!this.isEventHandlerClass(val)) continue;
1592
- this.registerHandler(val, relativePath);
1593
- this.hmrHandler?.trackHandler(fullPath, val);
1594
- }
1595
- });
1596
- }
1597
- async loadMiddlewares(dir) {
1598
- await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
1599
- for (const val of Object.values(imported)) {
1600
- if (!this.isMiddlewareClass(val)) continue;
1601
- this.registerMiddleware(val, relativePath);
1602
- this.hmrHandler?.trackMiddleware(fullPath, val);
1603
- }
1604
- });
1605
- }
1606
- /** @internal */
1607
- async onHmr(event) {
1608
- await this.hmrHandler?.handle(event);
1609
- }
1610
- unregisterHandler(handlerClass, artifacts) {
1611
- const events = artifacts ?? [...this.eventMap.keys()];
1612
- for (const event of events) {
1613
- const handlers = this.eventMap.get(event);
1614
- if (!handlers) continue;
1615
- const index = handlers.findIndex((h) => h.ctor === handlerClass);
1616
- if (index !== -1) {
1617
- handlers.splice(index, 1);
1618
- if (handlers.length === 0) this.eventMap.delete(event);
1619
- }
1620
- }
1621
- }
1622
- unregisterMiddleware(middlewareCtor) {
1623
- const index = this.middlewares.findIndex((entry) => entry.ctor === middlewareCtor);
1624
- if (index !== -1) this.middlewares.splice(index, 1);
1625
- }
1626
- registerMiddleware(middlewareCtor, relativePath) {
1627
- const metadata = Reflect.getMetadata(_seedcord_core_internal.MiddlewareMetadataKey, middlewareCtor);
1628
- if (metadata?.type !== "event") return;
1629
- if (this.middlewares.some((entry) => entry.ctor === middlewareCtor)) return;
1630
- this.middlewares.push({
1631
- ctor: middlewareCtor,
1632
- priority: metadata.priority,
1633
- ...metadata.events && { events: metadata.events }
1634
- });
1635
- this.middlewares.sort((a, b) => a.priority - b.priority);
1636
- if (this.loading) this.loadedMiddlewares.push({
1637
- name: `${middlewareCtor.name} (${metadata.priority})`,
1638
- from: (0, _seedcord_utils.formatFilePath)(relativePath)
1639
- });
1640
- }
1641
- async runMiddlewares(eventName, args) {
1642
- for (const { ctor, events } of this.middlewares) {
1643
- if (events && !events.includes(eventName)) continue;
1644
- try {
1645
- await new ctor(args, this.core, eventName).execute();
1646
- } catch (caught) {
1647
- handleEventFault(caught, String(eventName), ctor.name, args, this.core);
1648
- return false;
1649
- }
1650
- }
1651
- return true;
1652
- }
1653
- isEventHandlerClass(obj) {
1654
- if (typeof obj !== "function") return false;
1655
- return obj.prototype instanceof EventHandler && Reflect.hasMetadata(_seedcord_core_internal.EventMetadataKey, obj);
1656
- }
1657
- isMiddlewareClass(obj) {
1658
- if (typeof obj !== "function") return false;
1659
- return obj.prototype instanceof EventMiddleware && Reflect.hasMetadata(_seedcord_core_internal.MiddlewareMetadataKey, obj);
1660
- }
1661
- registerHandler(handlerClass, relativePath) {
1662
- const raw = Reflect.getMetadata(_seedcord_core_internal.EventMetadataKey, handlerClass);
1663
- const register = (key, frequency) => {
1664
- let handlers = this.eventMap.get(key);
1665
- if (!handlers) {
1666
- handlers = [];
1667
- this.eventMap.set(key, handlers);
1668
- }
1669
- handlers.push({
1670
- ctor: handlerClass,
1671
- frequency
1672
- });
1673
- if (this.isInitialized && !this.attachedEvents.has(key)) this.attachListener(key);
1674
- };
1675
- if (Array.isArray(raw)) for (const entry of raw) register(entry.event, entry.frequency);
1676
- else {
1677
- const names = (0, _seedcord_core_internal.areRoutes)(raw) ? raw : typeof raw === "string" ? [raw] : [];
1678
- for (const name of names) register(name, "on");
1679
- }
1680
- const from = (0, _seedcord_utils.formatFilePath)(relativePath);
1681
- if (this.loading) this.loadedHandlers.push({
1682
- name: handlerClass.name,
1683
- from
1684
- });
1685
- }
1686
- attachToClient() {
1687
- for (const [eventName] of this.eventMap) this.attachListener(eventName);
1688
- }
1689
- attachListener(eventName) {
1690
- if (this.attachedEvents.has(eventName)) return;
1691
- this.attachedEvents.add(eventName);
1692
- const handlerEntries = this.eventMap.get(eventName);
1693
- this.logger.debug(`Attaching ${_seedcord_logger.paint.mint.bold(eventName)} to the client with ${_seedcord_logger.paint.mute(handlerEntries?.length ?? 0)} handler(s)`);
1694
- this.core.bot.client.on(eventName, (...args) => {
1695
- if (this.draining) return;
1696
- this.core.bus[_seedcord_core_internal.PublishDefault]("anyEvent", {
1697
- name: eventName,
1698
- args
1699
- });
1700
- const run = this.processEvent(eventName, args).catch((caught) => {
1701
- const error = (0, _seedcord_core_internal.asError)(caught);
1702
- this.logger.error(`[${_seedcord_logger.paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
1703
- this.core.bus[_seedcord_core_internal.PublishDefault]("unhandledEventError", { error });
1704
- });
1705
- this.inFlight.add(run);
1706
- run.finally(() => this.inFlight.delete(run));
1707
- });
1708
- }
1709
- stopAccepting() {
1710
- this.draining = true;
1711
- }
1712
- drain(timeoutMs) {
1713
- return (0, _seedcord_core_node_internal.settleWithin)(Promise.allSettled(this.inFlight), timeoutMs);
1714
- }
1715
- async processEvent(eventName, args) {
1716
- const handlerEntries = this.eventMap.get(eventName);
1717
- if (!handlerEntries || handlerEntries.length === 0) return;
1718
- const handlersToExecute = handlerEntries.filter((entry) => entry.frequency !== "once" || !this.executedOnceHandlers.has(entry.ctor));
1719
- if (handlersToExecute.length === 0) return;
1720
- if (!await this.runMiddlewares(eventName, args)) return;
1721
- for (const entry of handlersToExecute) {
1722
- if (entry.frequency === "once") {
1723
- if (this.executedOnceHandlers.has(entry.ctor)) continue;
1724
- this.executedOnceHandlers.add(entry.ctor);
1725
- }
1726
- await this.processHandler(eventName, entry.ctor, args);
1727
- }
1728
- }
1729
- async processHandler(eventName, ctor, args) {
1730
- try {
1731
- this.logger.debug(`Processing ${_seedcord_logger.paint.mint.bold(eventName)} with ${_seedcord_logger.paint.mute(ctor.name)}`);
1732
- const handler = new ctor(args, this.core, eventName);
1733
- const eventCtx = eventGateContext(eventName, args, this.core);
1734
- await (0, _seedcord_core_internal.runHandlerGates)(ctor, eventCtx);
1735
- await handler.execute();
1736
- } catch (caught) {
1737
- handleEventFault(caught, String(eventName), ctor.name, args, this.core);
1738
- }
1739
- }
1740
- };
1741
-
1742
- //#endregion
1743
- //#region src/bot/defaults/UnhandledRepliable.ts
1744
- var UnhandledRepliable = class extends InteractionHandler {
1745
- async execute() {
1746
- await this.reply("Feature not implemented yet.");
1747
- }
1748
- };
1749
-
1750
- //#endregion
1751
- //#region src/bot/defaults/UnhandledAutocomplete.ts
1752
- var UnhandledAutocomplete = class extends AutocompleteHandler {
1753
- async execute() {
1754
- await this.respond([]);
1755
- }
1756
- };
1757
-
1758
- //#endregion
1759
- //#region src/bot/harmlessApiCodes.ts
1760
- /**
1761
- * Discord API error codes that mean a dead end on an interaction, a dead token, a double ack, or a gone
1762
- * message. Nothing to show the user and no bug to report, so the boundary's fault-card send drops them.
1763
- *
1764
- * @internal
1765
- */
1766
- const HARMLESS_API_CODES = /* @__PURE__ */ new Set([
1767
- discord_js.RESTJSONErrorCodes.UnknownInteraction,
1768
- discord_js.RESTJSONErrorCodes.InteractionHasAlreadyBeenAcknowledged,
1769
- discord_js.RESTJSONErrorCodes.UnknownMessage
1770
- ]);
1771
-
1772
- //#endregion
1773
- //#region src/bot/handleInteractionFault.ts
1774
- const logger = new _seedcord_logger.Logger("Faults", { channel: "errors" });
1775
- async function handleInteractionFault(caught, interaction, core, routeId, sender) {
1776
- if (caught instanceof _seedcord_core.Silence) {
1777
- if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger.debug(`Silence: ${caught.reason}`);
1778
- return;
1779
- }
1780
- const error = (0, _seedcord_core_internal.asError)(caught);
1781
- const ignore = new Set(core.config.errors?.ignoreApiCodes ?? []);
1782
- if (error instanceof discord_js.DiscordAPIError && ignore.has(error.code)) {
1783
- logger.debug(`swallowed api code ${error.code}`);
1784
- return;
1785
- }
1786
- if (interaction.isAutocomplete()) {
1787
- extractErrorResponse(error, core, {
1788
- guild: interaction.guild,
1789
- user: interaction.user,
1790
- metadata: interaction,
1791
- route: slashRouteOf(interaction)
1792
- });
1793
- await sendEmptyChoices(interaction, core, routeId);
1794
- return;
1795
- }
1796
- const { response } = extractErrorResponse(error, core, {
1797
- interaction,
1798
- guild: interaction.guild,
1799
- user: interaction.user,
1800
- metadata: interaction
1801
- });
1802
- await sendGuarded(sender ?? new ReplySender(interaction, routeId, core.bus), response, error instanceof _seedcord_core.Notice ? error.ephemeral : true);
1803
- }
1804
- async function sendEmptyChoices(interaction, core, routeId) {
1805
- const telemetry = {
1806
- bus: core.bus,
1807
- interactionId: interaction.id
1808
- };
1809
- try {
1810
- await (0, _seedcord_core_internal.reportedWrite)(telemetry, routeId, "respond", () => interaction.respond([]));
1811
- } catch (error) {
1812
- logger.debug(`autocomplete empty-choices send failed: ${String(error)}`);
1813
- }
1814
- }
1815
- async function sendGuarded(sender, response, ephemeral) {
1816
- try {
1817
- await sender.send(response, { ephemeral });
1818
- } catch (error) {
1819
- if (error instanceof discord_js.DiscordAPIError && HARMLESS_API_CODES.has(error.code)) {
1820
- logger.debug(`reply send hit harmless code ${error.code}`);
1821
- return;
1822
- }
1823
- logger.error("reply send failed", error);
1824
- }
1825
- }
1826
-
1827
- //#endregion
1828
- //#region src/bot/controllers/InteractionDispatcher.ts
1829
- var InteractionDispatcher = class {
1830
- core;
1831
- logger = new _seedcord_logger.Logger("Interactions", { channel: "interactions" });
1832
- isInitialized = false;
1833
- slashMap = /* @__PURE__ */ new Map();
1834
- buttonMap = /* @__PURE__ */ new Map();
1835
- modalMap = /* @__PURE__ */ new Map();
1836
- stringSelectMap = /* @__PURE__ */ new Map();
1837
- userSelectMap = /* @__PURE__ */ new Map();
1838
- roleSelectMap = /* @__PURE__ */ new Map();
1839
- channelSelectMap = /* @__PURE__ */ new Map();
1840
- mentionableSelectMap = /* @__PURE__ */ new Map();
1841
- messageContextMenuMap = /* @__PURE__ */ new Map();
1842
- userContextMenuMap = /* @__PURE__ */ new Map();
1843
- autocompleteMap = /* @__PURE__ */ new Map();
1844
- handlerFiles = /* @__PURE__ */ new Map();
1845
- keysToIgnore = /* @__PURE__ */ new Set();
1846
- middlewares = [];
1847
- inFlight = /* @__PURE__ */ new Set();
1848
- draining = false;
1849
- loading = false;
1850
- loadedHandlers = [];
1851
- loadedMiddlewares = [];
1852
- hmrHandler;
1853
- routeTypes = [
1854
- [_seedcord_core_internal.InteractionRoutes.Slash, this.slashMap],
1855
- [_seedcord_core_internal.InteractionRoutes.Button, this.buttonMap],
1856
- [_seedcord_core_internal.InteractionRoutes.Modal, this.modalMap],
1857
- [_seedcord_core_internal.InteractionRoutes.StringMenu, this.stringSelectMap],
1858
- [_seedcord_core_internal.InteractionRoutes.UserMenu, this.userSelectMap],
1859
- [_seedcord_core_internal.InteractionRoutes.RoleMenu, this.roleSelectMap],
1860
- [_seedcord_core_internal.InteractionRoutes.ChannelMenu, this.channelSelectMap],
1861
- [_seedcord_core_internal.InteractionRoutes.MentionableMenu, this.mentionableSelectMap],
1862
- [_seedcord_core_internal.InteractionRoutes.MessageContextMenu, this.messageContextMenuMap],
1863
- [_seedcord_core_internal.InteractionRoutes.UserContextMenu, this.userContextMenuMap],
1864
- [_seedcord_core_internal.InteractionRoutes.Autocomplete, this.autocompleteMap]
1865
- ];
1866
- constructor(core) {
1867
- this.core = core;
1868
- const ignoredKeysFromConfig = (0, _seedcord_utils.hasKeys)(this.core.config.bot.interactions, ["ignoreCustomIds"]) ? this.core.config.bot.interactions.ignoreCustomIds : void 0;
1869
- if (ignoredKeysFromConfig) for (const ignoredKey of ignoredKeysFromConfig) this.keysToIgnore.add(ignoredKey);
1870
- this.keysToIgnore.add(CONFIRM_DEF);
1871
- const interactionsDir = this.core.config.bot.interactions.path;
1872
- if (!interactionsDir) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreControllerPathMissing, ["InteractionDispatcher", "interactions"]);
1873
- if (!envapt.Envapter.isDevelopment && !envapt.Envapter.isTest) return;
1874
- this.hmrHandler = new _seedcord_core_hmr.HmrModuleHandler({
1875
- handlersDir: interactionsDir,
1876
- ...(0, _seedcord_utils.hasKeys)(this.core.config.bot.interactions, ["middlewares"]) && this.core.config.bot.interactions.middlewares && { middlewaresDir: this.core.config.bot.interactions.middlewares },
1877
- isHandler: this.isHandlerClass.bind(this),
1878
- isMiddleware: this.isMiddlewareClass.bind(this),
1879
- registerHandler: this.registerHandler.bind(this),
1880
- registerMiddleware: this.registerMiddleware.bind(this),
1881
- unregisterHandler: this.unregisterHandler.bind(this),
1882
- unregisterMiddleware: this.unregisterMiddleware.bind(this),
1883
- getArtifacts: this.getArtifacts.bind(this),
1884
- logger: this.logger
1885
- });
1886
- }
1887
- warnUnhandledRoutes(commandLeaves) {
1888
- for (const route of commandLeaves) if (!this.slashMap.has(route)) this.logger.warn(`Slash route ${_seedcord_logger.paint.sky.bold(route)} has no registered ${chalk.default.bold("@SlashRoute")} handler.`);
1889
- }
1890
- warnUnhandledContextMenuRoutes(leaves) {
1891
- for (const name of leaves.user) if (!this.userContextMenuMap.has(name)) this.logger.warn(`User context menu ${_seedcord_logger.paint.sky.bold(name)} has no registered ${chalk.default.bold("@ContextMenuRoute")} handler.`);
1892
- for (const name of leaves.message) if (!this.messageContextMenuMap.has(name)) this.logger.warn(`Message context menu ${_seedcord_logger.paint.sky.bold(name)} has no registered ${chalk.default.bold("@ContextMenuRoute")} handler.`);
1893
- }
1894
- getArtifacts(handlerClass) {
1895
- const artifacts = [];
1896
- for (const [routeType] of this.routeTypes) {
1897
- const meta = Reflect.getMetadata(_seedcord_core_internal.InteractionRouteKeys[routeType], handlerClass);
1898
- if ((0, _seedcord_core_internal.areRoutes)(meta)) artifacts.push({
1899
- routeType,
1900
- routes: meta
1901
- });
1902
- }
1903
- return artifacts;
1904
- }
1905
- async init() {
1906
- if (this.isInitialized) return;
1907
- this.isInitialized = true;
1908
- const handlersDir = this.core.config.bot.interactions.path;
1909
- if (!handlersDir) return;
1910
- const middlewareDir = (0, _seedcord_utils.hasKeys)(this.core.config.bot.interactions, ["middlewares"]) ? this.core.config.bot.interactions.middlewares : void 0;
1911
- this.loading = true;
1912
- this.loadedHandlers.length = 0;
1913
- this.loadedMiddlewares.length = 0;
1914
- try {
1915
- if (middlewareDir) await this.loadMiddlewares(middlewareDir);
1916
- await this.loadHandlers(handlersDir);
1917
- } finally {
1918
- this.loading = false;
1919
- }
1920
- this.attachToClient();
1921
- this.reportLoad();
1922
- }
1923
- reportLoad() {
1924
- const { utils } = this.logger;
1925
- utils.summary("Loaded", {
1926
- "interaction handlers": this.loadedHandlers.length,
1927
- middlewares: this.loadedMiddlewares.length
1928
- }, "debug");
1929
- if (this.loadedMiddlewares.length > 0) utils.block("Loaded middlewares", utils.entries(this.loadedMiddlewares), "debug");
1930
- utils.block("Loaded interaction handlers", [...utils.entries(this.loadedHandlers), ...utils.counts({
1931
- slash: this.slashMap.size,
1932
- buttons: this.buttonMap.size,
1933
- modals: this.modalMap.size,
1934
- "string selects": this.stringSelectMap.size,
1935
- "user selects": this.userSelectMap.size,
1936
- "role selects": this.roleSelectMap.size,
1937
- "channel selects": this.channelSelectMap.size,
1938
- "mentionable selects": this.mentionableSelectMap.size,
1939
- "message menus": this.messageContextMenuMap.size,
1940
- "user menus": this.userContextMenuMap.size,
1941
- autocomplete: this.autocompleteMap.size
1942
- })], "debug");
1943
- }
1944
- async loadHandlers(dir) {
1945
- await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
1946
- for (const val of Object.values(imported)) {
1947
- if (!this.isHandlerClass(val)) continue;
1948
- this.registerHandler(val, relativePath);
1949
- this.hmrHandler?.trackHandler(fullPath, val);
1950
- }
1951
- });
1952
- }
1953
- async loadMiddlewares(dir) {
1954
- await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
1955
- for (const val of Object.values(imported)) {
1956
- if (!this.isMiddlewareClass(val)) continue;
1957
- this.registerMiddleware(val, relativePath);
1958
- this.hmrHandler?.trackMiddleware(fullPath, val);
1959
- }
1960
- });
1961
- }
1962
- registerMiddleware(middlewareCtor, relativePath) {
1963
- const metadata = Reflect.getMetadata(_seedcord_core_internal.MiddlewareMetadataKey, middlewareCtor);
1964
- if (metadata?.type !== "interaction") return;
1965
- if (this.middlewares.some((entry) => entry.ctor === middlewareCtor)) return;
1966
- if (this.middlewares.some((entry) => entry.ctor.name === middlewareCtor.name)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.InteractionDuplicateMiddleware, [middlewareCtor.name]);
1967
- this.middlewares.push({
1968
- ctor: middlewareCtor,
1969
- priority: metadata.priority
1970
- });
1971
- this.middlewares.sort((a, b) => a.priority - b.priority);
1972
- if (this.loading) this.loadedMiddlewares.push({
1973
- name: `${middlewareCtor.name} (${metadata.priority})`,
1974
- from: (0, _seedcord_utils.formatFilePath)(relativePath)
1975
- });
1976
- }
1977
- isHandlerClass(obj) {
1978
- if (typeof obj !== "function") return false;
1979
- return obj.prototype instanceof InteractionHandler && Reflect.hasMetadata(_seedcord_core_internal.InteractionMetadataKey, obj) || obj.prototype instanceof AutocompleteHandler && Reflect.hasMetadata(_seedcord_core_internal.InteractionMetadataKey, obj);
1980
- }
1981
- isMiddlewareClass(obj) {
1982
- if (typeof obj !== "function") return false;
1983
- return obj.prototype instanceof InteractionMiddleware && Reflect.hasMetadata(_seedcord_core_internal.MiddlewareMetadataKey, obj);
1984
- }
1985
- registerHandler(handlerClass, relativePath) {
1986
- const from = (0, _seedcord_utils.formatFilePath)(relativePath);
1987
- const writes = [];
1988
- for (const [routeType, map] of this.routeTypes) {
1989
- const meta = Reflect.getMetadata(_seedcord_core_internal.InteractionRouteKeys[routeType], handlerClass);
1990
- if (!(0, _seedcord_core_internal.areRoutes)(meta)) continue;
1991
- for (const route of meta) {
1992
- const existing = map.get(route);
1993
- if (existing && existing !== handlerClass) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.InteractionDuplicateRoute, [
1994
- `${routeType}:${route}`,
1995
- `${existing.name} (${this.handlerFiles.get(existing) ?? "unknown file"})`,
1996
- `${handlerClass.name} (${from})`
1997
- ]);
1998
- writes.push([map, route]);
1999
- }
2000
- }
2001
- if (writes.length === 0) return;
2002
- for (const [map, route] of writes) map.set(route, handlerClass);
2003
- this.handlerFiles.set(handlerClass, from);
2004
- if (this.loading) this.loadedHandlers.push({
2005
- name: handlerClass.name,
2006
- from
2007
- });
2008
- }
2009
- async onHmr(event) {
2010
- await this.hmrHandler?.handle(event);
2011
- }
2012
- unregisterHandler(handlerClass, artifacts) {
2013
- this.handlerFiles.delete(handlerClass);
2014
- if (artifacts) {
2015
- for (const { routeType, routes } of artifacts) {
2016
- const map = this.routeTypes.find(([type]) => type === routeType)?.[1];
2017
- if (map) routes.forEach((route) => map.delete(route));
2018
- }
2019
- return;
2020
- }
2021
- for (const [routeType, map] of this.routeTypes) {
2022
- const meta = Reflect.getMetadata(_seedcord_core_internal.InteractionRouteKeys[routeType], handlerClass);
2023
- if (!(0, _seedcord_core_internal.areRoutes)(meta)) continue;
2024
- meta.forEach((route) => map.delete(route));
2025
- }
2026
- }
2027
- unregisterMiddleware(middlewareCtor) {
2028
- const index = this.middlewares.findIndex((entry) => entry.ctor === middlewareCtor);
2029
- if (index !== -1) this.middlewares.splice(index, 1);
2030
- }
2031
- attachToClient() {
2032
- this.core.bot.client.on(discord_js.Events.InteractionCreate, (interaction) => {
2033
- if (this.draining) return;
2034
- this.core.bus[_seedcord_core_internal.PublishDefault]("anyInteraction", { interaction });
2035
- const run = this.handleInteraction(interaction).catch((caught) => {
2036
- const error = (0, _seedcord_core_internal.asError)(caught);
2037
- this.logger.error(`[${_seedcord_logger.paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
2038
- this.core.bus[_seedcord_core_internal.PublishDefault]("unhandledInteractionError", { error });
2039
- });
2040
- this.inFlight.add(run);
2041
- run.finally(() => this.inFlight.delete(run));
2042
- });
2043
- }
2044
- stopAccepting() {
2045
- this.draining = true;
2046
- }
2047
- drain(timeoutMs) {
2048
- return (0, _seedcord_core_node_internal.settleWithin)(Promise.allSettled(this.inFlight), timeoutMs);
2049
- }
2050
- async handleCustomIdInteraction(interaction, kind, getMap) {
2051
- if ([...this.keysToIgnore].some((matcher) => matcher.owns(interaction.customId))) return;
2052
- const prefix = (0, _seedcord_core_internal.prefixOf)(interaction.customId);
2053
- if (!prefix) this.logger.warn(`${_seedcord_logger.paint.sky.bold(kind)} has invalid customId: ${_seedcord_logger.paint.mute(interaction.customId)}`);
2054
- await this.processInteraction(interaction, kind, () => prefix, (key) => getMap().get(key));
2055
- }
2056
- async processInteraction(interaction, kind, extractKey, getHandler, fallback = UnhandledRepliable) {
2057
- const key = extractKey(interaction);
2058
- const startedAt = performance.now();
2059
- const queuedMs = (0, _seedcord_core_internal.queuedMsFor)(interaction.id);
2060
- const matched = getHandler(key);
2061
- let routeId = `${kind}:${key || "unrouted"}`;
2062
- let reported = false;
2063
- const report = (outcome) => {
2064
- if (reported) return;
2065
- reported = true;
2066
- (0, _seedcord_core_internal.reportDispatch)(this.core.bus, {
2067
- routeId,
2068
- interactionId: interaction.id,
2069
- kind,
2070
- outcome,
2071
- startedAt,
2072
- fallback: !matched,
2073
- queuedMs
2074
- });
2075
- };
2076
- let sender;
2077
- try {
2078
- if (!interaction.isAutocomplete()) await this.runMiddlewares(interaction);
2079
- const HandlerCtor = matched ?? fallback;
2080
- const dispatch = new _seedcord_core.DispatchContext((0, _seedcord_core_internal.routeIdOf)(HandlerCtor) ?? routeId);
2081
- routeId = dispatch.routeId ?? routeId;
2082
- const handler = this.buildHandler(HandlerCtor, interaction, dispatch, key, !matched);
2083
- if (handler instanceof RepliableHandler) sender = handler.getSender();
2084
- const refusal = interaction.isAutocomplete() ? null : await this.gateRefusal(HandlerCtor, interaction, dispatch.routeId);
2085
- if (refusal) {
2086
- await this.answer(refusal.caught, interaction, routeId, sender, report);
2087
- return;
2088
- }
2089
- await handler.execute();
2090
- report("handled");
2091
- } catch (caught) {
2092
- await this.answer(caught, interaction, routeId, sender, report);
2093
- }
2094
- }
2095
- async answer(caught, interaction, routeId, sender, report) {
2096
- try {
2097
- await handleInteractionFault(caught, interaction, this.core, routeId, sender);
2098
- } finally {
2099
- report((0, _seedcord_core_internal.outcomeFor)(caught));
2100
- }
2101
- }
2102
- buildHandler(HandlerCtor, interaction, dispatch, key, isFallback) {
2103
- if (isFallback) this.logger.warn(`No handler found for key ${_seedcord_logger.paint.sky.bold(key)}. Falling back to ${HandlerCtor.name}.`);
2104
- this.logger.debug(`Processing ${_seedcord_logger.paint.mint.bold(key)} with ${_seedcord_logger.paint.mute(HandlerCtor.name)}`);
2105
- return new HandlerCtor(interaction, this.core, dispatch);
2106
- }
2107
- async runMiddlewares(interaction) {
2108
- for (const { ctor } of this.middlewares) await new ctor(interaction, this.core).execute();
2109
- }
2110
- async gateRefusal(HandlerCtor, interaction, routeId) {
2111
- const monitor = (0, _seedcord_core_internal.slowGateMonitor)();
2112
- try {
2113
- await (0, _seedcord_core_internal.runHandlerGates)(HandlerCtor, interactionGateContext(interaction, this.core), routeId ?? void 0, monitor?.observe);
2114
- return null;
2115
- } catch (caught) {
2116
- return { caught };
2117
- } finally {
2118
- monitor?.report(routeId);
2119
- }
2120
- }
2121
- async handleInteraction(interaction) {
2122
- switch (true) {
2123
- case interaction.isChatInputCommand():
2124
- await this.handleSlashCommand(interaction);
2125
- break;
2126
- case interaction.isButton():
2127
- await this.handleButton(interaction);
2128
- break;
2129
- case interaction.isModalSubmit():
2130
- await this.handleModal(interaction);
2131
- break;
2132
- case interaction.isStringSelectMenu():
2133
- await this.handleStringSelectMenu(interaction);
2134
- break;
2135
- case interaction.isUserSelectMenu():
2136
- await this.handleUserSelectMenu(interaction);
2137
- break;
2138
- case interaction.isRoleSelectMenu():
2139
- await this.handleRoleSelectMenu(interaction);
2140
- break;
2141
- case interaction.isChannelSelectMenu():
2142
- await this.handleChannelSelectMenu(interaction);
2143
- break;
2144
- case interaction.isMentionableSelectMenu():
2145
- await this.handleMentionableSelectMenu(interaction);
2146
- break;
2147
- case interaction.isMessageContextMenuCommand():
2148
- await this.handleMessageContextMenu(interaction);
2149
- break;
2150
- case interaction.isUserContextMenuCommand():
2151
- await this.handleUserContextMenu(interaction);
2152
- break;
2153
- case interaction.isAutocomplete():
2154
- await this.handleAutocomplete(interaction);
2155
- break;
2156
- default: this.logger.warn(`Unhandled interaction type: ${interaction.type}`);
2157
- }
2158
- }
2159
- async handleSlashCommand(interaction) {
2160
- const route = slashRouteOf(interaction);
2161
- await this.processInteraction(interaction, _seedcord_core_internal.InteractionRoutes.Slash, () => route, (key) => this.slashMap.get(key));
2162
- }
2163
- async handleButton(interaction) {
2164
- await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.Button, () => this.buttonMap);
2165
- }
2166
- async handleModal(interaction) {
2167
- await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.Modal, () => this.modalMap);
2168
- }
2169
- async handleStringSelectMenu(interaction) {
2170
- await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.StringMenu, () => this.stringSelectMap);
2171
- }
2172
- async handleUserSelectMenu(interaction) {
2173
- await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.UserMenu, () => this.userSelectMap);
2174
- }
2175
- async handleRoleSelectMenu(interaction) {
2176
- await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.RoleMenu, () => this.roleSelectMap);
2177
- }
2178
- async handleChannelSelectMenu(interaction) {
2179
- await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.ChannelMenu, () => this.channelSelectMap);
2180
- }
2181
- async handleMentionableSelectMenu(interaction) {
2182
- await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.MentionableMenu, () => this.mentionableSelectMap);
2183
- }
2184
- async handleMessageContextMenu(interaction) {
2185
- await this.processInteraction(interaction, _seedcord_core_internal.InteractionRoutes.MessageContextMenu, () => interaction.commandName, (key) => this.messageContextMenuMap.get(key));
2186
- }
2187
- async handleUserContextMenu(interaction) {
2188
- await this.processInteraction(interaction, _seedcord_core_internal.InteractionRoutes.UserContextMenu, () => interaction.commandName, (key) => this.userContextMenuMap.get(key));
2189
- }
2190
- async handleAutocomplete(interaction) {
2191
- const route = slashRouteOf(interaction);
2192
- await this.processInteraction(interaction, _seedcord_core_internal.InteractionRoutes.Autocomplete, () => route, (key) => this.autocompleteMap.get(key), UnhandledAutocomplete);
2193
- }
2194
- };
2195
-
2196
- //#endregion
2197
- //#region \0@oxc-project+runtime@0.142.0/helpers/esm/decorate.js
2198
- function __decorate(decorators, target, key, desc) {
2199
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2200
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2201
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2202
- return c > 3 && r && Object.defineProperty(target, key, r), r;
2203
- }
2204
-
2205
- //#endregion
2206
- //#region src/bot/Bot.ts
2207
- const DISPATCH_DRAIN_TIMEOUT_MS = 5e3;
2208
- const DRAIN_TASK_TIMEOUT_MS = 6e3;
2209
- const UNBIND_TIMEOUT_MS = 2e3;
2210
- const LOGOUT_TIMEOUT_MS = 2e3;
2211
- /**
2212
- * The Discord client and its controllers. Access it through `core.bot`.
2213
- */
2214
- var Bot = class {
2215
- /** @internal */
2216
- logger = new _seedcord_logger.Logger("Bot", { channel: "bot" });
2217
- isInitialized = false;
2218
- _client;
2219
- interactions;
2220
- events;
2221
- emojiInjector;
2222
- commandRegistry;
2223
- /** @internal */
2224
- async onHmr(event) {
2225
- if (this.interactions) await this.interactions.onHmr(event);
2226
- if (this.events) await this.events.onHmr(event);
2227
- if (this.commandRegistry) await this.commandRegistry.onHmr(event);
2228
- }
2229
- /** @internal */
2230
- constructor(core) {
2231
- this._client = new discord_js.Client(core.config.bot.clientOptions);
2232
- if (core.config.bot.interactions.path) this.interactions = new InteractionDispatcher(core);
2233
- if (core.config.bot.events.path) this.events = new EventDispatcher(core);
2234
- const commandsDir = core.config.bot.commands.path;
2235
- if (commandsDir) {
2236
- const injector = new _seedcord_core_internal.CommandInjector();
2237
- const registry = new _seedcord_core_node_internal.CommandRegistry({
2238
- dir: commandsDir,
2239
- rest: this._client.rest,
2240
- applicationId: () => this.applicationId(),
2241
- onDeployed: (result) => {
2242
- injector.inject(result, registry.allCommands());
2243
- }
2244
- });
2245
- this.commandRegistry = registry;
2246
- }
2247
- this.emojiInjector = new EmojiInjector(core);
2248
- this.registerShutdownTasks(core);
2249
- }
2250
- applicationId() {
2251
- const { application } = this._client;
2252
- if (!application) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreApplicationUnavailable);
2253
- return application.id;
2254
- }
2255
- registerShutdownTasks(core) {
2256
- core.shutdown.addTask(_seedcord_core_node_internal.ShutdownPhase.Unbind, "stop-dispatch", () => {
2257
- this.stopAccepting();
2258
- return Promise.resolve();
2259
- }, UNBIND_TIMEOUT_MS);
2260
- core.shutdown.addTask(_seedcord_core_node_internal.ShutdownPhase.Drain, "drain-dispatch", () => this.drain(), DRAIN_TASK_TIMEOUT_MS);
2261
- core.shutdown.addTask(_seedcord_core_node_internal.ShutdownPhase.Logout, "stop-bot", async () => await this.stop(), LOGOUT_TIMEOUT_MS);
2262
- }
2263
- stopAccepting() {
2264
- this.interactions?.stopAccepting();
2265
- this.events?.stopAccepting();
2266
- }
2267
- async drain() {
2268
- await Promise.allSettled([this.interactions?.drain(DISPATCH_DRAIN_TIMEOUT_MS), this.events?.drain(DISPATCH_DRAIN_TIMEOUT_MS)]);
2269
- }
2270
- /** @internal */
2271
- async init() {
2272
- if (this.isInitialized) return;
2273
- this.isInitialized = true;
2274
- const token = this.botToken;
2275
- if (this.interactions) await this.interactions.init();
2276
- if (this.events) await this.events.init();
2277
- await this.login(token);
2278
- await this.emojiInjector.init();
2279
- if (this.commandRegistry) {
2280
- await this.commandRegistry.init();
2281
- await this.commandRegistry.setCommands();
2282
- this.interactions?.warnUnhandledRoutes(this.commandRegistry.routeLeaves());
2283
- this.interactions?.warnUnhandledContextMenuRoutes(this.commandRegistry.contextMenuLeaves());
2284
- }
2285
- }
2286
- /** @internal */
2287
- async stop() {
2288
- this._client.removeAllListeners();
2289
- await this.logout();
2290
- }
2291
- async login(token) {
2292
- const ready = Promise.withResolvers();
2293
- this._client.once(discord_js.Events.ClientReady, () => ready.resolve());
2294
- this._client.login(token);
2295
- await ready.promise;
2296
- this.logger.info(`Logged in as ${chalk.default.bold.magenta(this._client.user?.username)}!`);
2297
- return this;
2298
- }
2299
- async logout() {
2300
- await this._client.destroy();
2301
- this.logger.info(chalk.default.bold.red("Logged out of Discord!"));
2302
- }
2303
- get client() {
2304
- return this._client;
2305
- }
2306
- };
2307
- __decorate([(0, envapt_legacy.Envapt)("DISCORD_BOT_TOKEN", { converter: (raw) => (0, _seedcord_errors_internal.validateDiscordToken)(raw) })], Bot.prototype, "botToken", void 0);
2308
-
2309
- //#endregion
2310
- //#region src/version.ts
2311
- /** Package version */
2312
- const version = "0.1.4";
2313
-
2314
- //#endregion
2315
- //#region src/Seedcord.ts
2316
- /**
2317
- * The gateway bot host. Opens a discord.js gateway session, discovers handlers, and runs
2318
- * coordinated startup and shutdown. Attach plugins with `attach()`.
2319
- */
2320
- var Seedcord = class Seedcord extends _seedcord_core_node_internal.Pluggable {
2321
- config;
2322
- /** @internal */
2323
- [_seedcord_types_internal.SeedcordBrand] = true;
2324
- /** @internal */
2325
- augmentTarget = "@seedcord/gateway";
2326
- /** @internal */
2327
- version = version;
2328
- healthCheck;
2329
- hmrManager;
2330
- /** @see {@link Bus} */
2331
- bus;
2332
- subscribers;
2333
- /** @see {@link Bot} */
2334
- bot;
2335
- /** The discord.js client's REST client. Its token is set during the Login phase. */
2336
- rest;
2337
- /** @see {@link IRateLimiter} */
2338
- rateLimiter;
2339
- /**
2340
- * @param config - Bot configuration including paths and Discord client options
2341
- * @throws A **SeedcordError** When attempting to create multiple instances (singleton)
2342
- */
2343
- constructor(config) {
2344
- super(new _seedcord_core_node_internal.CoordinatedShutdown(), new _seedcord_core_node_internal.CoordinatedStartup());
2345
- this.config = config;
2346
- (0, _seedcord_logger_node.installNodeDefaults)(config.logger);
2347
- (0, _seedcord_core_internal.setBotColor)(config.botColor);
2348
- this.hmrManager = new _seedcord_core_internal.HmrManager();
2349
- this.hmrManager.init();
2350
- this.bus = new _seedcord_core.Bus(this);
2351
- this.subscribers = new _seedcord_core_node_internal.SubscriberLoader(this.bus, config.subscribers.path);
2352
- this.bot = new Bot(this);
2353
- this.rest = this.bot.client.rest;
2354
- this.rateLimiter = config.store ?? new _seedcord_rate_limiter.MemoryRateLimiter();
2355
- this.healthCheck = _seedcord_core_node_internal.HealthCheck.fromOption(this.shutdown, config.healthCheck);
2356
- this.registerStartupTasks();
2357
- }
2358
- /** The bot's discord username, populated after login. */
2359
- get username() {
2360
- return this.bot.client.user?.username;
2361
- }
2362
- static reset() {
2363
- super.reset();
2364
- _seedcord_logger.LoggerChannelRegistry.instance.configure({});
2365
- }
2366
- registerStartupTasks() {
2367
- if (envapt.Envapter.isDevelopment || envapt.Envapter.isTest) this.registerHmrAwareModules();
2368
- this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Configuration, "bus-initialization", async () => {
2369
- this.bus.logger.utils.initialization("Subscribers", "start");
2370
- await this.subscribers.init();
2371
- this.bus.logger.utils.initialization("Subscribers", "end");
2372
- });
2373
- this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Login, "bot-initialization", async () => {
2374
- this.bot.logger.utils.initialization("Bot", "start");
2375
- await this.bot.init();
2376
- this.bot.logger.utils.initialization("Bot", "end");
2377
- });
2378
- const { healthCheck } = this;
2379
- if (healthCheck) this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Ready, "health-check", async () => {
2380
- healthCheck.logger.utils.initialization("HealthCheck", "start");
2381
- await healthCheck.init();
2382
- healthCheck.logger.utils.initialization("HealthCheck", "end");
2383
- });
2384
- }
2385
- /**
2386
- * Starts the bot and runs all initialization tasks
2387
- *
2388
- * @returns This Seedcord instance when initialized
2389
- */
2390
- async start() {
2391
- try {
2392
- await super.init();
2393
- } catch (caught) {
2394
- await this.shutdown.run(1, false);
2395
- Seedcord.reset();
2396
- throw caught;
2397
- }
2398
- return this;
2399
- }
2400
- registerHmrAwareModules() {
2401
- this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Configuration, "hmr-registration", async () => {
2402
- this.hmrManager.register(this.bot);
2403
- this.hmrManager.register(this.subscribers);
2404
- for (const plugin of this.plugins) this.hmrManager.register(plugin);
2405
- await Promise.resolve();
2406
- });
2407
- }
2408
- };
2409
-
2410
- //#endregion
2411
- exports.ArraySource = ArraySource;
2412
- exports.AutocompleteHandler = AutocompleteHandler;
2413
- exports.BaseHandler = BaseHandler;
2414
- exports.ButtonHandler = ButtonHandler;
2415
- exports.ComponentHandler = ComponentHandler;
2416
- exports.ContextMenuHandler = ContextMenuHandler;
2417
- exports.CursorSource = CursorSource;
2418
- exports.Emojis = Emojis;
2419
- exports.EventHandler = EventHandler;
2420
- exports.EventMiddleware = EventMiddleware;
2421
- exports.Gated = Gated;
2422
- exports.IgnoreBots = IgnoreBots;
2423
- exports.InteractionHandler = InteractionHandler;
2424
- exports.InteractionMiddleware = InteractionMiddleware;
2425
- exports.Middleware = Middleware;
2426
- exports.MiddlewareType = MiddlewareType;
2427
- exports.ModalHandler = ModalHandler;
2428
- exports.Nsfw = Nsfw;
2429
- exports.Paginator = Paginator;
2430
- exports.Plugin = Plugin;
2431
- exports.RegisterEvent = RegisterEvent;
2432
- exports.RepliableHandler = RepliableHandler;
2433
- exports.Seedcord = Seedcord;
2434
- exports.SelectMenuHandler = SelectMenuHandler;
2435
- exports.SlashHandler = SlashHandler;
2436
- exports.Subscriber = Subscriber;
2437
- exports.WebhookLog = WebhookLog;
2438
- exports.checkBotPermissions = checkBotPermissions;
2439
- exports.checkPermissions = checkPermissions;
2440
- exports.getConfirmation = getConfirmation;
2441
- exports.hasPermsToAssign = hasPermsToAssign;
2442
- exports.version = version;
2443
- Object.keys(_seedcord_core).forEach(function (k) {
2444
- if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
2445
- enumerable: true,
2446
- get: function () { return _seedcord_core[k]; }
2447
- });
2448
- });
2449
-
2450
- Object.keys(_seedcord_errors).forEach(function (k) {
2451
- if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
2452
- enumerable: true,
2453
- get: function () { return _seedcord_errors[k]; }
2454
- });
2455
- });
2456
-
2457
- var _seedcord_event_emitter = require("@seedcord/event-emitter");
2458
- Object.keys(_seedcord_event_emitter).forEach(function (k) {
2459
- if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
2460
- enumerable: true,
2461
- get: function () { return _seedcord_event_emitter[k]; }
2462
- });
2463
- });
2464
-
2465
- Object.keys(_seedcord_logger).forEach(function (k) {
2466
- if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
2467
- enumerable: true,
2468
- get: function () { return _seedcord_logger[k]; }
2469
- });
2470
- });
2471
-
2472
- Object.keys(_seedcord_rate_limiter).forEach(function (k) {
2473
- if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
2474
- enumerable: true,
2475
- get: function () { return _seedcord_rate_limiter[k]; }
2476
- });
2477
- });
2478
-
2479
- Object.keys(_seedcord_utils).forEach(function (k) {
2480
- if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
2481
- enumerable: true,
2482
- get: function () { return _seedcord_utils[k]; }
2483
- });
2484
- });
2485
-
2486
- //# sourceMappingURL=index.cjs.map