seedcord 0.5.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
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 chalk4 from 'chalk';
4
+ import chalk3 from 'chalk';
5
5
  import { SeparatorBuilder, SectionBuilder, MediaGalleryBuilder, FileBuilder, TextDisplayBuilder, ContainerBuilder, RoleSelectMenuBuilder, MentionableSelectMenuBuilder, ChannelSelectMenuBuilder, UserSelectMenuBuilder, StringSelectMenuOptionBuilder, StringSelectMenuBuilder, ButtonBuilder, FileUploadBuilder, 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
7
  import { hexToNumber, traverseDirectory, filterCirculars, prettify } from '@seedcord/utils';
@@ -11,69 +11,21 @@ import { EventEmitter } from 'events';
11
11
 
12
12
  var __defProp = Object.defineProperty;
13
13
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
14
- var Plugin = class {
15
- static {
16
- __name(this, "Plugin");
17
- }
18
- pluggable;
19
- constructor(pluggable) {
20
- this.pluggable = pluggable;
21
- }
22
- };
23
- var Pluggable = class _Pluggable {
24
- static {
25
- __name(this, "Pluggable");
26
- }
27
- isInitialized = false;
28
- shutdown;
29
- startup;
30
- static PLUGIN_INIT_TIMEOUT_MS = 15e3;
31
- constructor(shutdown, startup) {
32
- this.shutdown = shutdown;
33
- this.startup = startup;
34
- }
35
- async init() {
36
- if (this.isInitialized) return this;
37
- await this.startup.run();
38
- this.isInitialized = true;
39
- return this;
40
- }
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
- */
62
- attach(key, Plugin2, startupPhase, ...args) {
63
- if (this.isInitialized) throw new Error("Cannot attach a plugin after initialization.");
64
- if (this[key]) throw new Error(`Plugin with key "${key}" already exists.`);
65
- const instance = new Plugin2(this, ...args);
66
- const entry = {
67
- [key]: instance
14
+
15
+ // src/bot/decorators/Command.ts
16
+ var CommandMetadataKey = Symbol("command:metadata");
17
+ function RegisterCommand(scope, guilds = []) {
18
+ return (ctor) => {
19
+ const meta = scope === "global" ? {
20
+ scope
21
+ } : {
22
+ scope,
23
+ guilds
68
24
  };
69
- this.startup.addTask(startupPhase, `Plugin:${key}`, async () => {
70
- instance.logger.info(chalk4.bold("Initializing"));
71
- await instance.init();
72
- instance.logger.info(chalk4.bold("Initialized"));
73
- }, _Pluggable.PLUGIN_INIT_TIMEOUT_MS);
74
- return Object.assign(this, entry);
75
- }
76
- };
25
+ Reflect.defineMetadata(CommandMetadataKey, meta, ctor);
26
+ };
27
+ }
28
+ __name(RegisterCommand, "RegisterCommand");
77
29
  function parseEnvColor(raw, fallback) {
78
30
  if (!raw) return fallback;
79
31
  const toHex = /* @__PURE__ */ __name((n) => `#${n.toString(16).padStart(6, "0")}`, "toHex");
@@ -251,21 +203,6 @@ var CustomError = class extends Error {
251
203
  }
252
204
  };
253
205
 
254
- // src/bot/decorators/Command.ts
255
- var CommandMetadataKey = Symbol("command:metadata");
256
- function RegisterCommand(scope, guilds = []) {
257
- return (ctor) => {
258
- const meta = scope === "global" ? {
259
- scope
260
- } : {
261
- scope,
262
- guilds
263
- };
264
- Reflect.defineMetadata(CommandMetadataKey, meta, ctor);
265
- };
266
- }
267
- __name(RegisterCommand, "RegisterCommand");
268
-
269
206
  // src/bot/controllers/CommandRegistry.ts
270
207
  var CommandRegistry = class {
271
208
  static {
@@ -282,9 +219,9 @@ var CommandRegistry = class {
282
219
  async init() {
283
220
  if (this.isInitialised) return;
284
221
  this.isInitialised = true;
285
- this.logger.info(chalk4.bold(this.core.config.bot.commands.path));
222
+ this.logger.info(chalk3.bold(this.core.config.bot.commands.path));
286
223
  await this.loadCommands(this.core.config.bot.commands.path);
287
- this.logger.info(`${chalk4.bold.green("Loaded")}: ${chalk4.magenta.bold(this.globalCommands.length)} global, ${chalk4.magenta.bold(this.guildCommands.size)} guild groups`);
224
+ this.logger.info(`${chalk3.bold.green("Loaded")}: ${chalk3.magenta.bold(this.globalCommands.length)} global, ${chalk3.magenta.bold(this.guildCommands.size)} guild groups`);
288
225
  }
289
226
  async loadCommands(dir) {
290
227
  await traverseDirectory(dir, (_full, rel, mod) => {
@@ -303,24 +240,24 @@ var CommandRegistry = class {
303
240
  const commandType = comp instanceof SlashCommandBuilder ? "slash command" : "context menu command";
304
241
  if (meta.scope === "global") {
305
242
  this.globalCommands.push(comp);
306
- this.logger.info(`${chalk4.italic("Registered")} ${chalk4.bold.yellow(ctor.name)} from ${chalk4.gray(rel)}`);
307
- this.logger.info(` \u2192 Global ${commandType}: ${chalk4.bold.cyan(comp.name)}`);
243
+ this.logger.info(`${chalk3.italic("Registered")} ${chalk3.bold.yellow(ctor.name)} from ${chalk3.gray(rel)}`);
244
+ this.logger.info(`\u2192 Global ${commandType}: ${chalk3.bold.cyan(comp.name)}`);
308
245
  } else {
309
246
  for (const g of meta.guilds) {
310
247
  const arr = this.guildCommands.get(g) ?? [];
311
248
  arr.push(comp);
312
249
  this.guildCommands.set(g, arr);
313
250
  }
314
- this.logger.info(`${chalk4.italic("Registered")} ${chalk4.bold.yellow(ctor.name)} from ${chalk4.gray(rel)}`);
315
- this.logger.info(` \u2192 Guild ${commandType}: ${chalk4.bold.cyan(comp.name)} for ${chalk4.magenta.bold(meta.guilds.length)} guild(s)`);
251
+ this.logger.info(`${chalk3.italic("Registered")} ${chalk3.bold.yellow(ctor.name)} from ${chalk3.gray(rel)}`);
252
+ this.logger.info(`\u2192 Guild ${commandType}: ${chalk3.bold.cyan(comp.name)} for ${chalk3.magenta.bold(meta.guilds.length)} guild(s)`);
316
253
  }
317
254
  }
318
255
  async setCommands() {
319
256
  if (this.globalCommands.length > 0) {
320
257
  await this.core.bot.client.application?.commands.set(this.globalCommands);
321
258
  const tag = this.globalCommands.length === 1 ? "command" : "commands";
322
- this.logger.info(`${chalk4.bold.green("Configured")} ${chalk4.magenta.bold(this.globalCommands.length)} global ${tag}`);
323
- this.logger.info(` \u2192 ${this.globalCommands.map((command) => chalk4.bold.cyan(command.name)).join(", ")}`);
259
+ this.logger.info(`${chalk3.bold.green("Configured")} ${chalk3.magenta.bold(this.globalCommands.length)} global ${tag}`);
260
+ this.logger.info(`\u2192 ${this.globalCommands.map((command) => chalk3.bold.cyan(command.name)).join(", ")}`);
324
261
  }
325
262
  for (const [guildId, commands] of this.guildCommands.entries()) {
326
263
  const guild = this.core.bot.client.guilds.cache.get(guildId);
@@ -330,12 +267,62 @@ var CommandRegistry = class {
330
267
  }
331
268
  await guild.commands.set(commands);
332
269
  const tag = commands.length === 1 ? "command" : "commands";
333
- this.logger.info(`${chalk4.bold.green("Configured")} ${chalk4.magenta.bold(commands.length)} ${tag} for guild ${chalk4.bold.yellow(guild.name)}`);
334
- this.logger.info(` \u2192 ${commands.map((command) => chalk4.bold.cyan(command.name)).join(", ")}`);
270
+ this.logger.info(`${chalk3.bold.green("Configured")} ${chalk3.magenta.bold(commands.length)} ${tag} for guild ${chalk3.bold.yellow(guild.name)}`);
271
+ this.logger.info(` \u2192 ${commands.map((command) => chalk3.bold.cyan(command.name)).join(", ")}`);
335
272
  }
336
273
  }
337
274
  };
338
275
 
276
+ // src/bot/decorators/Events.ts
277
+ var EventMetadataKey = Symbol("event:metadata");
278
+ function RegisterEvent(events, options) {
279
+ return function(constructor) {
280
+ const saved = Reflect.getMetadata(EventMetadataKey, constructor);
281
+ const existing = saved ?? [];
282
+ const toStore = Array.isArray(events) ? events : [
283
+ events
284
+ ];
285
+ const frequency = options?.frequency ?? "on";
286
+ const entries = toStore.map((event) => ({
287
+ event,
288
+ frequency
289
+ }));
290
+ Reflect.defineMetadata(EventMetadataKey, [
291
+ ...existing,
292
+ ...entries
293
+ ], constructor);
294
+ };
295
+ }
296
+ __name(RegisterEvent, "RegisterEvent");
297
+
298
+ // src/bot/decorators/Middlewares.ts
299
+ var MiddlewareType = /* @__PURE__ */ (function(MiddlewareType2) {
300
+ MiddlewareType2["Interaction"] = "middleware:interaction";
301
+ MiddlewareType2["Event"] = "middleware:event";
302
+ return MiddlewareType2;
303
+ })({});
304
+ var MiddlewareMetadataKey = Symbol("middleware:metadata");
305
+ function Middleware(type, priority = 0, options = {}) {
306
+ return (ctor) => {
307
+ const normalizedPriority = Number(priority);
308
+ if (!Number.isFinite(normalizedPriority)) {
309
+ throw new TypeError("Middleware priority must be a finite number");
310
+ }
311
+ if (type === "middleware:interaction" && options.events?.length) {
312
+ throw new Error("Interaction middleware cannot specify event filters");
313
+ }
314
+ const metadata = {
315
+ priority: normalizedPriority,
316
+ type,
317
+ ...options.events ? {
318
+ events: options.events
319
+ } : {}
320
+ };
321
+ Reflect.defineMetadata(MiddlewareMetadataKey, metadata, ctor);
322
+ };
323
+ }
324
+ __name(Middleware, "Middleware");
325
+
339
326
  // src/interfaces/Handler.ts
340
327
  var BaseHandler = class {
341
328
  static {
@@ -435,51 +422,6 @@ function areRoutes(routes) {
435
422
  }
436
423
  __name(areRoutes, "areRoutes");
437
424
 
438
- // src/bot/decorators/Events.ts
439
- var EventMetadataKey = Symbol("event:metadata");
440
- function RegisterEvent(events) {
441
- return function(constructor) {
442
- const saved = Reflect.getMetadata(EventMetadataKey, constructor);
443
- const existing = areRoutes(saved) ? saved : [];
444
- const toStore = Array.isArray(events) ? events : [
445
- events
446
- ];
447
- Reflect.defineMetadata(EventMetadataKey, [
448
- ...existing,
449
- ...toStore
450
- ], constructor);
451
- };
452
- }
453
- __name(RegisterEvent, "RegisterEvent");
454
-
455
- // src/bot/decorators/Middlewares.ts
456
- var MiddlewareType = /* @__PURE__ */ (function(MiddlewareType2) {
457
- MiddlewareType2["Interaction"] = "middleware:interaction";
458
- MiddlewareType2["Event"] = "middleware:event";
459
- return MiddlewareType2;
460
- })({});
461
- var MiddlewareMetadataKey = Symbol("middleware:metadata");
462
- function Middleware(type, priority = 0, options = {}) {
463
- return (ctor) => {
464
- const normalizedPriority = Number(priority);
465
- if (!Number.isFinite(normalizedPriority)) {
466
- throw new TypeError("Middleware priority must be a finite number");
467
- }
468
- if (type === "middleware:interaction" && options.events?.length) {
469
- throw new Error("Interaction middleware cannot specify event filters");
470
- }
471
- const metadata = {
472
- priority: normalizedPriority,
473
- type,
474
- ...options.events ? {
475
- events: options.events
476
- } : {}
477
- };
478
- Reflect.defineMetadata(MiddlewareMetadataKey, metadata, ctor);
479
- };
480
- }
481
- __name(Middleware, "Middleware");
482
-
483
425
  // src/bot/controllers/EventController.ts
484
426
  var EventController = class {
485
427
  static {
@@ -499,27 +441,27 @@ var EventController = class {
499
441
  }
500
442
  this.isInitialized = true;
501
443
  const handlersDir = this.core.config.bot.events.path;
502
- this.logger.info(chalk4.bold(handlersDir));
444
+ this.logger.info(chalk3.bold(handlersDir));
503
445
  const middlewareDir = this.core.config.bot.events.middlewares;
504
446
  if (middlewareDir) {
505
- this.logger.info(`${chalk4.bold(middlewareDir)} ${chalk4.gray("(middlewares)")}`);
447
+ this.logger.info(`${chalk3.bold(middlewareDir)} ${chalk3.gray("(middlewares)")}`);
506
448
  await this.loadMiddlewares(middlewareDir);
507
449
  }
508
450
  await this.loadHandlers(handlersDir);
509
451
  this.attachToClient();
510
- this.logger.info(`\u2192 ${chalk4.magenta.bold(this.middlewares.length)} middlewares`);
452
+ this.logger.info(`\u2192 ${chalk3.magenta.bold(this.middlewares.length)} middlewares`);
511
453
  const loadedEventsArray = [];
512
454
  this.eventMap.forEach((handlers, eventName) => {
513
- loadedEventsArray.push(`${chalk4.magenta.bold(handlers.length)} ${eventName}`);
455
+ loadedEventsArray.push(`${chalk3.magenta.bold(handlers.length)} ${eventName}`);
514
456
  });
515
- this.logger.info(`${chalk4.bold.green("Loaded")}: ${this.eventMap.size > 0 ? loadedEventsArray.join(", ") : "none"}`);
457
+ this.logger.info(`${chalk3.bold.green("Loaded")}: ${this.eventMap.size > 0 ? loadedEventsArray.join(", ") : "none"}`);
516
458
  }
517
459
  async loadHandlers(dir) {
518
460
  await traverseDirectory(dir, (_fullPath, relativePath, imported) => {
519
461
  for (const val of Object.values(imported)) {
520
462
  if (!this.isEventHandlerClass(val)) continue;
521
463
  this.registerHandler(val);
522
- this.logger.info(`${chalk4.italic("Registered")} ${chalk4.bold.yellow(val.name)} from ${chalk4.gray(relativePath)}`);
464
+ this.logger.info(`${chalk3.italic("Registered")} ${chalk3.bold.yellow(val.name)} from ${chalk3.gray(relativePath)}`);
523
465
  }
524
466
  }, this.logger);
525
467
  }
@@ -544,7 +486,7 @@ var EventController = class {
544
486
  } : {}
545
487
  });
546
488
  this.middlewares.sort((a, b) => a.priority - b.priority);
547
- this.logger.info(`${chalk4.italic("Registered event middleware")} ${chalk4.bold.yellow(middlewareCtor.name)} ${chalk4.gray(`(priority ${metadata.priority})`)} from ${chalk4.gray(relativePath)}`);
489
+ this.logger.info(`${chalk3.italic("Registered event middleware")} ${chalk3.bold.yellow(middlewareCtor.name)} ${chalk3.gray(`(priority ${metadata.priority})`)} from ${chalk3.gray(relativePath)}`);
548
490
  }
549
491
  async runMiddlewares(eventName, args) {
550
492
  for (const { ctor, events } of this.middlewares) {
@@ -572,6 +514,21 @@ var EventController = class {
572
514
  }
573
515
  registerHandler(handlerClass) {
574
516
  const raw = Reflect.getMetadata(EventMetadataKey, handlerClass);
517
+ if (Array.isArray(raw)) {
518
+ for (const entry of raw) {
519
+ const key = entry.event;
520
+ let handlers = this.eventMap.get(key);
521
+ if (!handlers) {
522
+ handlers = [];
523
+ this.eventMap.set(key, handlers);
524
+ }
525
+ handlers.push({
526
+ ctor: handlerClass,
527
+ frequency: entry.frequency
528
+ });
529
+ }
530
+ return;
531
+ }
575
532
  const names = areRoutes(raw) ? raw : typeof raw === "string" ? [
576
533
  raw
577
534
  ] : [];
@@ -583,38 +540,47 @@ var EventController = class {
583
540
  handlers = [];
584
541
  this.eventMap.set(key, handlers);
585
542
  }
586
- handlers.push(handlerClass);
543
+ handlers.push({
544
+ ctor: handlerClass,
545
+ frequency: "on"
546
+ });
587
547
  }
588
548
  }
589
549
  attachToClient() {
590
- for (const [eventName] of this.eventMap) {
591
- this.logger.debug(`Attaching ${chalk4.bold.green(eventName)} to ${chalk4.bold.yellow(this.core.bot.client.user?.username)}`);
592
- this.core.bot.client.on(eventName, (...args) => {
593
- void (async () => {
594
- await this.processEvent(eventName, args);
595
- })();
596
- });
550
+ for (const [eventName, handlerEntries] of this.eventMap) {
551
+ this.logger.debug(`Attaching ${chalk3.bold.green(eventName)} to ${chalk3.bold.yellow(this.core.bot.client.user?.username)}`);
552
+ for (const entry of handlerEntries) {
553
+ const register = entry.frequency === "once" ? this.core.bot.client.once.bind(this.core.bot.client) : this.core.bot.client.on.bind(this.core.bot.client);
554
+ register(eventName, (...args) => {
555
+ void (async () => {
556
+ await this.processEvent(eventName, args, entry.ctor);
557
+ })();
558
+ });
559
+ }
597
560
  }
598
561
  }
599
- async processEvent(eventName, args) {
562
+ async processEvent(eventName, args, specificHandler) {
600
563
  const shouldContinue = await this.runMiddlewares(eventName, args);
601
564
  if (!shouldContinue) return;
602
- const handlerCtors = this.eventMap.get(eventName);
603
- if (!handlerCtors || handlerCtors.length === 0) return;
604
- for (const HandlerCtor of handlerCtors) {
605
- try {
606
- this.logger.debug(`Processing ${chalk4.bold.green(eventName)} with ${chalk4.gray(HandlerCtor.name)}`);
607
- const handler = new HandlerCtor(args, this.core);
608
- if (handler.hasChecks()) {
609
- await handler.runChecks();
610
- }
611
- if (handler.shouldBreak()) return;
612
- if (!handler.hasErrors()) {
613
- await handler.execute();
614
- }
615
- } catch (err) {
616
- this.logger.error(`Error in event ${String(eventName)} handler ${HandlerCtor.name}:`, err);
617
- }
565
+ if (specificHandler) {
566
+ await this.processHandler(eventName, specificHandler, args);
567
+ return;
568
+ }
569
+ const handlerEntries = this.eventMap.get(eventName);
570
+ if (!handlerEntries || handlerEntries.length === 0) return;
571
+ for (const entry of handlerEntries) {
572
+ await this.processHandler(eventName, entry.ctor, args);
573
+ }
574
+ }
575
+ async processHandler(eventName, ctor, args) {
576
+ try {
577
+ this.logger.debug(`Processing ${chalk3.bold.green(eventName)} with ${chalk3.gray(ctor.name)}`);
578
+ const handler = new ctor(args, this.core);
579
+ if (handler.hasChecks()) await handler.runChecks();
580
+ if (handler.shouldBreak()) return;
581
+ if (!handler.hasErrors()) await handler.execute();
582
+ } catch (err) {
583
+ this.logger.error(`Error in event ${String(eventName)} handler ${ctor.name}:`, err);
618
584
  }
619
585
  }
620
586
  };
@@ -711,6 +677,26 @@ function storeMetadata(symbol, routes, constructor) {
711
677
  Reflect.defineMetadata(InteractionMetadataKey, true, constructor);
712
678
  }
713
679
  __name(storeMetadata, "storeMetadata");
680
+ function buildSlashRoute(arg1, arg2, arg3) {
681
+ let command;
682
+ let sub;
683
+ let group;
684
+ if (typeof arg1 === "string") {
685
+ command = arg1;
686
+ sub = arg2;
687
+ group = arg3;
688
+ } else if (arg1 instanceof ChatInputCommandInteraction || arg1 instanceof AutocompleteInteraction) {
689
+ command = arg1.commandName;
690
+ group = arg1.options.getSubcommandGroup(false) ?? void 0;
691
+ sub = arg1.options.getSubcommand(false) ?? void 0;
692
+ } else {
693
+ throw new TypeError("Invalid argument passed to buildSlashRoute");
694
+ }
695
+ if (sub && group) return `${command}/${group}/${sub}`;
696
+ if (sub) return `${command}/${sub}`;
697
+ return command;
698
+ }
699
+ __name(buildSlashRoute, "buildSlashRoute");
714
700
 
715
701
  // src/bot/errors/Database.ts
716
702
  var DatabaseError = class extends CustomError {
@@ -876,26 +862,6 @@ _ts_decorate3([
876
862
  _ts_metadata3("design:paramtypes", []),
877
863
  _ts_metadata3("design:returntype", Promise)
878
864
  ], UnhandledEvent.prototype, "execute", null);
879
- function buildSlashRoute(arg1, arg2, arg3) {
880
- let command;
881
- let sub;
882
- let group;
883
- if (typeof arg1 === "string") {
884
- command = arg1;
885
- sub = arg2;
886
- group = arg3;
887
- } else if (arg1 instanceof ChatInputCommandInteraction || arg1 instanceof AutocompleteInteraction) {
888
- command = arg1.commandName;
889
- group = arg1.options.getSubcommandGroup(false) ?? void 0;
890
- sub = arg1.options.getSubcommand(false) ?? void 0;
891
- } else {
892
- throw new TypeError("Invalid argument passed to buildSlashRoute");
893
- }
894
- if (sub && group) return `${command}/${group}/${sub}`;
895
- if (sub) return `${command}/${sub}`;
896
- return command;
897
- }
898
- __name(buildSlashRoute, "buildSlashRoute");
899
865
 
900
866
  // src/bot/controllers/InteractionController.ts
901
867
  var InteractionController = class {
@@ -929,34 +895,34 @@ var InteractionController = class {
929
895
  if (this.isInitialized) return;
930
896
  this.isInitialized = true;
931
897
  const handlersDir = this.core.config.bot.interactions.path;
932
- this.logger.info(chalk4.bold(handlersDir));
898
+ this.logger.info(chalk3.bold(handlersDir));
933
899
  const middlewareDir = this.core.config.bot.interactions.middlewares;
934
900
  if (middlewareDir) {
935
- this.logger.info(`${chalk4.bold(middlewareDir)} ${chalk4.gray("(middlewares)")}`);
901
+ this.logger.info(`${chalk3.bold(middlewareDir)} ${chalk3.gray("(middlewares)")}`);
936
902
  await this.loadMiddlewares(middlewareDir);
937
903
  }
938
904
  await this.loadHandlers(handlersDir);
939
905
  this.attachToClient();
940
- this.logger.info(`${chalk4.bold.green("Loaded interaction handlers:")}`);
941
- this.logger.info(`\u2192 ${chalk4.magenta.bold(this.middlewares.length)} middlewares`);
942
- this.logger.info(`\u2192 ${chalk4.magenta.bold(this.slashMap.size)} slash commands`);
943
- this.logger.info(`\u2192 ${chalk4.magenta.bold(this.buttonMap.size)} buttons`);
944
- this.logger.info(`\u2192 ${chalk4.magenta.bold(this.modalMap.size)} modals`);
945
- this.logger.info(`\u2192 ${chalk4.magenta.bold(this.stringSelectMap.size)} string selects`);
946
- this.logger.info(`\u2192 ${chalk4.magenta.bold(this.userSelectMap.size)} user selects`);
947
- this.logger.info(`\u2192 ${chalk4.magenta.bold(this.roleSelectMap.size)} role selects`);
948
- this.logger.info(`\u2192 ${chalk4.magenta.bold(this.channelSelectMap.size)} channel selects`);
949
- this.logger.info(`\u2192 ${chalk4.magenta.bold(this.mentionableSelectMap.size)} mentionable selects`);
950
- this.logger.info(`\u2192 ${chalk4.magenta.bold(this.messageContextMenuMap.size)} message context menus`);
951
- this.logger.info(`\u2192 ${chalk4.magenta.bold(this.userContextMenuMap.size)} user context menus`);
952
- this.logger.info(`\u2192 ${chalk4.magenta.bold(this.autocompleteMap.size)} autocomplete`);
906
+ this.logger.info(`${chalk3.bold.green("Loaded interaction handlers:")}`);
907
+ this.logger.info(`\u2192 ${chalk3.magenta.bold(this.middlewares.length)} middlewares`);
908
+ this.logger.info(`\u2192 ${chalk3.magenta.bold(this.slashMap.size)} slash commands`);
909
+ this.logger.info(`\u2192 ${chalk3.magenta.bold(this.buttonMap.size)} buttons`);
910
+ this.logger.info(`\u2192 ${chalk3.magenta.bold(this.modalMap.size)} modals`);
911
+ this.logger.info(`\u2192 ${chalk3.magenta.bold(this.stringSelectMap.size)} string selects`);
912
+ this.logger.info(`\u2192 ${chalk3.magenta.bold(this.userSelectMap.size)} user selects`);
913
+ this.logger.info(`\u2192 ${chalk3.magenta.bold(this.roleSelectMap.size)} role selects`);
914
+ this.logger.info(`\u2192 ${chalk3.magenta.bold(this.channelSelectMap.size)} channel selects`);
915
+ this.logger.info(`\u2192 ${chalk3.magenta.bold(this.mentionableSelectMap.size)} mentionable selects`);
916
+ this.logger.info(`\u2192 ${chalk3.magenta.bold(this.messageContextMenuMap.size)} message context menus`);
917
+ this.logger.info(`\u2192 ${chalk3.magenta.bold(this.userContextMenuMap.size)} user context menus`);
918
+ this.logger.info(`\u2192 ${chalk3.magenta.bold(this.autocompleteMap.size)} autocomplete`);
953
919
  }
954
920
  async loadHandlers(dir) {
955
921
  await traverseDirectory(dir, (_fullPath, relativePath, imported) => {
956
922
  for (const val of Object.values(imported)) {
957
923
  if (!this.isHandlerClass(val)) continue;
958
924
  this.registerHandler(val);
959
- this.logger.info(`${chalk4.italic("Registered")} ${chalk4.bold.yellow(val.name)} from ${chalk4.gray(relativePath)}`);
925
+ this.logger.info(`${chalk3.italic("Registered")} ${chalk3.bold.yellow(val.name)} from ${chalk3.gray(relativePath)}`);
960
926
  }
961
927
  }, this.logger);
962
928
  }
@@ -978,7 +944,7 @@ var InteractionController = class {
978
944
  priority: metadata.priority
979
945
  });
980
946
  this.middlewares.sort((a, b) => a.priority - b.priority);
981
- this.logger.info(`${chalk4.italic("Registered middleware")} ${chalk4.bold.yellow(middlewareCtor.name)} ${chalk4.gray(`(priority ${metadata.priority})`)} from ${chalk4.gray(relativePath)}`);
947
+ this.logger.info(`${chalk3.italic("Registered middleware")} ${chalk3.bold.yellow(middlewareCtor.name)} ${chalk3.gray(`(priority ${metadata.priority})`)} from ${chalk3.gray(relativePath)}`);
982
948
  }
983
949
  isHandlerClass(obj) {
984
950
  if (typeof obj !== "function") return false;
@@ -1045,7 +1011,7 @@ var InteractionController = class {
1045
1011
  attachToClient() {
1046
1012
  this.core.bot.client.on(Events.InteractionCreate, (interaction) => {
1047
1013
  this.handleInteraction(interaction).catch((err) => {
1048
- this.logger.error(`[${chalk4.bold.red("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
1014
+ this.logger.error(`[${chalk3.bold.red("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
1049
1015
  });
1050
1016
  });
1051
1017
  }
@@ -1076,10 +1042,10 @@ var InteractionController = class {
1076
1042
  }
1077
1043
  let HandlerCtor = getHandler(key);
1078
1044
  if (!HandlerCtor) {
1079
- this.logger.warn(`No handler found for key ${chalk4.bold.cyan(key)}. Falling back to UnhandledEvent.`);
1045
+ this.logger.warn(`No handler found for key ${chalk3.bold.cyan(key)}. Falling back to UnhandledEvent.`);
1080
1046
  HandlerCtor = UnhandledEvent;
1081
1047
  }
1082
- this.logger.debug(`Processing ${chalk4.bold.green(key)} with ${chalk4.gray(HandlerCtor.name)}`);
1048
+ this.logger.debug(`Processing ${chalk3.bold.green(key)} with ${chalk3.gray(HandlerCtor.name)}`);
1083
1049
  const handler = new HandlerCtor(interaction, this.core, args);
1084
1050
  if (handler.hasChecks()) await handler.runChecks();
1085
1051
  if (handler.shouldBreak()) return;
@@ -1163,6 +1129,69 @@ var InteractionController = class {
1163
1129
  await this.processInteraction(interaction, () => autocompleteKey, (key) => this.autocompleteMap.get(key));
1164
1130
  }
1165
1131
  };
1132
+ var Plugin = class {
1133
+ static {
1134
+ __name(this, "Plugin");
1135
+ }
1136
+ pluggable;
1137
+ constructor(pluggable) {
1138
+ this.pluggable = pluggable;
1139
+ }
1140
+ };
1141
+ var Pluggable = class _Pluggable {
1142
+ static {
1143
+ __name(this, "Pluggable");
1144
+ }
1145
+ isInitialized = false;
1146
+ shutdown;
1147
+ startup;
1148
+ static PLUGIN_INIT_TIMEOUT_MS = 15e3;
1149
+ constructor(shutdown, startup) {
1150
+ this.shutdown = shutdown;
1151
+ this.startup = startup;
1152
+ }
1153
+ async init() {
1154
+ if (this.isInitialized) return this;
1155
+ await this.startup.run();
1156
+ this.isInitialized = true;
1157
+ return this;
1158
+ }
1159
+ /**
1160
+ * Attaches a plugin to this instance
1161
+ *
1162
+ * Plugins provide external functionality and are initialized during the specified startup phase.
1163
+ * The plugin instance becomes available as a property in `core` wherever it's available.
1164
+ *
1165
+ * Make sure to augment the {@link Core} interface with the plugin type to ensure TypeScript recognizes it and provides intellisense.
1166
+ *
1167
+ * @typeParam Key - The property name for accessing the plugin
1168
+ * @typeParam Ctor - The plugin constructor type
1169
+ * @param key - Property name to access the plugin instance
1170
+ * @param Plugin - Plugin constructor class
1171
+ * @param startupPhase - When during startup to initialize this plugin ({@link StartupPhase})
1172
+ * @param args - Additional arguments to pass to the plugin constructor
1173
+ * @returns This instance with the plugin attached as a typed property
1174
+ * @throws An {@link Error} When called after initialization or if key already exists
1175
+ * @example
1176
+ * ```typescript
1177
+ * seedcord.attach('db', Mongo, StartupPhase.Configuration, { uri: 'mongodb://...', name: 'seedcord', dir: ... })
1178
+ * ```
1179
+ */
1180
+ attach(key, Plugin2, startupPhase, ...args) {
1181
+ if (this.isInitialized) throw new Error("Cannot attach a plugin after initialization.");
1182
+ if (this[key]) throw new Error(`Plugin with key "${key}" already exists.`);
1183
+ const instance = new Plugin2(this, ...args);
1184
+ const entry = {
1185
+ [key]: instance
1186
+ };
1187
+ this.startup.addTask(startupPhase, `Plugin:${key}`, async () => {
1188
+ instance.logger.info(chalk3.bold("Initializing"));
1189
+ await instance.init();
1190
+ instance.logger.info(chalk3.bold("Initialized"));
1191
+ }, _Pluggable.PLUGIN_INIT_TIMEOUT_MS);
1192
+ return Object.assign(this, entry);
1193
+ }
1194
+ };
1166
1195
  var EmojiInjector = class {
1167
1196
  static {
1168
1197
  __name(this, "EmojiInjector");
@@ -1174,7 +1203,7 @@ var EmojiInjector = class {
1174
1203
  }
1175
1204
  async init() {
1176
1205
  if (!this.core.config.bot.emojis || Object.keys(this.core.config.bot.emojis).length === 0) {
1177
- this.logger.info(`${chalk4.bold.green("Loaded")}: ${chalk4.magenta.bold("0")} emojis`);
1206
+ this.logger.info(`${chalk3.bold.green("Loaded")}: ${chalk3.magenta.bold("0")} emojis`);
1178
1207
  return;
1179
1208
  }
1180
1209
  const configEmojis = this.core.config.bot.emojis;
@@ -1185,10 +1214,10 @@ var EmojiInjector = class {
1185
1214
  if (emoji) {
1186
1215
  configEmojis[key] = `<${emoji.identifier}>`;
1187
1216
  foundCount++;
1188
- this.logger.debug(`${chalk4.bold.green("Found")}: ${chalk4.magenta.bold(emojiName)} (${emoji.id})`);
1217
+ this.logger.debug(`${chalk3.bold.green("Found")}: ${chalk3.magenta.bold(emojiName)} (${emoji.id})`);
1189
1218
  }
1190
1219
  });
1191
- this.logger.info(`${chalk4.bold.green("Loaded")}: ${chalk4.magenta.bold(foundCount)} emojis`);
1220
+ this.logger.info(`${chalk3.bold.green("Loaded")}: ${chalk3.magenta.bold(foundCount)} emojis`);
1192
1221
  }
1193
1222
  };
1194
1223
 
@@ -1254,7 +1283,7 @@ var Bot = class extends Plugin {
1254
1283
  */
1255
1284
  async login() {
1256
1285
  await this._client.login(this.botToken);
1257
- this.logger.info(`Logged in as ${chalk4.bold.magenta(this._client.user?.username)}!`);
1286
+ this.logger.info(`Logged in as ${chalk3.bold.magenta(this._client.user?.username)}!`);
1258
1287
  return this;
1259
1288
  }
1260
1289
  /**
@@ -1262,7 +1291,7 @@ var Bot = class extends Plugin {
1262
1291
  */
1263
1292
  async logout() {
1264
1293
  await this._client.destroy();
1265
- this.logger.info(chalk4.bold.red("Logged out of Discord!"));
1294
+ this.logger.info(chalk3.bold.red("Logged out of Discord!"));
1266
1295
  }
1267
1296
  get client() {
1268
1297
  return this._client;
@@ -1280,14 +1309,18 @@ _ts_decorate4([
1280
1309
 
1281
1310
  // src/effects/decorators/RegisterEffect.ts
1282
1311
  var EffectMetadataKey = Symbol("effect:metadata");
1283
- function RegisterEffect(effect) {
1312
+ function RegisterEffect(effect, options) {
1284
1313
  return function(constructor) {
1285
- Reflect.defineMetadata(EffectMetadataKey, effect, constructor);
1314
+ const meta = {
1315
+ effect,
1316
+ frequency: options?.frequency
1317
+ };
1318
+ Reflect.defineMetadata(EffectMetadataKey, meta, constructor);
1286
1319
  };
1287
1320
  }
1288
1321
  __name(RegisterEffect, "RegisterEffect");
1289
1322
 
1290
- // src/effects/interfaces/EffectsHandler.ts
1323
+ // src/effects/EffectsHandler.ts
1291
1324
  var EffectsHandler = class {
1292
1325
  static {
1293
1326
  __name(this, "EffectsHandler");
@@ -1308,7 +1341,7 @@ var EffectsHandler = class {
1308
1341
  }
1309
1342
  };
1310
1343
 
1311
- // src/effects/interfaces/abstracts/WebhookLog.ts
1344
+ // src/effects/bases/WebhookLog.ts
1312
1345
  var WebhookLog = class extends EffectsHandler {
1313
1346
  static {
1314
1347
  __name(this, "WebhookLog");
@@ -1484,51 +1517,56 @@ var EffectsRegistry = class extends Plugin {
1484
1517
  if (this.isInitialized) return;
1485
1518
  this.isInitialized = true;
1486
1519
  const effectsDir = this.core.config.effects.path;
1487
- this.logger.info(chalk4.bold(effectsDir));
1488
- this.registerEffect("unknownException", UnknownException);
1520
+ this.logger.info(chalk3.bold(effectsDir));
1521
+ this.registerEffect(UnknownException, {
1522
+ effect: "unknownException",
1523
+ frequency: "on"
1524
+ });
1489
1525
  await this.loadEffects(effectsDir);
1490
1526
  this.attachEffects();
1491
1527
  const totalEffects = Array.from(this.effectsMap.values()).reduce((acc, handlers) => acc + handlers.length, 0);
1492
- this.logger.info(`${chalk4.bold.green("Loaded")}: ${chalk4.bold.magenta(totalEffects)} side effects`);
1528
+ this.logger.info(`${chalk3.bold.green("Loaded")}: ${chalk3.bold.magenta(totalEffects)} side effects`);
1493
1529
  }
1494
1530
  async loadEffects(dir) {
1495
1531
  await traverseDirectory(dir, (_fullPath, relativePath, imported) => {
1496
1532
  for (const exportName of Object.keys(imported)) {
1497
1533
  const val = imported[exportName];
1498
1534
  if (this.isEffectHandler(val)) {
1499
- const effectName = Reflect.getMetadata(EffectMetadataKey, val);
1500
- if (effectName) {
1501
- this.registerEffect(effectName, val);
1502
- this.logger.info(`${chalk4.italic("Registered")} ${chalk4.bold.yellow(val.name)} from ${chalk4.gray(relativePath)}`);
1503
- }
1535
+ const meta = Reflect.getMetadata(EffectMetadataKey, val);
1536
+ this.registerEffect(val, meta);
1537
+ this.logger.info(`${chalk3.italic("Registered")} ${chalk3.bold.yellow(val.name)} from ${chalk3.gray(relativePath)}`);
1504
1538
  }
1505
1539
  }
1506
1540
  }, this.logger);
1507
1541
  }
1508
- registerEffect(effectName, handler) {
1509
- let handlers = this.effectsMap.get(effectName);
1542
+ registerEffect(handler, options) {
1543
+ let handlers = this.effectsMap.get(options.effect);
1510
1544
  if (!handlers) {
1511
1545
  handlers = [];
1512
- this.effectsMap.set(effectName, handlers);
1546
+ this.effectsMap.set(options.effect, handlers);
1513
1547
  }
1514
- handlers.push(handler);
1548
+ handlers.push({
1549
+ ctor: handler,
1550
+ frequency: options.frequency ?? "on"
1551
+ });
1515
1552
  }
1516
1553
  isEffectHandler(obj) {
1517
1554
  if (typeof obj !== "function") return false;
1518
- return obj.prototype instanceof EffectsHandler;
1555
+ return obj.prototype instanceof EffectsHandler && Reflect.hasMetadata(EffectMetadataKey, obj);
1519
1556
  }
1520
1557
  attachEffects() {
1521
- for (const [effectName, handlerCtors] of this.effectsMap) {
1522
- this.emitter.on(effectName, (data) => {
1523
- for (const HandlerCtor of handlerCtors) {
1558
+ for (const [effectName, handlerEntries] of this.effectsMap) {
1559
+ for (const entry of handlerEntries) {
1560
+ const register = entry.frequency === "once" ? this.emitter.once.bind(this.emitter) : this.emitter.on.bind(this.emitter);
1561
+ register(effectName, (data) => {
1524
1562
  try {
1525
- const instance = new HandlerCtor(data, this.core);
1563
+ const instance = new entry.ctor(data, this.core);
1526
1564
  void instance.execute();
1527
1565
  } catch (err) {
1528
- this.logger.error(`Error in side effect ${String(effectName)} handler ${HandlerCtor.name}:`, err);
1566
+ this.logger.error(`Error in side effect ${String(effectName)} handler ${entry.ctor.name}:`, err);
1529
1567
  }
1530
- }
1531
- });
1568
+ });
1569
+ }
1532
1570
  }
1533
1571
  }
1534
1572
  emit(event, data) {
@@ -1580,19 +1618,19 @@ var Seedcord = class _Seedcord extends Pluggable {
1580
1618
  */
1581
1619
  registerStartupTasks() {
1582
1620
  this.startup.addTask(StartupPhase.Configuration, "Effect Initialization", async () => {
1583
- this.effects.logger.info(chalk4.bold("Initializing"));
1621
+ this.effects.logger.info(chalk3.bold("Initializing"));
1584
1622
  await this.effects.init();
1585
- this.effects.logger.info(chalk4.bold("Initialized"));
1623
+ this.effects.logger.info(chalk3.bold("Initialized"));
1586
1624
  });
1587
1625
  this.startup.addTask(StartupPhase.Instantiation, "Bot Initialization", async () => {
1588
- this.bot.logger.info(chalk4.bold("Initializing"));
1626
+ this.bot.logger.info(chalk3.bold("Initializing"));
1589
1627
  await this.bot.init();
1590
- this.bot.logger.info(chalk4.bold("Initialized"));
1628
+ this.bot.logger.info(chalk3.bold("Initialized"));
1591
1629
  });
1592
1630
  this.startup.addTask(StartupPhase.Ready, "Health Check", async () => {
1593
- this.healthCheck.logger.info(chalk4.bold("Initializing"));
1631
+ this.healthCheck.logger.info(chalk3.bold("Initializing"));
1594
1632
  await this.healthCheck.init();
1595
- this.healthCheck.logger.info(chalk4.bold("Initialized"));
1633
+ this.healthCheck.logger.info(chalk3.bold("Initialized"));
1596
1634
  });
1597
1635
  }
1598
1636
  /**