seedcord 0.4.2 → 0.5.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.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import 'reflect-metadata';
2
2
  import { Logger, ShutdownPhase, CoordinatedShutdown, CoordinatedStartup, HealthCheck, StartupPhase } from '@seedcord/services';
3
3
  export * from '@seedcord/services';
4
- import chalk2 from 'chalk';
5
- import { SeparatorBuilder, SectionBuilder, MediaGalleryBuilder, FileBuilder, TextDisplayBuilder, ContainerBuilder, SlashCommandSubcommandGroupBuilder, SlashCommandSubcommandBuilder, ContextMenuCommandBuilder, ModalBuilder, RoleSelectMenuBuilder, MentionableSelectMenuBuilder, ChannelSelectMenuBuilder, UserSelectMenuBuilder, StringSelectMenuOptionBuilder, StringSelectMenuBuilder, ButtonBuilder, EmbedBuilder, SlashCommandBuilder, InteractionContextType, ActionRowBuilder, TextInputBuilder, Collection, MessageFlags, Events, Client, WebhookClient, DiscordAPIError, SnowflakeUtil, Message } from 'discord.js';
4
+ import chalk4 from 'chalk';
5
+ import { SeparatorBuilder, SectionBuilder, MediaGalleryBuilder, FileBuilder, TextDisplayBuilder, ContainerBuilder, RoleSelectMenuBuilder, MentionableSelectMenuBuilder, ChannelSelectMenuBuilder, UserSelectMenuBuilder, StringSelectMenuOptionBuilder, StringSelectMenuBuilder, ButtonBuilder, TextInputBuilder, LabelBuilder, ModalBuilder, EmbedBuilder, SlashCommandSubcommandGroupBuilder, SlashCommandSubcommandBuilder, ContextMenuCommandBuilder, SlashCommandBuilder, InteractionContextType, ActionRowBuilder, Collection, MessageFlags, Events, Client, WebhookClient, AttachmentBuilder, SeparatorSpacingSize, DiscordAPIError, SnowflakeUtil, Role, PermissionFlagsBits, ChatInputCommandInteraction, AutocompleteInteraction, Message, TextChannel, Guild, RESTJSONErrorCodes, Colors } from 'discord.js';
6
6
  import { Envapt } from 'envapt';
7
- import { traverseDirectory } from '@seedcord/utils';
7
+ import { hexToNumber, traverseDirectory, filterCirculars, prettify } from '@seedcord/utils';
8
8
  export * from '@seedcord/utils';
9
9
  import * as crypto2 from 'crypto';
10
10
  import { EventEmitter } from 'events';
@@ -39,26 +39,26 @@ var Pluggable = class _Pluggable {
39
39
  return this;
40
40
  }
41
41
  /**
42
- * Attaches a plugin to this instance
43
- *
44
- * Plugins provide external functionality and are initialized during the specified startup phase.
45
- * The plugin instance becomes available as a property in `core` wherever it's available.
46
- *
47
- * Make sure to augment the {@link Core} interface with the plugin type to ensure TypeScript recognizes it and provides intellisense.
48
- *
49
- * @typeParam Key - The property name for accessing the plugin
50
- * @typeParam Ctor - The plugin constructor type
51
- * @param key - Property name to access the plugin instance
52
- * @param Plugin - Plugin constructor class
53
- * @param startupPhase - When during startup to initialize this plugin ({@link StartupPhase})
54
- * @param args - Additional arguments to pass to the plugin constructor
55
- * @returns This instance with the plugin attached as a typed property
56
- * @throws An {@link Error} When called after initialization or if key already exists
57
- * @example
58
- * ```typescript
59
- * seedcord.attach('db', Mongo, StartupPhase.Configuration, { uri: 'mongodb://...', name: 'seedcord', dir: ... })
60
- * ```
61
- */
42
+ * Attaches a plugin to this instance
43
+ *
44
+ * Plugins provide external functionality and are initialized during the specified startup phase.
45
+ * The plugin instance becomes available as a property in `core` wherever it's available.
46
+ *
47
+ * Make sure to augment the {@link Core} interface with the plugin type to ensure TypeScript recognizes it and provides intellisense.
48
+ *
49
+ * @typeParam Key - The property name for accessing the plugin
50
+ * @typeParam Ctor - The plugin constructor type
51
+ * @param key - Property name to access the plugin instance
52
+ * @param Plugin - Plugin constructor class
53
+ * @param startupPhase - When during startup to initialize this plugin ({@link StartupPhase})
54
+ * @param args - Additional arguments to pass to the plugin constructor
55
+ * @returns This instance with the plugin attached as a typed property
56
+ * @throws An {@link Error} When called after initialization or if key already exists
57
+ * @example
58
+ * ```typescript
59
+ * seedcord.attach('db', Mongo, StartupPhase.Configuration, { uri: 'mongodb://...', name: 'seedcord', dir: ... })
60
+ * ```
61
+ */
62
62
  attach(key, Plugin2, startupPhase, ...args) {
63
63
  if (this.isInitialized) throw new Error("Cannot attach a plugin after initialization.");
64
64
  if (this[key]) throw new Error(`Plugin with key "${key}" already exists.`);
@@ -67,13 +67,27 @@ var Pluggable = class _Pluggable {
67
67
  [key]: instance
68
68
  };
69
69
  this.startup.addTask(startupPhase, `Plugin:${key}`, async () => {
70
- instance.logger.info(chalk2.bold("Initializing"));
70
+ instance.logger.info(chalk4.bold("Initializing"));
71
71
  await instance.init();
72
- instance.logger.info(chalk2.bold("Initialized"));
72
+ instance.logger.info(chalk4.bold("Initialized"));
73
73
  }, _Pluggable.PLUGIN_INIT_TIMEOUT_MS);
74
74
  return Object.assign(this, entry);
75
75
  }
76
76
  };
77
+ function parseEnvColor(raw, fallback) {
78
+ if (!raw) return fallback;
79
+ const toHex = /* @__PURE__ */ __name((n) => `#${n.toString(16).padStart(6, "0")}`, "toHex");
80
+ const num = Number(raw);
81
+ if (!Number.isNaN(num) && num >= 0) return toHex(num);
82
+ if (raw.startsWith("#")) return raw;
83
+ if (/^[0-9A-Fa-f]{6}$/.test(raw)) return `#${raw}`;
84
+ const colorKey = Object.keys(Colors).find((key) => key.toLowerCase() === raw.toLowerCase());
85
+ if (colorKey) return toHex(Colors[colorKey]);
86
+ return fallback;
87
+ }
88
+ __name(parseEnvColor, "parseEnvColor");
89
+
90
+ // src/interfaces/Components.ts
77
91
  function _ts_decorate(decorators, target, key, desc) {
78
92
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
79
93
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -86,8 +100,18 @@ function _ts_metadata(k, v) {
86
100
  }
87
101
  __name(_ts_metadata, "_ts_metadata");
88
102
  var BuilderTypes = {
103
+ // Command Components
89
104
  command: SlashCommandBuilder,
105
+ context_menu: ContextMenuCommandBuilder,
106
+ subcommand: SlashCommandSubcommandBuilder,
107
+ group: SlashCommandSubcommandGroupBuilder,
108
+ // Embed Components
90
109
  embed: EmbedBuilder,
110
+ // Modal Components
111
+ modal: ModalBuilder,
112
+ label: LabelBuilder,
113
+ text_input: TextInputBuilder,
114
+ // Action Row Components
91
115
  button: ButtonBuilder,
92
116
  menu_string: StringSelectMenuBuilder,
93
117
  menu_option_string: StringSelectMenuOptionBuilder,
@@ -95,10 +119,7 @@ var BuilderTypes = {
95
119
  menu_channel: ChannelSelectMenuBuilder,
96
120
  menu_mentionable: MentionableSelectMenuBuilder,
97
121
  menu_role: RoleSelectMenuBuilder,
98
- modal: ModalBuilder,
99
- context_menu: ContextMenuCommandBuilder,
100
- subcommand: SlashCommandSubcommandBuilder,
101
- group: SlashCommandSubcommandGroupBuilder,
122
+ // ComponentsV2
102
123
  container: ContainerBuilder,
103
124
  text_display: TextDisplayBuilder,
104
125
  file: FileBuilder,
@@ -112,13 +133,9 @@ var RowTypes = {
112
133
  menu_user: ActionRowBuilder,
113
134
  menu_channel: ActionRowBuilder,
114
135
  menu_mentionable: ActionRowBuilder,
115
- menu_role: ActionRowBuilder,
116
- modal: ActionRowBuilder
117
- };
118
- var ModalTypes = {
119
- text: TextInputBuilder
136
+ menu_role: ActionRowBuilder
120
137
  };
121
- var BaseComponent = class BaseComponent2 {
138
+ var BaseComponent = class {
122
139
  static {
123
140
  __name(this, "BaseComponent");
124
141
  }
@@ -128,26 +145,26 @@ var BaseComponent = class BaseComponent2 {
128
145
  this._component = new ComponentClass();
129
146
  }
130
147
  /**
131
- * Gets the component instance for configuration
132
- *
133
- * Use this to access Discord.js builder methods like setTitle(), setDescription(), etc.
134
- *
135
- * Use this in your component classes to configure the builder
136
- * @example this.instance.setTitle('My Modal')
137
- */
148
+ * Gets the component instance for configuration
149
+ *
150
+ * Use this to access Discord.js builder methods like setTitle(), setDescription(), etc.
151
+ *
152
+ * Use this in your component classes to configure the builder
153
+ * @example this.instance.setTitle('My Modal')
154
+ */
138
155
  get instance() {
139
156
  return this._component;
140
157
  }
141
158
  /**
142
- * Builds a customId string for interactive components
143
- *
144
- * Creates customIds in the format "prefix:arg1-arg2-arg3" for buttons, modals, etc.
145
- * Arguments are joined with hyphens and separated from prefix with a colon.
146
- *
147
- * @param prefix - The route prefix that handlers will match against
148
- * @param args - Additional arguments to encode in the customId
149
- * @returns Formatted customId string
150
- */
159
+ * Builds a customId string for interactive components
160
+ *
161
+ * Creates customIds in the format `prefix:arg1-arg2-arg3` for buttons, modals, etc.
162
+ * Arguments are joined with hyphens and separated from prefix with a colon.
163
+ *
164
+ * @param prefix - The route prefix that handlers will match against
165
+ * @param args - Additional arguments to encode in the customId
166
+ * @returns Formatted customId string
167
+ */
151
168
  buildCustomId(prefix, ...args) {
152
169
  if (args.length === 0) return prefix;
153
170
  return `${prefix}:${args.join("-")}`;
@@ -161,6 +178,9 @@ var BuilderComponent = class extends BaseComponent {
161
178
  const ComponentClass = BuilderTypes[type];
162
179
  super(ComponentClass);
163
180
  if (this.instance instanceof EmbedBuilder) this.instance.setColor(this.botColor);
181
+ if (this.instance instanceof ContainerBuilder) {
182
+ this.instance.setAccentColor(this.botColor === "Default" ? void 0 : hexToNumber(this.botColor.toString()));
183
+ }
164
184
  if (this.instance instanceof SlashCommandBuilder || this.instance instanceof ContextMenuCommandBuilder) {
165
185
  this.instance.setContexts(InteractionContextType.Guild);
166
186
  }
@@ -171,7 +191,8 @@ var BuilderComponent = class extends BaseComponent {
171
191
  };
172
192
  _ts_decorate([
173
193
  Envapt("DEFAULT_BOT_COLOR", {
174
- fallback: "Default"
194
+ fallback: "Default",
195
+ converter: /* @__PURE__ */ __name((raw, fallback) => parseEnvColor(raw, fallback), "converter")
175
196
  }),
176
197
  _ts_metadata("design:type", typeof ColorResolvable === "undefined" ? Object : ColorResolvable)
177
198
  ], BuilderComponent.prototype, "botColor", void 0);
@@ -187,39 +208,13 @@ var RowComponent = class extends BaseComponent {
187
208
  return this.instance;
188
209
  }
189
210
  };
190
- var ModalRow = class ModalRow2 extends RowComponent {
191
- static {
192
- __name(this, "ModalRow");
193
- }
194
- /**
195
- * Creates a new modal action row with the specified component.
196
- *
197
- * @param component - The modal field component to wrap in an action row
198
- */
199
- constructor(component) {
200
- super("modal");
201
- this.instance.addComponents(component);
202
- }
203
- };
204
- var ModalComponent = class extends BaseComponent {
205
- static {
206
- __name(this, "ModalComponent");
207
- }
208
- constructor(type) {
209
- const ComponentClass = ModalTypes[type];
210
- super(ComponentClass);
211
- }
212
- get component() {
213
- return new ModalRow(this.instance).component;
214
- }
215
- };
216
211
  var BaseErrorEmbed = class extends BuilderComponent {
217
212
  static {
218
213
  __name(this, "BaseErrorEmbed");
219
214
  }
220
215
  /**
221
- * Creates a new error embed with default configuration.
222
- */
216
+ * Creates a new error embed with default configuration.
217
+ */
223
218
  constructor() {
224
219
  super("embed");
225
220
  this.instance.setTitle("Cannot Proceed");
@@ -237,27 +232,25 @@ var CustomError = class extends Error {
237
232
  Error.captureStackTrace(this, this.constructor);
238
233
  }
239
234
  /**
240
- * Whether this error should be emitted to logs
241
- *
242
- * Controls logging behavior. Errors with emit=true will always be logged,
243
- * while emit=false errors may be suppressed in production.
244
- *
245
- * @returns True if the error should be logged
246
- */
235
+ * Whether this error should be emitted to logs
236
+ *
237
+ * Controls logging behavior. Errors with emit=true will always be logged,
238
+ * while emit=false errors may be suppressed in production.
239
+ *
240
+ * @returns True if the error should be logged
241
+ */
247
242
  get emit() {
248
243
  return this._emit;
249
244
  }
250
245
  /**
251
- * Sets whether this error should be emitted to logs
252
- *
253
- * @see {@link emit}
254
- */
246
+ * Sets whether this error should be emitted to logs
247
+ */
255
248
  set emit(value) {
256
249
  this._emit = value;
257
250
  }
258
251
  };
259
252
 
260
- // src/bot/decorators/CommandRegisterable.ts
253
+ // src/bot/decorators/Command.ts
261
254
  var CommandMetadataKey = Symbol("command:metadata");
262
255
  function RegisterCommand(scope, guilds = []) {
263
256
  return (ctor) => {
@@ -288,9 +281,9 @@ var CommandRegistry = class {
288
281
  async init() {
289
282
  if (this.isInitialised) return;
290
283
  this.isInitialised = true;
291
- this.logger.info(chalk2.bold(this.core.config.bot.commands.path));
284
+ this.logger.info(chalk4.bold(this.core.config.bot.commands.path));
292
285
  await this.loadCommands(this.core.config.bot.commands.path);
293
- this.logger.info(`${chalk2.bold.green("Loaded")}: ${chalk2.magenta.bold(this.globalCommands.length)} global, ${chalk2.magenta.bold(this.guildCommands.size)} guild groups`);
286
+ this.logger.info(`${chalk4.bold.green("Loaded")}: ${chalk4.magenta.bold(this.globalCommands.length)} global, ${chalk4.magenta.bold(this.guildCommands.size)} guild groups`);
294
287
  }
295
288
  async loadCommands(dir) {
296
289
  await traverseDirectory(dir, (_full, rel, mod) => {
@@ -309,24 +302,24 @@ var CommandRegistry = class {
309
302
  const commandType = comp instanceof SlashCommandBuilder ? "slash command" : "context menu command";
310
303
  if (meta.scope === "global") {
311
304
  this.globalCommands.push(comp);
312
- this.logger.info(`${chalk2.italic("Registered")} ${chalk2.bold.yellow(ctor.name)} from ${chalk2.gray(rel)}`);
313
- this.logger.info(` \u2192 Global ${commandType}: ${chalk2.bold.cyan(comp.name)}`);
305
+ this.logger.info(`${chalk4.italic("Registered")} ${chalk4.bold.yellow(ctor.name)} from ${chalk4.gray(rel)}`);
306
+ this.logger.info(` \u2192 Global ${commandType}: ${chalk4.bold.cyan(comp.name)}`);
314
307
  } else {
315
308
  for (const g of meta.guilds) {
316
309
  const arr = this.guildCommands.get(g) ?? [];
317
310
  arr.push(comp);
318
311
  this.guildCommands.set(g, arr);
319
312
  }
320
- this.logger.info(`${chalk2.italic("Registered")} ${chalk2.bold.yellow(ctor.name)} from ${chalk2.gray(rel)}`);
321
- this.logger.info(` \u2192 Guild ${commandType}: ${chalk2.bold.cyan(comp.name)} for ${chalk2.magenta.bold(meta.guilds.length)} guild(s)`);
313
+ this.logger.info(`${chalk4.italic("Registered")} ${chalk4.bold.yellow(ctor.name)} from ${chalk4.gray(rel)}`);
314
+ this.logger.info(` \u2192 Guild ${commandType}: ${chalk4.bold.cyan(comp.name)} for ${chalk4.magenta.bold(meta.guilds.length)} guild(s)`);
322
315
  }
323
316
  }
324
317
  async setCommands() {
325
318
  if (this.globalCommands.length > 0) {
326
319
  await this.core.bot.client.application?.commands.set(this.globalCommands);
327
320
  const tag = this.globalCommands.length === 1 ? "command" : "commands";
328
- this.logger.info(`${chalk2.bold.green("Configured")} ${chalk2.magenta.bold(this.globalCommands.length)} global ${tag}`);
329
- this.logger.info(` \u2192 ${this.globalCommands.map((command) => chalk2.bold.cyan(command.name)).join(", ")}`);
321
+ this.logger.info(`${chalk4.bold.green("Configured")} ${chalk4.magenta.bold(this.globalCommands.length)} global ${tag}`);
322
+ this.logger.info(` \u2192 ${this.globalCommands.map((command) => chalk4.bold.cyan(command.name)).join(", ")}`);
330
323
  }
331
324
  for (const [guildId, commands] of this.guildCommands.entries()) {
332
325
  const guild = this.core.bot.client.guilds.cache.get(guildId);
@@ -336,14 +329,14 @@ var CommandRegistry = class {
336
329
  }
337
330
  await guild.commands.set(commands);
338
331
  const tag = commands.length === 1 ? "command" : "commands";
339
- this.logger.info(`${chalk2.bold.green("Configured")} ${chalk2.magenta.bold(commands.length)} ${tag} for guild ${chalk2.bold.yellow(guild.name)}`);
340
- this.logger.info(` \u2192 ${commands.map((command) => chalk2.bold.cyan(command.name)).join(", ")}`);
332
+ this.logger.info(`${chalk4.bold.green("Configured")} ${chalk4.magenta.bold(commands.length)} ${tag} for guild ${chalk4.bold.yellow(guild.name)}`);
333
+ this.logger.info(` \u2192 ${commands.map((command) => chalk4.bold.cyan(command.name)).join(", ")}`);
341
334
  }
342
335
  }
343
336
  };
344
337
 
345
338
  // src/interfaces/Handler.ts
346
- var BaseHandler = class BaseHandler2 {
339
+ var BaseHandler = class {
347
340
  static {
348
341
  __name(this, "BaseHandler");
349
342
  }
@@ -374,19 +367,19 @@ var BaseHandler = class BaseHandler2 {
374
367
  return this.event;
375
368
  }
376
369
  /**
377
- * Gets arguments parsed from interaction customId
378
- *
379
- * Arguments are extracted from customId using ":" and "-" separators.
380
- * For customId "accept:user123-guild456", returns ["user123", "guild456"]
381
- */
370
+ * Gets arguments parsed from interaction customId
371
+ *
372
+ * Arguments are extracted from customId using `:` and `-` separators.
373
+ * For customId `accept:user123-guild456`, returns `["user123", "guild456"]`
374
+ */
382
375
  getArgs() {
383
376
  return this.args;
384
377
  }
385
378
  /**
386
- * Gets a specific argument by index from parsed customId
387
- * @param index - Zero-based index of the argument to retrieve
388
- * @returns The argument at the specified index, or undefined if not found
389
- */
379
+ * Gets a specific argument by index from parsed customId
380
+ * @param index - Zero-based index of the argument to retrieve
381
+ * @returns The argument at the specified index, or undefined if not found
382
+ */
390
383
  getArg(index) {
391
384
  return this.args[index];
392
385
  }
@@ -407,11 +400,19 @@ var InteractionMiddleware = class extends BaseHandler {
407
400
  super(event, core, args);
408
401
  }
409
402
  };
403
+ var EventMiddleware = class extends BaseHandler {
404
+ static {
405
+ __name(this, "EventMiddleware");
406
+ }
407
+ constructor(event, core, args) {
408
+ super(event, core, args);
409
+ }
410
+ };
410
411
  var AutocompleteHandler = class extends BaseHandler {
411
412
  static {
412
413
  __name(this, "AutocompleteHandler");
413
414
  }
414
- /** The currently focused autocomplete option (Based on what you set in \@AutocompleteRoute) */
415
+ /** The currently focused autocomplete option (Based on what you set in `@AutocompleteRoute`) */
415
416
  focused;
416
417
  constructor(event, core, args) {
417
418
  super(event, core, args);
@@ -427,15 +428,57 @@ var EventHandler = class extends BaseHandler {
427
428
  }
428
429
  };
429
430
 
430
- // src/bot/decorators/EventRegisterable.ts
431
+ // src/miscellaneous/areRoutes.ts
432
+ function areRoutes(routes) {
433
+ return Array.isArray(routes) && routes.every((r) => typeof r === "string");
434
+ }
435
+ __name(areRoutes, "areRoutes");
436
+
437
+ // src/bot/decorators/Events.ts
431
438
  var EventMetadataKey = Symbol("event:metadata");
432
- function RegisterEvent(eventName) {
439
+ function RegisterEvent(events) {
433
440
  return function(constructor) {
434
- Reflect.defineMetadata(EventMetadataKey, eventName, constructor);
441
+ const saved = Reflect.getMetadata(EventMetadataKey, constructor);
442
+ const existing = areRoutes(saved) ? saved : [];
443
+ const toStore = Array.isArray(events) ? events : [
444
+ events
445
+ ];
446
+ Reflect.defineMetadata(EventMetadataKey, [
447
+ ...existing,
448
+ ...toStore
449
+ ], constructor);
435
450
  };
436
451
  }
437
452
  __name(RegisterEvent, "RegisterEvent");
438
453
 
454
+ // src/bot/decorators/Middlewares.ts
455
+ var MiddlewareType = /* @__PURE__ */ (function(MiddlewareType2) {
456
+ MiddlewareType2["Interaction"] = "middleware:interaction";
457
+ MiddlewareType2["Event"] = "middleware:event";
458
+ return MiddlewareType2;
459
+ })({});
460
+ var MiddlewareMetadataKey = Symbol("middleware:metadata");
461
+ function Middleware(type, priority = 0, options = {}) {
462
+ return (ctor) => {
463
+ const normalizedPriority = Number(priority);
464
+ if (!Number.isFinite(normalizedPriority)) {
465
+ throw new TypeError("Middleware priority must be a finite number");
466
+ }
467
+ if (type === "middleware:interaction" && options.events?.length) {
468
+ throw new Error("Interaction middleware cannot specify event filters");
469
+ }
470
+ const metadata = {
471
+ priority: normalizedPriority,
472
+ type,
473
+ ...options.events ? {
474
+ events: options.events
475
+ } : {}
476
+ };
477
+ Reflect.defineMetadata(MiddlewareMetadataKey, metadata, ctor);
478
+ };
479
+ }
480
+ __name(Middleware, "Middleware");
481
+
439
482
  // src/bot/controllers/EventController.ts
440
483
  var EventController = class {
441
484
  static {
@@ -445,6 +488,7 @@ var EventController = class {
445
488
  logger = new Logger("Events");
446
489
  isInitialized = false;
447
490
  eventMap = new Collection();
491
+ middlewares = [];
448
492
  constructor(core) {
449
493
  this.core = core;
450
494
  }
@@ -454,42 +498,96 @@ var EventController = class {
454
498
  }
455
499
  this.isInitialized = true;
456
500
  const handlersDir = this.core.config.bot.events.path;
457
- this.logger.info(chalk2.bold(handlersDir));
501
+ this.logger.info(chalk4.bold(handlersDir));
502
+ const middlewareDir = this.core.config.bot.events.middlewares;
503
+ if (middlewareDir) {
504
+ this.logger.info(`${chalk4.bold(middlewareDir)} ${chalk4.gray("(middlewares)")}`);
505
+ await this.loadMiddlewares(middlewareDir);
506
+ }
458
507
  await this.loadHandlers(handlersDir);
459
508
  this.attachToClient();
509
+ this.logger.info(`\u2192 ${chalk4.magenta.bold(this.middlewares.length)} middlewares`);
460
510
  const loadedEventsArray = [];
461
511
  this.eventMap.forEach((handlers, eventName) => {
462
- loadedEventsArray.push(`${chalk2.magenta.bold(handlers.length)} ${eventName}`);
512
+ loadedEventsArray.push(`${chalk4.magenta.bold(handlers.length)} ${eventName}`);
463
513
  });
464
- this.logger.info(`${chalk2.bold.green("Loaded")}: ${this.eventMap.size > 0 ? loadedEventsArray.join(", ") : "none"}`);
514
+ this.logger.info(`${chalk4.bold.green("Loaded")}: ${this.eventMap.size > 0 ? loadedEventsArray.join(", ") : "none"}`);
465
515
  }
466
516
  async loadHandlers(dir) {
467
517
  await traverseDirectory(dir, (_fullPath, relativePath, imported) => {
468
518
  for (const val of Object.values(imported)) {
469
- if (this.isEventHandlerClass(val)) {
470
- this.registerHandler(val);
471
- this.logger.info(`${chalk2.italic("Registered")} ${chalk2.bold.yellow(val.name)} from ${chalk2.gray(relativePath)}`);
472
- }
519
+ if (!this.isEventHandlerClass(val)) continue;
520
+ this.registerHandler(val);
521
+ this.logger.info(`${chalk4.italic("Registered")} ${chalk4.bold.yellow(val.name)} from ${chalk4.gray(relativePath)}`);
522
+ }
523
+ }, this.logger);
524
+ }
525
+ async loadMiddlewares(dir) {
526
+ await traverseDirectory(dir, (_fullPath, relativePath, imported) => {
527
+ for (const val of Object.values(imported)) {
528
+ if (!this.isMiddlewareClass(val)) continue;
529
+ const metadata = Reflect.getMetadata(MiddlewareMetadataKey, val);
530
+ if (metadata?.type !== MiddlewareType.Event) continue;
531
+ this.registerMiddleware(val, metadata, relativePath);
473
532
  }
474
533
  }, this.logger);
475
534
  }
535
+ registerMiddleware(middlewareCtor, metadata, relativePath) {
536
+ const alreadyRegistered = this.middlewares.some((entry) => entry.ctor === middlewareCtor);
537
+ if (alreadyRegistered) return;
538
+ this.middlewares.push({
539
+ ctor: middlewareCtor,
540
+ priority: metadata.priority,
541
+ ...metadata.events ? {
542
+ events: metadata.events
543
+ } : {}
544
+ });
545
+ this.middlewares.sort((a, b) => a.priority - b.priority);
546
+ this.logger.info(`${chalk4.italic("Registered event middleware")} ${chalk4.bold.yellow(middlewareCtor.name)} ${chalk4.gray(`(priority ${metadata.priority})`)} from ${chalk4.gray(relativePath)}`);
547
+ }
548
+ async runMiddlewares(eventName, args) {
549
+ for (const { ctor, events } of this.middlewares) {
550
+ if (events && !events.includes(eventName)) continue;
551
+ try {
552
+ const middleware = new ctor(args, this.core);
553
+ if (middleware.hasChecks()) await middleware.runChecks();
554
+ if (middleware.shouldBreak() || middleware.hasErrors()) return false;
555
+ await middleware.execute();
556
+ if (middleware.shouldBreak() || middleware.hasErrors()) return false;
557
+ } catch (err) {
558
+ this.logger.error(`Error in event middleware ${ctor.name} for event ${String(eventName)}:`, err);
559
+ return false;
560
+ }
561
+ }
562
+ return true;
563
+ }
476
564
  isEventHandlerClass(obj) {
477
565
  if (typeof obj !== "function") return false;
478
566
  return obj.prototype instanceof EventHandler && Reflect.hasMetadata(EventMetadataKey, obj);
479
567
  }
568
+ isMiddlewareClass(obj) {
569
+ if (typeof obj !== "function") return false;
570
+ return obj.prototype instanceof EventMiddleware && Reflect.hasMetadata(MiddlewareMetadataKey, obj);
571
+ }
480
572
  registerHandler(handlerClass) {
481
- const eventName = Reflect.getMetadata(EventMetadataKey, handlerClass);
482
- if (!eventName) return;
483
- let handlers = this.eventMap.get(eventName);
484
- if (!handlers) {
485
- handlers = [];
486
- this.eventMap.set(eventName, handlers);
573
+ const raw = Reflect.getMetadata(EventMetadataKey, handlerClass);
574
+ const names = areRoutes(raw) ? raw : typeof raw === "string" ? [
575
+ raw
576
+ ] : [];
577
+ if (names.length === 0) return;
578
+ for (const name of names) {
579
+ const key = name;
580
+ let handlers = this.eventMap.get(key);
581
+ if (!handlers) {
582
+ handlers = [];
583
+ this.eventMap.set(key, handlers);
584
+ }
585
+ handlers.push(handlerClass);
487
586
  }
488
- handlers.push(handlerClass);
489
587
  }
490
588
  attachToClient() {
491
589
  for (const [eventName] of this.eventMap) {
492
- this.logger.debug(`Attaching ${chalk2.bold.green(eventName)} to ${chalk2.bold.yellow(this.core.bot.client.user?.username)}`);
590
+ this.logger.debug(`Attaching ${chalk4.bold.green(eventName)} to ${chalk4.bold.yellow(this.core.bot.client.user?.username)}`);
493
591
  this.core.bot.client.on(eventName, (...args) => {
494
592
  void (async () => {
495
593
  await this.processEvent(eventName, args);
@@ -498,11 +596,13 @@ var EventController = class {
498
596
  }
499
597
  }
500
598
  async processEvent(eventName, args) {
599
+ const shouldContinue = await this.runMiddlewares(eventName, args);
600
+ if (!shouldContinue) return;
501
601
  const handlerCtors = this.eventMap.get(eventName);
502
602
  if (!handlerCtors || handlerCtors.length === 0) return;
503
603
  for (const HandlerCtor of handlerCtors) {
504
604
  try {
505
- this.logger.debug(`Processing ${chalk2.bold.green(eventName)} with ${chalk2.gray(HandlerCtor.name)}`);
605
+ this.logger.debug(`Processing ${chalk4.bold.green(eventName)} with ${chalk4.gray(HandlerCtor.name)}`);
506
606
  const handler = new HandlerCtor(args, this.core);
507
607
  if (handler.hasChecks()) {
508
608
  await handler.runChecks();
@@ -518,7 +618,7 @@ var EventController = class {
518
618
  }
519
619
  };
520
620
 
521
- // src/bot/decorators/InteractionConfigurable.ts
621
+ // src/bot/decorators/Interactions.ts
522
622
  var InteractionRoutes = /* @__PURE__ */ (function(InteractionRoutes2) {
523
623
  InteractionRoutes2["Slash"] = "interaction:slash";
524
624
  InteractionRoutes2["Button"] = "interaction:button";
@@ -598,9 +698,6 @@ function SelectMenuRoute(type, routeOrRoutes) {
598
698
  }
599
699
  __name(SelectMenuRoute, "SelectMenuRoute");
600
700
  function storeMetadata(symbol, routes, constructor) {
601
- const areRoutes = /* @__PURE__ */ __name((routes2) => {
602
- return Array.isArray(routes2) && routes2.every((r) => typeof r === "string");
603
- }, "areRoutes");
604
701
  const savedRoutes = Reflect.getMetadata(symbol, constructor);
605
702
  const existing = areRoutes(savedRoutes) ? savedRoutes : [];
606
703
  const toStore = Array.isArray(routes) ? routes : [
@@ -621,11 +718,11 @@ var DatabaseError = class extends CustomError {
621
718
  }
622
719
  uuid;
623
720
  /**
624
- * Creates a new DatabaseError.
625
- *
626
- * @param message - The error message describing what went wrong
627
- * @param uuid - A unique identifier for this specific error instance
628
- */
721
+ * Creates a new DatabaseError.
722
+ *
723
+ * @param message - The error message describing what went wrong
724
+ * @param uuid - A unique identifier for this specific error instance
725
+ */
629
726
  constructor(message, uuid) {
630
727
  super(message), this.uuid = uuid;
631
728
  this.emit = true;
@@ -635,69 +732,71 @@ var DatabaseError = class extends CustomError {
635
732
  }
636
733
  };
637
734
 
638
- // src/bot/utilities/ErrorHandlingUtils.ts
639
- var ErrorHandlingUtils = class {
640
- static {
641
- __name(this, "ErrorHandlingUtils");
642
- }
643
- static logger = new Logger("Errors");
644
- /**
645
- * Processes an error and extracts the standardized response, if available.
646
- *
647
- * Handles different error types (CustomError, DatabaseError) with appropriate
648
- * logging, side effects, and user-facing error messages.
649
- *
650
- * @param error - The error to process
651
- * @param core - The core framework instance
652
- * @param guild - The guild where the error occurred (if any)
653
- * @param user - The user who triggered the error (if any)
654
- * @returns Object containing UUID and formatted error response embed
655
- */
656
- static extractErrorResponse(error, core, guild, user) {
657
- const uuid = crypto2.randomUUID();
658
- if (error instanceof CustomError) {
659
- if (error instanceof DatabaseError) {
660
- core.effects.emit("unknownException", {
661
- uuid,
662
- error,
663
- guild,
664
- user
665
- });
666
- this.logger.error(`DatabaseError: ${error.uuid}`);
667
- } else if (error.emit) {
668
- this.logger.error(`${error.name}: ${error.message}`, error);
669
- }
670
- return {
735
+ // src/bot/utilities/errors/extractErrorResponse.ts
736
+ function _ts_decorate2(decorators, target, key, desc) {
737
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
738
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
739
+ 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;
740
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
741
+ }
742
+ __name(_ts_decorate2, "_ts_decorate");
743
+ function _ts_metadata2(k, v) {
744
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
745
+ }
746
+ __name(_ts_metadata2, "_ts_metadata");
747
+ var logger = new Logger("ErrorsHandling");
748
+ function extractErrorResponse(error, core, guild, user, metadata) {
749
+ const uuid = crypto2.randomUUID();
750
+ if (error instanceof CustomError) {
751
+ if (error instanceof DatabaseError) {
752
+ core.effects.emit("unknownException", {
671
753
  uuid,
672
- response: error.response
673
- };
754
+ error,
755
+ guild,
756
+ user,
757
+ metadata
758
+ });
759
+ logger.error(`DatabaseError: ${error.uuid}`);
760
+ } else if (error.emit) {
761
+ logger.error(`${error.name}: ${error.message}`, error);
674
762
  }
675
- const showStack = core.config.bot.errorStack;
676
- if (showStack) this.logger.error(uuid, error);
677
- else this.logger.error(`${uuid} | ${error.message}`);
678
- core.effects.emit("unknownException", {
679
- uuid,
680
- error,
681
- guild,
682
- user
683
- });
684
763
  return {
685
764
  uuid,
686
- response: new GenericError(uuid).response
765
+ response: error.response
687
766
  };
688
767
  }
689
- };
690
- var GenericError = class GenericError2 extends CustomError {
768
+ const showStack = core.config.bot.errorStack;
769
+ if (showStack) logger.error(uuid, error);
770
+ else logger.error(`${uuid} | ${error.message}`);
771
+ core.effects.emit("unknownException", {
772
+ uuid,
773
+ error,
774
+ guild,
775
+ user,
776
+ metadata
777
+ });
778
+ return {
779
+ uuid,
780
+ response: new GenericError(uuid).response
781
+ };
782
+ }
783
+ __name(extractErrorResponse, "extractErrorResponse");
784
+ var GenericError = class extends CustomError {
691
785
  static {
692
786
  __name(this, "GenericError");
693
787
  }
694
788
  uuid;
789
+ developerUsername = "the developer";
695
790
  constructor(uuid) {
696
791
  super("An unknown error occurred"), this.uuid = uuid;
697
- this.response.setTitle("Error").setDescription(`An unknown error occurred. Please reach out to the developer with a way to reproduce the error and the following:
792
+ this.response.setTitle("Error").setDescription(`An unknown error occurred. Please reach out to ${this.developerUsername} with a way to reproduce the error and the following:
698
793
  ### UUID: \`${this.uuid}\``);
699
794
  }
700
795
  };
796
+ _ts_decorate2([
797
+ Envapt("DEVELOPER_DISCORD_USERNAME"),
798
+ _ts_metadata2("design:type", String)
799
+ ], GenericError.prototype, "developerUsername", void 0);
701
800
 
702
801
  // src/bot/decorators/Catchable.ts
703
802
  function Catchable(options) {
@@ -714,7 +813,7 @@ function Catchable(options) {
714
813
  if (!(error instanceof Error)) throw error;
715
814
  this.setErrored();
716
815
  if (log) console.error(error);
717
- const { response } = ErrorHandlingUtils.extractErrorResponse(error, this.core, interaction.guild, interaction.user);
816
+ const { response } = extractErrorResponse(error, this.core, interaction.guild, interaction.user, interaction);
718
817
  const res = {
719
818
  embeds: [
720
819
  response
@@ -748,17 +847,17 @@ function Catchable(options) {
748
847
  __name(Catchable, "Catchable");
749
848
 
750
849
  // src/bot/defaults/UnhandledEvent.ts
751
- function _ts_decorate2(decorators, target, key, desc) {
850
+ function _ts_decorate3(decorators, target, key, desc) {
752
851
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
753
852
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
754
853
  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;
755
854
  return c > 3 && r && Object.defineProperty(target, key, r), r;
756
855
  }
757
- __name(_ts_decorate2, "_ts_decorate");
758
- function _ts_metadata2(k, v) {
856
+ __name(_ts_decorate3, "_ts_decorate");
857
+ function _ts_metadata3(k, v) {
759
858
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
760
859
  }
761
- __name(_ts_metadata2, "_ts_metadata");
860
+ __name(_ts_metadata3, "_ts_metadata");
762
861
  var UnhandledEvent = class extends InteractionHandler {
763
862
  static {
764
863
  __name(this, "UnhandledEvent");
@@ -770,12 +869,32 @@ var UnhandledEvent = class extends InteractionHandler {
770
869
  });
771
870
  }
772
871
  };
773
- _ts_decorate2([
872
+ _ts_decorate3([
774
873
  Catchable(),
775
- _ts_metadata2("design:type", Function),
776
- _ts_metadata2("design:paramtypes", []),
777
- _ts_metadata2("design:returntype", Promise)
874
+ _ts_metadata3("design:type", Function),
875
+ _ts_metadata3("design:paramtypes", []),
876
+ _ts_metadata3("design:returntype", Promise)
778
877
  ], UnhandledEvent.prototype, "execute", null);
878
+ function buildSlashRoute(arg1, arg2, arg3) {
879
+ let command;
880
+ let sub;
881
+ let group;
882
+ if (typeof arg1 === "string") {
883
+ command = arg1;
884
+ sub = arg2;
885
+ group = arg3;
886
+ } else if (arg1 instanceof ChatInputCommandInteraction || arg1 instanceof AutocompleteInteraction) {
887
+ command = arg1.commandName;
888
+ group = arg1.options.getSubcommandGroup(false) ?? void 0;
889
+ sub = arg1.options.getSubcommand(false) ?? void 0;
890
+ } else {
891
+ throw new TypeError("Invalid argument passed to buildSlashRoute");
892
+ }
893
+ if (sub && group) return `${command}/${group}/${sub}`;
894
+ if (sub) return `${command}/${sub}`;
895
+ return command;
896
+ }
897
+ __name(buildSlashRoute, "buildSlashRoute");
779
898
 
780
899
  // src/bot/controllers/InteractionController.ts
781
900
  var InteractionController = class {
@@ -809,40 +928,66 @@ var InteractionController = class {
809
928
  if (this.isInitialized) return;
810
929
  this.isInitialized = true;
811
930
  const handlersDir = this.core.config.bot.interactions.path;
812
- this.logger.info(chalk2.bold(handlersDir));
931
+ this.logger.info(chalk4.bold(handlersDir));
932
+ const middlewareDir = this.core.config.bot.interactions.middlewares;
933
+ if (middlewareDir) {
934
+ this.logger.info(`${chalk4.bold(middlewareDir)} ${chalk4.gray("(middlewares)")}`);
935
+ await this.loadMiddlewares(middlewareDir);
936
+ }
813
937
  await this.loadHandlers(handlersDir);
814
938
  this.attachToClient();
815
- this.logger.info(`${chalk2.bold.green("Loaded interaction handlers:")}`);
816
- this.logger.info(`\u2192 ${chalk2.magenta.bold(this.slashMap.size)} slash commands`);
817
- this.logger.info(`\u2192 ${chalk2.magenta.bold(this.buttonMap.size)} buttons`);
818
- this.logger.info(`\u2192 ${chalk2.magenta.bold(this.modalMap.size)} modals`);
819
- this.logger.info(`\u2192 ${chalk2.magenta.bold(this.stringSelectMap.size)} string selects`);
820
- this.logger.info(`\u2192 ${chalk2.magenta.bold(this.userSelectMap.size)} user selects`);
821
- this.logger.info(`\u2192 ${chalk2.magenta.bold(this.roleSelectMap.size)} role selects`);
822
- this.logger.info(`\u2192 ${chalk2.magenta.bold(this.channelSelectMap.size)} channel selects`);
823
- this.logger.info(`\u2192 ${chalk2.magenta.bold(this.mentionableSelectMap.size)} mentionable selects`);
824
- this.logger.info(`\u2192 ${chalk2.magenta.bold(this.messageContextMenuMap.size)} message context menus`);
825
- this.logger.info(`\u2192 ${chalk2.magenta.bold(this.userContextMenuMap.size)} user context menus`);
826
- this.logger.info(`\u2192 ${chalk2.magenta.bold(this.autocompleteMap.size)} autocomplete`);
939
+ this.logger.info(`${chalk4.bold.green("Loaded interaction handlers:")}`);
940
+ this.logger.info(`\u2192 ${chalk4.magenta.bold(this.middlewares.length)} middlewares`);
941
+ this.logger.info(`\u2192 ${chalk4.magenta.bold(this.slashMap.size)} slash commands`);
942
+ this.logger.info(`\u2192 ${chalk4.magenta.bold(this.buttonMap.size)} buttons`);
943
+ this.logger.info(`\u2192 ${chalk4.magenta.bold(this.modalMap.size)} modals`);
944
+ this.logger.info(`\u2192 ${chalk4.magenta.bold(this.stringSelectMap.size)} string selects`);
945
+ this.logger.info(`\u2192 ${chalk4.magenta.bold(this.userSelectMap.size)} user selects`);
946
+ this.logger.info(`\u2192 ${chalk4.magenta.bold(this.roleSelectMap.size)} role selects`);
947
+ this.logger.info(`\u2192 ${chalk4.magenta.bold(this.channelSelectMap.size)} channel selects`);
948
+ this.logger.info(`\u2192 ${chalk4.magenta.bold(this.mentionableSelectMap.size)} mentionable selects`);
949
+ this.logger.info(`\u2192 ${chalk4.magenta.bold(this.messageContextMenuMap.size)} message context menus`);
950
+ this.logger.info(`\u2192 ${chalk4.magenta.bold(this.userContextMenuMap.size)} user context menus`);
951
+ this.logger.info(`\u2192 ${chalk4.magenta.bold(this.autocompleteMap.size)} autocomplete`);
827
952
  }
828
953
  async loadHandlers(dir) {
829
954
  await traverseDirectory(dir, (_fullPath, relativePath, imported) => {
830
955
  for (const val of Object.values(imported)) {
831
- if (this.isHandlerClass(val)) {
832
- this.registerHandler(val);
833
- this.logger.info(`${chalk2.italic("Registered")} ${chalk2.bold.yellow(val.name)} from ${chalk2.gray(relativePath)}`);
834
- }
956
+ if (!this.isHandlerClass(val)) continue;
957
+ this.registerHandler(val);
958
+ this.logger.info(`${chalk4.italic("Registered")} ${chalk4.bold.yellow(val.name)} from ${chalk4.gray(relativePath)}`);
835
959
  }
836
960
  }, this.logger);
837
961
  }
962
+ async loadMiddlewares(dir) {
963
+ await traverseDirectory(dir, (_fullPath, relativePath, imported) => {
964
+ for (const val of Object.values(imported)) {
965
+ if (!this.isMiddlewareClass(val)) continue;
966
+ const metadata = Reflect.getMetadata(MiddlewareMetadataKey, val);
967
+ if (metadata?.type !== MiddlewareType.Interaction) continue;
968
+ this.registerMiddleware(val, metadata, relativePath);
969
+ }
970
+ }, this.logger);
971
+ }
972
+ registerMiddleware(middlewareCtor, metadata, relativePath) {
973
+ const alreadyRegistered = this.middlewares.some((entry) => entry.ctor === middlewareCtor);
974
+ if (alreadyRegistered) return;
975
+ this.middlewares.push({
976
+ ctor: middlewareCtor,
977
+ priority: metadata.priority
978
+ });
979
+ this.middlewares.sort((a, b) => a.priority - b.priority);
980
+ this.logger.info(`${chalk4.italic("Registered middleware")} ${chalk4.bold.yellow(middlewareCtor.name)} ${chalk4.gray(`(priority ${metadata.priority})`)} from ${chalk4.gray(relativePath)}`);
981
+ }
838
982
  isHandlerClass(obj) {
839
983
  if (typeof obj !== "function") return false;
840
984
  return obj.prototype instanceof InteractionHandler && Reflect.hasMetadata(InteractionMetadataKey, obj) || obj.prototype instanceof AutocompleteHandler && Reflect.hasMetadata(InteractionMetadataKey, obj);
841
985
  }
986
+ isMiddlewareClass(obj) {
987
+ if (typeof obj !== "function") return false;
988
+ return obj.prototype instanceof InteractionMiddleware && Reflect.hasMetadata(MiddlewareMetadataKey, obj);
989
+ }
842
990
  registerHandler(handlerClass) {
843
- const areRoutes = /* @__PURE__ */ __name((routes) => {
844
- return Array.isArray(routes) && routes.every((r) => typeof r === "string");
845
- }, "areRoutes");
846
991
  const routeTypes = [
847
992
  [
848
993
  InteractionRoutes.Slash,
@@ -899,7 +1044,7 @@ var InteractionController = class {
899
1044
  attachToClient() {
900
1045
  this.core.bot.client.on(Events.InteractionCreate, (interaction) => {
901
1046
  this.handleInteraction(interaction).catch((err) => {
902
- this.logger.error(`[${chalk2.bold.red("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
1047
+ this.logger.error(`[${chalk4.bold.red("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
903
1048
  });
904
1049
  });
905
1050
  }
@@ -921,17 +1066,19 @@ var InteractionController = class {
921
1066
  async processInteraction(interaction, extractKey, getHandler, args) {
922
1067
  const key = extractKey(interaction);
923
1068
  if (this.keysToIgnore.has(key)) return;
924
- for (const MiddlewareCtor of this.middlewares) {
925
- const middleware = new MiddlewareCtor(interaction, this.core, args);
1069
+ for (const { ctor } of this.middlewares) {
1070
+ const middleware = new ctor(interaction, this.core, args);
1071
+ if (middleware.hasChecks()) await middleware.runChecks();
1072
+ if (middleware.shouldBreak() || middleware.hasErrors()) return;
926
1073
  await middleware.execute();
927
- if (middleware.hasErrors()) return;
1074
+ if (middleware.shouldBreak() || middleware.hasErrors()) return;
928
1075
  }
929
1076
  let HandlerCtor = getHandler(key);
930
1077
  if (!HandlerCtor) {
931
- this.logger.warn(`No handler found for key ${chalk2.bold.cyan(key)}. Falling back to UnhandledEvent.`);
1078
+ this.logger.warn(`No handler found for key ${chalk4.bold.cyan(key)}. Falling back to UnhandledEvent.`);
932
1079
  HandlerCtor = UnhandledEvent;
933
1080
  }
934
- this.logger.debug(`Processing ${chalk2.bold.green(key)} with ${chalk2.gray(HandlerCtor.name)}`);
1081
+ this.logger.debug(`Processing ${chalk4.bold.green(key)} with ${chalk4.gray(HandlerCtor.name)}`);
935
1082
  const handler = new HandlerCtor(interaction, this.core, args);
936
1083
  if (handler.hasChecks()) await handler.runChecks();
937
1084
  if (handler.shouldBreak()) return;
@@ -978,7 +1125,7 @@ var InteractionController = class {
978
1125
  }
979
1126
  }
980
1127
  async handleSlashCommand(interaction) {
981
- const route = this.buildSlashRoute(interaction);
1128
+ const route = buildSlashRoute(interaction);
982
1129
  await this.processInteraction(interaction, () => route, (key) => this.slashMap.get(key));
983
1130
  }
984
1131
  async handleButton(interaction) {
@@ -1009,26 +1156,11 @@ var InteractionController = class {
1009
1156
  await this.processInteraction(interaction, () => interaction.commandName, (key) => this.userContextMenuMap.get(key));
1010
1157
  }
1011
1158
  async handleAutocomplete(interaction) {
1012
- const route = this.buildSlashRoute(interaction);
1159
+ const route = buildSlashRoute(interaction);
1013
1160
  const focused = interaction.options.getFocused(true);
1014
1161
  const autocompleteKey = `${route}:${focused.name}`;
1015
1162
  await this.processInteraction(interaction, () => autocompleteKey, (key) => this.autocompleteMap.get(key));
1016
1163
  }
1017
- // Build the route from commandName, subcommandGroup, subcommand
1018
- buildSlashRoute(interaction) {
1019
- const command = interaction.commandName;
1020
- const group = interaction.options.getSubcommandGroup(false);
1021
- const sub = interaction.options.getSubcommand(false);
1022
- let route = command;
1023
- if (group && sub) {
1024
- route = `${route}/${group}/${sub}`;
1025
- } else if (group) {
1026
- route = `${route}/${group}`;
1027
- } else if (sub) {
1028
- route = `${route}/${sub}`;
1029
- }
1030
- return route;
1031
- }
1032
1164
  };
1033
1165
  var EmojiInjector = class {
1034
1166
  static {
@@ -1041,7 +1173,7 @@ var EmojiInjector = class {
1041
1173
  }
1042
1174
  async init() {
1043
1175
  if (!this.core.config.bot.emojis || Object.keys(this.core.config.bot.emojis).length === 0) {
1044
- this.logger.info(`${chalk2.bold.green("Loaded")}: ${chalk2.magenta.bold("0")} emojis`);
1176
+ this.logger.info(`${chalk4.bold.green("Loaded")}: ${chalk4.magenta.bold("0")} emojis`);
1045
1177
  return;
1046
1178
  }
1047
1179
  const configEmojis = this.core.config.bot.emojis;
@@ -1052,25 +1184,25 @@ var EmojiInjector = class {
1052
1184
  if (emoji) {
1053
1185
  configEmojis[key] = `<${emoji.identifier}>`;
1054
1186
  foundCount++;
1055
- this.logger.debug(`${chalk2.bold.green("Found")}: ${chalk2.magenta.bold(emojiName)} (${emoji.id})`);
1187
+ this.logger.debug(`${chalk4.bold.green("Found")}: ${chalk4.magenta.bold(emojiName)} (${emoji.id})`);
1056
1188
  }
1057
1189
  });
1058
- this.logger.info(`${chalk2.bold.green("Loaded")}: ${chalk2.magenta.bold(foundCount)} emojis`);
1190
+ this.logger.info(`${chalk4.bold.green("Loaded")}: ${chalk4.magenta.bold(foundCount)} emojis`);
1059
1191
  }
1060
1192
  };
1061
1193
 
1062
1194
  // src/bot/Bot.ts
1063
- function _ts_decorate3(decorators, target, key, desc) {
1195
+ function _ts_decorate4(decorators, target, key, desc) {
1064
1196
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1065
1197
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1066
1198
  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;
1067
1199
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1068
1200
  }
1069
- __name(_ts_decorate3, "_ts_decorate");
1070
- function _ts_metadata3(k, v) {
1201
+ __name(_ts_decorate4, "_ts_decorate");
1202
+ function _ts_metadata4(k, v) {
1071
1203
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1072
1204
  }
1073
- __name(_ts_metadata3, "_ts_metadata");
1205
+ __name(_ts_metadata4, "_ts_metadata");
1074
1206
  var Bot = class extends Plugin {
1075
1207
  static {
1076
1208
  __name(this, "Bot");
@@ -1083,10 +1215,6 @@ var Bot = class extends Plugin {
1083
1215
  events;
1084
1216
  commands;
1085
1217
  emojiInjector;
1086
- /**
1087
- * @param core - Seedcord core instance
1088
- * @internal
1089
- */
1090
1218
  constructor(core) {
1091
1219
  super(core), this.core = core;
1092
1220
  this._client = new Client(core.config.bot.clientOptions);
@@ -1097,9 +1225,9 @@ var Bot = class extends Plugin {
1097
1225
  this.core.shutdown.addTask(ShutdownPhase.DiscordCleanup, "stop-bot", async () => await this.stop());
1098
1226
  }
1099
1227
  /**
1100
- * Initializes Discord client and all controllers
1101
- * @internal
1102
- */
1228
+ * Initializes Discord client and all controllers
1229
+ * @internal
1230
+ */
1103
1231
  async init() {
1104
1232
  if (this.isInitialized) {
1105
1233
  return;
@@ -1113,40 +1241,40 @@ var Bot = class extends Plugin {
1113
1241
  await this.emojiInjector.init();
1114
1242
  }
1115
1243
  /**
1116
- * Stops the bot and cleans up connections
1117
- * @internal
1118
- */
1244
+ * Stops the bot and cleans up connections
1245
+ * @internal
1246
+ */
1119
1247
  async stop() {
1120
1248
  this._client.removeAllListeners();
1121
1249
  await this.logout();
1122
1250
  }
1123
1251
  /**
1124
- * Logs the bot into Discord using the configured token
1125
- */
1252
+ * Logs the bot into Discord using the configured token
1253
+ */
1126
1254
  async login() {
1127
1255
  await this._client.login(this.botToken);
1128
- this.logger.info(`Logged in as ${chalk2.bold.magenta(this._client.user?.username)}!`);
1256
+ this.logger.info(`Logged in as ${chalk4.bold.magenta(this._client.user?.username)}!`);
1129
1257
  return this;
1130
1258
  }
1131
1259
  /**
1132
- * Logs out and destroys the Discord client connection
1133
- */
1260
+ * Logs out and destroys the Discord client connection
1261
+ */
1134
1262
  async logout() {
1135
1263
  await this._client.destroy();
1136
- this.logger.info(chalk2.bold.red("Logged out of Discord!"));
1264
+ this.logger.info(chalk4.bold.red("Logged out of Discord!"));
1137
1265
  }
1138
1266
  get client() {
1139
1267
  return this._client;
1140
1268
  }
1141
1269
  };
1142
- _ts_decorate3([
1270
+ _ts_decorate4([
1143
1271
  Envapt("DISCORD_BOT_TOKEN", {
1144
1272
  converter(raw, _fallback) {
1145
1273
  if (typeof raw !== "string") throw new Error("Missing DISCORD_BOT_TOKEN");
1146
1274
  return raw;
1147
1275
  }
1148
1276
  }),
1149
- _ts_metadata3("design:type", String)
1277
+ _ts_metadata4("design:type", String)
1150
1278
  ], Bot.prototype, "botToken", void 0);
1151
1279
 
1152
1280
  // src/effects/decorators/RegisterEffect.ts
@@ -1166,11 +1294,11 @@ var EffectsHandler = class {
1166
1294
  data;
1167
1295
  core;
1168
1296
  /**
1169
- * Creates a new effects handler instance.
1170
- *
1171
- * @param data - The effect event data
1172
- * @param core - The core framework instance
1173
- */
1297
+ * Creates a new effects handler instance.
1298
+ *
1299
+ * @param data - The effect event data
1300
+ * @param core - The core framework instance
1301
+ */
1174
1302
  constructor(data, core) {
1175
1303
  this.data = data;
1176
1304
  this.core = core;
@@ -1190,35 +1318,55 @@ var WebhookLog = class extends EffectsHandler {
1190
1318
  };
1191
1319
 
1192
1320
  // src/effects/default/UnknownException.ts
1193
- function _ts_decorate4(decorators, target, key, desc) {
1321
+ function _ts_decorate5(decorators, target, key, desc) {
1194
1322
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1195
1323
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1196
1324
  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;
1197
1325
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1198
1326
  }
1199
- __name(_ts_decorate4, "_ts_decorate");
1200
- function _ts_metadata4(k, v) {
1327
+ __name(_ts_decorate5, "_ts_decorate");
1328
+ function _ts_metadata5(k, v) {
1201
1329
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1202
1330
  }
1203
- __name(_ts_metadata4, "_ts_metadata");
1331
+ __name(_ts_metadata5, "_ts_metadata");
1204
1332
  var UnknownException = class _UnknownException extends WebhookLog {
1205
1333
  static {
1206
1334
  __name(this, "UnknownException");
1207
1335
  }
1336
+ static logger = new Logger("Effect: UnknownException");
1208
1337
  webhook = new WebhookClient({
1209
1338
  url: _UnknownException.unknownExceptionWebhookUrl
1210
1339
  });
1211
1340
  async execute() {
1212
- await this.webhook.send({
1213
- username: "Unknown Exception",
1214
- avatarURL: "https://cdn.discordapp.com/attachments/1351446034827579466/1351446912947191830/warning-2.png",
1215
- embeds: [
1216
- new UnhandledErrorEmbed(this.data).component
1217
- ]
1341
+ const metadataFile = this.prepareMetadataFile();
1342
+ try {
1343
+ await this.webhook.send({
1344
+ flags: "IsComponentsV2",
1345
+ withComponents: true,
1346
+ username: "Unknown Exception",
1347
+ avatarURL: "https://cdn.discordapp.com/attachments/1351446034827579466/1351446912947191830/warning-2.png",
1348
+ components: [
1349
+ new UnhandledErrorContainer(this.data).component
1350
+ ],
1351
+ files: metadataFile ? [
1352
+ metadataFile
1353
+ ] : []
1354
+ });
1355
+ } catch (error) {
1356
+ _UnknownException.logger.error("Failed to send unknown exception webhook", error);
1357
+ }
1358
+ }
1359
+ prepareMetadataFile() {
1360
+ const { metadata } = this.data;
1361
+ if (!metadata) return null;
1362
+ const content = filterCirculars(metadata);
1363
+ return new AttachmentBuilder(Buffer.from(JSON.stringify(content, void 0, 2), "utf-8"), {
1364
+ name: "metadata.json",
1365
+ description: "Metadata associated with the error"
1218
1366
  });
1219
1367
  }
1220
1368
  };
1221
- _ts_decorate4([
1369
+ _ts_decorate5([
1222
1370
  Envapt("UNKNOWN_EXCEPTION_WEBHOOK_URL", {
1223
1371
  converter(raw, _fallback) {
1224
1372
  if (!raw) throw new Error("Missing UNKNOWN_EXCEPTION_WEBHOOK_URL");
@@ -1226,27 +1374,38 @@ _ts_decorate4([
1226
1374
  return raw;
1227
1375
  }
1228
1376
  }),
1229
- _ts_metadata4("design:type", String)
1377
+ _ts_metadata5("design:type", String)
1230
1378
  ], UnknownException, "unknownExceptionWebhookUrl", void 0);
1231
- UnknownException = _ts_decorate4([
1379
+ UnknownException = _ts_decorate5([
1232
1380
  RegisterEffect("unknownException")
1233
1381
  ], UnknownException);
1234
- var UnhandledErrorEmbed = class UnhandledErrorEmbed2 extends BuilderComponent {
1382
+ var DefaultSeparator = class DefaultSeparator2 extends BuilderComponent {
1235
1383
  static {
1236
- __name(this, "UnhandledErrorEmbed");
1384
+ __name(this, "DefaultSeparator");
1385
+ }
1386
+ constructor() {
1387
+ super("separator");
1388
+ this.instance.setSpacing(SeparatorSpacingSize.Small).setDivider(true);
1389
+ }
1390
+ };
1391
+ var UnhandledErrorContainer = class UnhandledErrorContainer2 extends BuilderComponent {
1392
+ static {
1393
+ __name(this, "UnhandledErrorContainer");
1237
1394
  }
1238
1395
  constructor(data) {
1239
- super("embed");
1240
- const { uuid, error, guild, user } = data;
1241
- this.instance.setTitle(`An unknown exception was thrown`).setColor("#ef4860").setDescription(`**Guild ID:** \`${guild?.id ?? "Not used in a guild"}\`
1396
+ super("container");
1397
+ const { uuid, error, guild, user, metadata } = data;
1398
+ this.instance.addTextDisplayComponents((text) => text.setContent(`### An unknown exception was thrown
1399
+ **Guild ID:** \`${guild?.id ?? "Not used in a guild"}\`
1242
1400
  **Guild Name:** ${guild?.name ?? "Not used in a guild"}
1243
1401
  **User ID:** \`${user?.id ?? "Missing user info"}\`
1244
1402
  **Username:** ${user?.username ?? "Missing user info"}
1245
- ### UUID: \`${uuid}\`
1246
- \`\`\`${error.stack}\`\`\``);
1247
- this.setTimestampsIfAvailable(error);
1403
+ `)).addSeparatorComponents(new DefaultSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`
1404
+ \`\`\`${error.stack}\`\`\``));
1405
+ this.addTimestampsIfAvailable(error);
1406
+ this.addMetadataIfAvailable(metadata);
1248
1407
  }
1249
- setTimestampsIfAvailable(error) {
1408
+ addTimestampsIfAvailable(error) {
1250
1409
  if (!(error instanceof DiscordAPIError)) return;
1251
1410
  const now = Date.now();
1252
1411
  const snowflake = error.url.match(/\/interactions\/(\d+)\//)?.[1];
@@ -1255,15 +1414,14 @@ var UnhandledErrorEmbed = class UnhandledErrorEmbed2 extends BuilderComponent {
1255
1414
  const diff = now - interactionTs;
1256
1415
  const seconds = Math.floor(diff / 1e3);
1257
1416
  const millis = diff % 1e3;
1258
- this.instance.addFields([
1259
- {
1260
- name: "Timestamps",
1261
- value: `- **\`Interaction sent\` :** ${new Date(interactionTs).toISOString()} (${interactionTs})
1417
+ this.instance.addSeparatorComponents(new DefaultSeparator().component).addTextDisplayComponents((text) => text.setContent(`### Timestamps
1418
+ - **\`Interaction sent\` :** ${new Date(interactionTs).toISOString()} (${interactionTs})
1262
1419
  - **\`Error logged \` :** ${new Date(now).toISOString()} (${now})
1263
- - **\`Offset \` :** ${seconds}s ${millis}ms`,
1264
- inline: true
1265
- }
1266
- ]);
1420
+ - **\`Offset \` :** ${seconds}s ${millis}ms`));
1421
+ }
1422
+ addMetadataIfAvailable(metadata) {
1423
+ if (!metadata) return;
1424
+ this.instance.addSeparatorComponents(new DefaultSeparator().component).addTextDisplayComponents((text) => text.setContent("### Metadata")).addFileComponents((file) => file.setURL("attachment://metadata.json"));
1267
1425
  }
1268
1426
  };
1269
1427
  var EffectsEmitter = class {
@@ -1272,37 +1430,37 @@ var EffectsEmitter = class {
1272
1430
  }
1273
1431
  emitter = new EventEmitter();
1274
1432
  /**
1275
- * Registers a listener for the specified side effect.
1276
- *
1277
- * @typeParam KeyOfEffects - The side effect name type
1278
- * @param event - The side effect name to listen for
1279
- * @param listener - Function to call when the event is emitted
1280
- * @returns This EffectsEmitter instance for chaining
1281
- */
1433
+ * Registers a listener for the specified side effect.
1434
+ *
1435
+ * @typeParam KeyOfEffects - The side effect name type
1436
+ * @param event - The side effect name to listen for
1437
+ * @param listener - Function to call when the event is emitted
1438
+ * @returns This EffectsEmitter instance for chaining
1439
+ */
1282
1440
  on(event, listener) {
1283
1441
  this.emitter.on(event, listener);
1284
1442
  return this;
1285
1443
  }
1286
1444
  /**
1287
- * Registers a one-time listener for the specified side effect.
1288
- *
1289
- * @typeParam KeyOfEffects - The side effect name type
1290
- * @param event - The side effect name to listen for once
1291
- * @param listener - Function to call when the event is emitted
1292
- * @returns This EffectsEmitter instance for chaining
1293
- */
1445
+ * Registers a one-time listener for the specified side effect.
1446
+ *
1447
+ * @typeParam KeyOfEffects - The side effect name type
1448
+ * @param event - The side effect name to listen for once
1449
+ * @param listener - Function to call when the event is emitted
1450
+ * @returns This EffectsEmitter instance for chaining
1451
+ */
1294
1452
  once(event, listener) {
1295
1453
  this.emitter.once(event, listener);
1296
1454
  return this;
1297
1455
  }
1298
1456
  /**
1299
- * Emits a side effect with the provided data.
1300
- *
1301
- * @typeParam KeyOfEffects - The side effect name type
1302
- * @param event - The side effect name to emit
1303
- * @param data - The data to pass to registered listeners
1304
- * @returns True if the event had listeners, false otherwise
1305
- */
1457
+ * Emits a side effect with the provided data.
1458
+ *
1459
+ * @typeParam KeyOfEffects - The side effect name type
1460
+ * @param event - The side effect name to emit
1461
+ * @param data - The data to pass to registered listeners
1462
+ * @returns True if the event had listeners, false otherwise
1463
+ */
1306
1464
  emit(event, data) {
1307
1465
  return this.emitter.emit(event, data);
1308
1466
  }
@@ -1325,12 +1483,12 @@ var EffectsRegistry = class extends Plugin {
1325
1483
  if (this.isInitialized) return;
1326
1484
  this.isInitialized = true;
1327
1485
  const effectsDir = this.core.config.effects.path;
1328
- this.logger.info(chalk2.bold(effectsDir));
1486
+ this.logger.info(chalk4.bold(effectsDir));
1329
1487
  this.registerEffect("unknownException", UnknownException);
1330
1488
  await this.loadEffects(effectsDir);
1331
1489
  this.attachEffects();
1332
1490
  const totalEffects = Array.from(this.effectsMap.values()).reduce((acc, handlers) => acc + handlers.length, 0);
1333
- this.logger.info(`${chalk2.bold.green("Loaded")}: ${chalk2.bold.magenta(totalEffects)} side effects`);
1491
+ this.logger.info(`${chalk4.bold.green("Loaded")}: ${chalk4.bold.magenta(totalEffects)} side effects`);
1334
1492
  }
1335
1493
  async loadEffects(dir) {
1336
1494
  await traverseDirectory(dir, (_fullPath, relativePath, imported) => {
@@ -1340,7 +1498,7 @@ var EffectsRegistry = class extends Plugin {
1340
1498
  const effectName = Reflect.getMetadata(EffectMetadataKey, val);
1341
1499
  if (effectName) {
1342
1500
  this.registerEffect(effectName, val);
1343
- this.logger.info(`${chalk2.italic("Registered")} ${chalk2.bold.yellow(val.name)} from ${chalk2.gray(relativePath)}`);
1501
+ this.logger.info(`${chalk4.italic("Registered")} ${chalk4.bold.yellow(val.name)} from ${chalk4.gray(relativePath)}`);
1344
1502
  }
1345
1503
  }
1346
1504
  }
@@ -1395,11 +1553,11 @@ var Seedcord = class _Seedcord extends Pluggable {
1395
1553
  /** @see {@link HealthCheck} */
1396
1554
  healthCheck;
1397
1555
  /**
1398
- * Creates a new Seedcord instance
1399
- *
1400
- * @param config - Bot configuration including paths and Discord client options
1401
- * @throws An {@link Error} When attempting to create multiple instances (singleton)
1402
- */
1556
+ * Creates a new Seedcord instance
1557
+ *
1558
+ * @param config - Bot configuration including paths and Discord client options
1559
+ * @throws An {@link Error} When attempting to create multiple instances (singleton)
1560
+ */
1403
1561
  constructor(config) {
1404
1562
  const shutdown = new CoordinatedShutdown();
1405
1563
  const startup = new CoordinatedStartup();
@@ -1416,31 +1574,31 @@ var Seedcord = class _Seedcord extends Pluggable {
1416
1574
  this.registerStartupTasks();
1417
1575
  }
1418
1576
  /**
1419
- * Registers default startup tasks
1420
- * @internal
1421
- */
1577
+ * Registers default startup tasks
1578
+ * @internal
1579
+ */
1422
1580
  registerStartupTasks() {
1423
1581
  this.startup.addTask(StartupPhase.Configuration, "Effect Initialization", async () => {
1424
- this.effects.logger.info(chalk2.bold("Initializing"));
1582
+ this.effects.logger.info(chalk4.bold("Initializing"));
1425
1583
  await this.effects.init();
1426
- this.effects.logger.info(chalk2.bold("Initialized"));
1584
+ this.effects.logger.info(chalk4.bold("Initialized"));
1427
1585
  });
1428
1586
  this.startup.addTask(StartupPhase.Instantiation, "Bot Initialization", async () => {
1429
- this.bot.logger.info(chalk2.bold("Initializing"));
1587
+ this.bot.logger.info(chalk4.bold("Initializing"));
1430
1588
  await this.bot.init();
1431
- this.bot.logger.info(chalk2.bold("Initialized"));
1589
+ this.bot.logger.info(chalk4.bold("Initialized"));
1432
1590
  });
1433
1591
  this.startup.addTask(StartupPhase.Ready, "Health Check", async () => {
1434
- this.healthCheck.logger.info(chalk2.bold("Initializing"));
1592
+ this.healthCheck.logger.info(chalk4.bold("Initializing"));
1435
1593
  await this.healthCheck.init();
1436
- this.healthCheck.logger.info(chalk2.bold("Initialized"));
1594
+ this.healthCheck.logger.info(chalk4.bold("Initialized"));
1437
1595
  });
1438
1596
  }
1439
1597
  /**
1440
- * Starts the bot and runs all initialization tasks
1441
- *
1442
- * @returns This Seedcord instance when fully initialized
1443
- */
1598
+ * Starts the bot and runs all initialization tasks
1599
+ *
1600
+ * @returns This Seedcord instance when fully initialized
1601
+ */
1444
1602
  async start() {
1445
1603
  await super.init();
1446
1604
  return this;
@@ -1470,7 +1628,7 @@ function EventCatchable(log) {
1470
1628
  this.getEvent()
1471
1629
  ];
1472
1630
  const msg = eventArgs.find((x) => x instanceof Message);
1473
- const { response } = ErrorHandlingUtils.extractErrorResponse(err, this.core, msg?.guild ?? null, msg?.author ?? null);
1631
+ const { response } = extractErrorResponse(err, this.core, msg?.guild ?? null, msg?.author ?? null, eventArgs);
1474
1632
  if (!msg) return;
1475
1633
  await msg.reply({
1476
1634
  embeds: [
@@ -1483,6 +1641,232 @@ function EventCatchable(log) {
1483
1641
  };
1484
1642
  }
1485
1643
  __name(EventCatchable, "EventCatchable");
1644
+
1645
+ // src/bot/errors/Channels.ts
1646
+ var ChannelNotFoundError = class extends CustomError {
1647
+ static {
1648
+ __name(this, "ChannelNotFoundError");
1649
+ }
1650
+ channelId;
1651
+ /**
1652
+ * Creates a new ChannelNotFoundError.
1653
+ *
1654
+ * @param message - The error message
1655
+ * @param channelId - The ID of the channel that could not be found
1656
+ */
1657
+ constructor(message, channelId) {
1658
+ super(message), this.channelId = channelId;
1659
+ this.response.setDescription(`Channel with ID \`${this.channelId}\` not found.`);
1660
+ }
1661
+ };
1662
+ var CannotSendEmbedsError = class extends CustomError {
1663
+ static {
1664
+ __name(this, "CannotSendEmbedsError");
1665
+ }
1666
+ channelId;
1667
+ /**
1668
+ * Creates a new CannotSendEmbedsError.
1669
+ *
1670
+ * @param message - The error message
1671
+ * @param channelId - The ID of the channel where embeds cannot be sent
1672
+ */
1673
+ constructor(message, channelId) {
1674
+ super(message), this.channelId = channelId;
1675
+ this.response.setDescription(`Cannot send embeds in <#${this.channelId}>.
1676
+
1677
+ Please ensure I have the following permissions:
1678
+ \u2022 View Channel
1679
+ \u2022 Send Messages
1680
+ \u2022 Embed Links
1681
+ \u2022 Attach Files
1682
+ \u2022 Read Message History
1683
+ \u2022 Use External Emojis
1684
+ `);
1685
+ }
1686
+ };
1687
+ var CouldNotFindChannel = class extends CustomError {
1688
+ static {
1689
+ __name(this, "CouldNotFindChannel");
1690
+ }
1691
+ channelId;
1692
+ /**
1693
+ * Creates a new CouldNotFindChannel error.
1694
+ *
1695
+ * @param message - The error message
1696
+ * @param channelId - The ID of the channel that could not be found
1697
+ */
1698
+ constructor(message, channelId) {
1699
+ super(message), this.channelId = channelId;
1700
+ this.response.setDescription(`Could not find channel with ID \`${this.channelId}\`. It could also be that the channel is not a text channel.`);
1701
+ }
1702
+ };
1703
+ var ChannelNotTextChannel = class extends CustomError {
1704
+ static {
1705
+ __name(this, "ChannelNotTextChannel");
1706
+ }
1707
+ channelId;
1708
+ /**
1709
+ * Creates a new ChannelNotTextChannel error.
1710
+ *
1711
+ * @param message - The error message
1712
+ * @param channelId - The ID of the channel that is not a text channel
1713
+ */
1714
+ constructor(message, channelId) {
1715
+ super(message), this.channelId = channelId;
1716
+ this.response.setDescription(`Channel with ID \`${this.channelId}\` is not a text channel.`);
1717
+ }
1718
+ };
1719
+ var MissingPermissions = class extends CustomError {
1720
+ static {
1721
+ __name(this, "MissingPermissions");
1722
+ }
1723
+ missingPerms;
1724
+ roleOrChannel;
1725
+ /**
1726
+ * Creates a new BotMissingPermissionsError.
1727
+ *
1728
+ * @param message - The error message
1729
+ * @param missingPerms - Array of missing permission names
1730
+ * @param roleOrChannel - The role or channel where permissions are missing
1731
+ */
1732
+ constructor(message, missingPerms, roleOrChannel) {
1733
+ super(message), this.missingPerms = missingPerms, this.roleOrChannel = roleOrChannel;
1734
+ const missing = this.missingPerms.map((perm) => `\u2022 ${perm}`).join("\n");
1735
+ const errorSubtext = this.roleOrChannel instanceof Role ? `My role, <@&${this.roleOrChannel.id}>, is missing the following permissions:` : `I am missing the following permissions in <#${this.roleOrChannel.id}>:`;
1736
+ this.response.setDescription(`${errorSubtext}
1737
+
1738
+ Please ensure I have the following missing permission(s):
1739
+ ${missing}`);
1740
+ }
1741
+ };
1742
+ var RoleHigherThanMe = class extends CustomError {
1743
+ static {
1744
+ __name(this, "RoleHigherThanMe");
1745
+ }
1746
+ role;
1747
+ botRole;
1748
+ /**
1749
+ * Creates a new RoleHigherThanMe error.
1750
+ *
1751
+ * @param message - The error message
1752
+ */
1753
+ constructor(message, role, botRole) {
1754
+ super(message), this.role = role, this.botRole = botRole;
1755
+ this.response.setDescription(`I cannot assign a role that is higher than me.
1756
+
1757
+ The role <@&${this.role.id}> is higher than my role <@&${this.botRole.id}> in the hierarchy.`);
1758
+ }
1759
+ };
1760
+ var CannotAssignBotRole = class extends CustomError {
1761
+ static {
1762
+ __name(this, "CannotAssignBotRole");
1763
+ }
1764
+ /**
1765
+ * Creates a new CannotAssignBotRole error.
1766
+ *
1767
+ * @param message - The error message
1768
+ */
1769
+ constructor(message = "I cannot assign a managed role.") {
1770
+ super(message);
1771
+ this.response.setDescription("I cannot assign a managed role.");
1772
+ }
1773
+ };
1774
+ var RoleDoesNotExist = class extends CustomError {
1775
+ static {
1776
+ __name(this, "RoleDoesNotExist");
1777
+ }
1778
+ roleId;
1779
+ /**
1780
+ * Creates a new RoleDoesNotExist error.
1781
+ *
1782
+ * @param message - The error message
1783
+ * @param roleId - The ID of the role that doesn't exist
1784
+ */
1785
+ constructor(message, roleId) {
1786
+ super(message), this.roleId = roleId;
1787
+ this.response.setDescription(`The role with ID \`${this.roleId}\` does not exist.`);
1788
+ }
1789
+ };
1790
+ var HasDangerousPermissions = class extends CustomError {
1791
+ static {
1792
+ __name(this, "HasDangerousPermissions");
1793
+ }
1794
+ role;
1795
+ dangerousPerms;
1796
+ /**
1797
+ * Creates a new HasDangerousPermissions error.
1798
+ *
1799
+ * @param message - The error message
1800
+ * @param role - The role with dangerous permissions
1801
+ * @param dangerousPerms - Array of dangerous permission names
1802
+ */
1803
+ constructor(message, role, dangerousPerms) {
1804
+ super(message), this.role = role, this.dangerousPerms = dangerousPerms;
1805
+ const dangerous = this.dangerousPerms.map((perm) => `\u2022 ${perm}`).join("\n");
1806
+ this.response.setDescription(`The role <@&${this.role.id}> has the following dangerous permissions:
1807
+
1808
+ Please ensure the following dangerous permission(s) are not enabled:
1809
+ ${dangerous}`);
1810
+ }
1811
+ };
1812
+
1813
+ // src/bot/errors/User.ts
1814
+ var UserNotInGuild = class extends CustomError {
1815
+ static {
1816
+ __name(this, "UserNotInGuild");
1817
+ }
1818
+ /**
1819
+ * Creates a new UserNotInGuild error.
1820
+ *
1821
+ * @param message - The error message
1822
+ */
1823
+ constructor(message = "User is not in the guild.") {
1824
+ super(message);
1825
+ this.response.setDescription("User is not in the guild.");
1826
+ }
1827
+ };
1828
+ var UserNotFound = class extends CustomError {
1829
+ static {
1830
+ __name(this, "UserNotFound");
1831
+ }
1832
+ userArg;
1833
+ /**
1834
+ * Creates a new UserNotFound error.
1835
+ *
1836
+ * @param userArg - The user argument that could not be resolved
1837
+ */
1838
+ constructor(userArg) {
1839
+ super(`User not found: ${userArg}`), this.userArg = userArg;
1840
+ this.response.setTitle("User Not Found").setDescription(`User probably doesn't exist or was deleted.
1841
+ **User Argument:** \`${this.userArg}\`
1842
+ Please check the user ID and try again. Only pass valid user IDs as the argument.`);
1843
+ }
1844
+ };
1845
+ async function fetchText(client, channelId) {
1846
+ if (channelId instanceof TextChannel) {
1847
+ return channelId;
1848
+ }
1849
+ let channel = client.channels.cache.get(channelId);
1850
+ if (!channel) {
1851
+ try {
1852
+ channel = await client.channels.fetch(channelId);
1853
+ } catch {
1854
+ throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
1855
+ }
1856
+ }
1857
+ if (channel?.isTextBased()) {
1858
+ return channel;
1859
+ }
1860
+ throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
1861
+ }
1862
+ __name(fetchText, "fetchText");
1863
+
1864
+ // src/bot/utilities/channels/sendInText.ts
1865
+ async function sendInText(client, channelId, message) {
1866
+ const channel = await fetchText(client, channelId);
1867
+ return await channel.send(message);
1868
+ }
1869
+ __name(sendInText, "sendInText");
1486
1870
  function throwCustomError(error, message, CustomError2) {
1487
1871
  const uuid = crypto.randomUUID();
1488
1872
  Logger.Error("Throwing Custom Error", error.name);
@@ -1499,6 +1883,265 @@ function throwCustomError(error, message, CustomError2) {
1499
1883
  }
1500
1884
  __name(throwCustomError, "throwCustomError");
1501
1885
 
1502
- export { AutocompleteHandler, AutocompleteRoute, BaseErrorEmbed, Bot, BuilderComponent, ButtonRoute, Catchable, Checkable, CommandMetadataKey, ContextMenuRoute, CustomError, DatabaseError, EffectMetadataKey, EffectsEmitter, EffectsHandler, EffectsRegistry, EventCatchable, EventHandler, EventMetadataKey, InteractionHandler, InteractionMetadataKey, InteractionMiddleware, InteractionRoutes, ModalComponent, ModalRoute, Pluggable, Plugin, RegisterCommand, RegisterEffect, RegisterEvent, RowComponent, Seedcord, SelectMenuRoute, SelectMenuType, SlashRoute, WebhookLog, throwCustomError };
1886
+ // src/bot/utilities/messages/attemptSendDM.ts
1887
+ async function attemptSendDM(user, content) {
1888
+ const payload = {
1889
+ ...content.content !== void 0 && {
1890
+ content: content.content
1891
+ },
1892
+ ...content.embeds !== void 0 && {
1893
+ embeds: [
1894
+ ...content.embeds
1895
+ ]
1896
+ },
1897
+ ...content.components !== void 0 && {
1898
+ components: [
1899
+ ...content.components
1900
+ ]
1901
+ }
1902
+ };
1903
+ try {
1904
+ return await user.send(payload);
1905
+ } catch {
1906
+ return null;
1907
+ }
1908
+ }
1909
+ __name(attemptSendDM, "attemptSendDM");
1910
+ var PermissionNames = new Map(Object.entries(PermissionFlagsBits).map(([key, bit]) => [
1911
+ bit,
1912
+ prettify(key)
1913
+ ]));
1914
+ var PERM_GROUPS = {
1915
+ manage: /* @__PURE__ */ new Map([
1916
+ [
1917
+ PermissionFlagsBits.ManageChannels,
1918
+ "Manage Channels"
1919
+ ],
1920
+ [
1921
+ PermissionFlagsBits.ManageRoles,
1922
+ "Manage Roles"
1923
+ ],
1924
+ [
1925
+ PermissionFlagsBits.ManageWebhooks,
1926
+ "Manage Webhooks"
1927
+ ],
1928
+ [
1929
+ PermissionFlagsBits.ManageMessages,
1930
+ "Manage Messages"
1931
+ ],
1932
+ [
1933
+ PermissionFlagsBits.ManageNicknames,
1934
+ "Manage Nicknames"
1935
+ ]
1936
+ ]),
1937
+ embed: /* @__PURE__ */ new Map([
1938
+ [
1939
+ PermissionFlagsBits.ViewChannel,
1940
+ "View Channel"
1941
+ ],
1942
+ [
1943
+ PermissionFlagsBits.SendMessages,
1944
+ "Send Messages"
1945
+ ],
1946
+ [
1947
+ PermissionFlagsBits.EmbedLinks,
1948
+ "Embed Links"
1949
+ ],
1950
+ [
1951
+ PermissionFlagsBits.AttachFiles,
1952
+ "Attach Files"
1953
+ ],
1954
+ [
1955
+ PermissionFlagsBits.UseExternalEmojis,
1956
+ "Use External Emojis"
1957
+ ],
1958
+ [
1959
+ PermissionFlagsBits.ReadMessageHistory,
1960
+ "Read Message History"
1961
+ ]
1962
+ ]),
1963
+ others: /* @__PURE__ */ new Map([
1964
+ [
1965
+ PermissionFlagsBits.AddReactions,
1966
+ "Add Reactions"
1967
+ ],
1968
+ [
1969
+ PermissionFlagsBits.UseApplicationCommands,
1970
+ "Use Application Commands"
1971
+ ]
1972
+ ])
1973
+ };
1974
+ function checkPermissions(client, roleOrChannel, scope = "all", inverse = false) {
1975
+ let required;
1976
+ if (Array.isArray(scope)) {
1977
+ required = /* @__PURE__ */ new Map();
1978
+ for (const bit of scope) {
1979
+ const name = PermissionNames.get(bit);
1980
+ if (name) required.set(bit, name);
1981
+ }
1982
+ } else {
1983
+ switch (scope) {
1984
+ case "manage":
1985
+ required = PERM_GROUPS.manage;
1986
+ break;
1987
+ case "embed":
1988
+ required = PERM_GROUPS.embed;
1989
+ break;
1990
+ case "others":
1991
+ required = new Map([
1992
+ ...PERM_GROUPS.others,
1993
+ ...PERM_GROUPS.embed
1994
+ ]);
1995
+ break;
1996
+ default:
1997
+ required = new Map([
1998
+ ...PERM_GROUPS.manage,
1999
+ ...PERM_GROUPS.others,
2000
+ ...PERM_GROUPS.embed
2001
+ ]);
2002
+ break;
2003
+ }
2004
+ }
2005
+ let permissions;
2006
+ if (roleOrChannel instanceof Role) {
2007
+ permissions = roleOrChannel.permissions;
2008
+ } else {
2009
+ if (!client.user) throw new Error("Client user is not available");
2010
+ permissions = roleOrChannel.permissionsFor(client.user, true);
2011
+ }
2012
+ if (!permissions) {
2013
+ throw new MissingPermissions("Missing Permissions", Array.from(required.values()), roleOrChannel);
2014
+ }
2015
+ if (inverse) {
2016
+ const dangerous = Array.from(required.entries()).filter(([bit]) => permissions.has(bit, true)).map(([, name]) => name);
2017
+ if (dangerous.length > 0) {
2018
+ throw new HasDangerousPermissions("Role has dangerous permissions", roleOrChannel, dangerous);
2019
+ }
2020
+ } else {
2021
+ const missing = Array.from(required.entries()).filter(([bit]) => !permissions.has(bit, true)).map(([, name]) => name);
2022
+ if (missing.length > 0) {
2023
+ throw new MissingPermissions("Missing Permissions", missing, roleOrChannel);
2024
+ }
2025
+ }
2026
+ }
2027
+ __name(checkPermissions, "checkPermissions");
2028
+
2029
+ // src/bot/utilities/roles/getBotRole.ts
2030
+ function getBotRole(client, guild) {
2031
+ if (!client.user) throw new Error("Client user is not available");
2032
+ const botRole = guild.roles.botRoleFor(client.user);
2033
+ if (!botRole) throw new Error("Bot role not found in guild");
2034
+ return botRole;
2035
+ }
2036
+ __name(getBotRole, "getBotRole");
2037
+
2038
+ // src/bot/utilities/roles/checkBotPermissions.ts
2039
+ function checkBotPermissions(client, guildOrChannel, scope = "all", inverse = false) {
2040
+ if (guildOrChannel instanceof Guild) {
2041
+ const botRole = getBotRole(client, guildOrChannel);
2042
+ checkPermissions(client, botRole, scope, inverse);
2043
+ } else {
2044
+ checkPermissions(client, guildOrChannel, scope);
2045
+ }
2046
+ }
2047
+ __name(checkBotPermissions, "checkBotPermissions");
2048
+ async function fetchRole(clientOrGuild, roleId) {
2049
+ let role;
2050
+ if (!roleId) {
2051
+ throw new RoleDoesNotExist("Role ID is null or undefined", roleId);
2052
+ }
2053
+ if (clientOrGuild instanceof Guild) {
2054
+ const guild = clientOrGuild;
2055
+ role = guild.roles.cache.get(roleId);
2056
+ if (!role) {
2057
+ try {
2058
+ role = await guild.roles.fetch(roleId);
2059
+ } catch {
2060
+ throw new RoleDoesNotExist("Role not found in specified guild", roleId);
2061
+ }
2062
+ }
2063
+ } else {
2064
+ const client = clientOrGuild;
2065
+ role = client.guilds.cache.map((guild) => guild.roles.cache.get(roleId)).find((role2) => role2);
2066
+ if (!role) {
2067
+ const guilds = client.guilds.cache;
2068
+ for (const guild of guilds.values()) {
2069
+ try {
2070
+ role = await guild.roles.fetch(roleId);
2071
+ if (role) break;
2072
+ } catch {
2073
+ continue;
2074
+ }
2075
+ }
2076
+ }
2077
+ }
2078
+ if (!role) {
2079
+ throw new RoleDoesNotExist("Role not found", roleId);
2080
+ }
2081
+ return role;
2082
+ }
2083
+ __name(fetchRole, "fetchRole");
2084
+ function hasPermsToAssign(targetRole) {
2085
+ const botRole = getBotRole(targetRole.client, targetRole.guild);
2086
+ if (targetRole.comparePositionTo(botRole) >= 0) {
2087
+ throw new RoleHigherThanMe("Role is higher than me", targetRole, botRole);
2088
+ }
2089
+ if (targetRole.managed) {
2090
+ throw new CannotAssignBotRole(`Cannot assign bot role ${targetRole.name}`);
2091
+ }
2092
+ checkBotPermissions(targetRole.client, targetRole.guild, [
2093
+ PermissionFlagsBits.ManageRoles
2094
+ ]);
2095
+ }
2096
+ __name(hasPermsToAssign, "hasPermsToAssign");
2097
+
2098
+ // src/bot/utilities/users/fetchGuildMember.ts
2099
+ async function fetchGuildMember(guild, userId) {
2100
+ let user = guild.members.cache.get(userId);
2101
+ user ??= await guild.members.fetch(userId).catch(() => {
2102
+ throw new UserNotInGuild(`User with ID ${userId} not found in guild`);
2103
+ });
2104
+ return user;
2105
+ }
2106
+ __name(fetchGuildMember, "fetchGuildMember");
2107
+
2108
+ // src/bot/utilities/users/fetchManyGuildMembers.ts
2109
+ async function fetchManyGuildMembers(guild, userIds) {
2110
+ const results = await Promise.allSettled(userIds.map((userId) => fetchGuildMember(guild, userId)));
2111
+ return results.filter((result) => result.status === "fulfilled").map((result) => result.value);
2112
+ }
2113
+ __name(fetchManyGuildMembers, "fetchManyGuildMembers");
2114
+ async function fetchUser(client, userId) {
2115
+ let user = client.users.cache.get(userId);
2116
+ user ??= await client.users.fetch(userId).catch((err) => {
2117
+ if (err instanceof DiscordAPIError && err.code === RESTJSONErrorCodes.UnknownUser) {
2118
+ throw new UserNotFound(userId);
2119
+ }
2120
+ throw err;
2121
+ });
2122
+ return user;
2123
+ }
2124
+ __name(fetchUser, "fetchUser");
2125
+
2126
+ // src/bot/utilities/users/fetchManyUsers.ts
2127
+ async function fetchManyUsers(client, userIds) {
2128
+ const results = await Promise.allSettled(userIds.map((userId) => fetchUser(client, userId)));
2129
+ return results.filter((result) => result.status === "fulfilled").map((result) => result.value);
2130
+ }
2131
+ __name(fetchManyUsers, "fetchManyUsers");
2132
+
2133
+ // src/bot/utilities/users/updateMemberRoles.ts
2134
+ async function updateMemberRoles(rolesToAdd, rolesToRemove, member) {
2135
+ const current = new Set(member.roles.cache.map((r) => r.id));
2136
+ const toAdd = new Set(rolesToAdd);
2137
+ const toRemove = new Set(rolesToRemove);
2138
+ const updated = current.union(toAdd).difference(toRemove);
2139
+ await member.roles.set([
2140
+ ...updated
2141
+ ]);
2142
+ }
2143
+ __name(updateMemberRoles, "updateMemberRoles");
2144
+
2145
+ export { AutocompleteHandler, AutocompleteRoute, BaseComponent, BaseErrorEmbed, BaseHandler, Bot, BuilderComponent, BuilderTypes, ButtonRoute, CannotAssignBotRole, CannotSendEmbedsError, Catchable, ChannelNotFoundError, ChannelNotTextChannel, Checkable, CommandMetadataKey, CommandRegistry, ContextMenuRoute, CouldNotFindChannel, CustomError, DatabaseError, EffectMetadataKey, EffectsEmitter, EffectsHandler, EffectsRegistry, EmojiInjector, EventCatchable, EventController, EventHandler, EventMetadataKey, EventMiddleware, GenericError, HasDangerousPermissions, InteractionController, InteractionHandler, InteractionMetadataKey, InteractionMiddleware, InteractionRoutes, Middleware, MiddlewareMetadataKey, MiddlewareType, MissingPermissions, ModalRoute, PERM_GROUPS, PermissionNames, Pluggable, Plugin, RegisterCommand, RegisterEffect, RegisterEvent, RoleDoesNotExist, RoleHigherThanMe, RowComponent, RowTypes, Seedcord, SelectMenuRoute, SelectMenuType, SlashRoute, UnhandledEvent, UnknownException, UserNotFound, UserNotInGuild, WebhookLog, attemptSendDM, buildSlashRoute, checkBotPermissions, checkPermissions, extractErrorResponse, fetchGuildMember, fetchManyGuildMembers, fetchManyUsers, fetchRole, fetchText, fetchUser, getBotRole, hasPermsToAssign, sendInText, throwCustomError, updateMemberRoles };
1503
2146
  //# sourceMappingURL=index.mjs.map
1504
2147
  //# sourceMappingURL=index.mjs.map