seedcord 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require('reflect-metadata');
4
4
  var services = require('@seedcord/services');
5
- var chalk4 = require('chalk');
5
+ var chalk3 = require('chalk');
6
6
  var discord_js = require('discord.js');
7
7
  var envapt = require('envapt');
8
8
  var utils = require('@seedcord/utils');
@@ -29,74 +29,26 @@ function _interopNamespace(e) {
29
29
  return Object.freeze(n);
30
30
  }
31
31
 
32
- var chalk4__default = /*#__PURE__*/_interopDefault(chalk4);
32
+ var chalk3__default = /*#__PURE__*/_interopDefault(chalk3);
33
33
  var crypto2__namespace = /*#__PURE__*/_interopNamespace(crypto2);
34
34
 
35
35
  var __defProp = Object.defineProperty;
36
36
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
37
- var Plugin = class {
38
- static {
39
- __name(this, "Plugin");
40
- }
41
- pluggable;
42
- constructor(pluggable) {
43
- this.pluggable = pluggable;
44
- }
45
- };
46
- var Pluggable = class _Pluggable {
47
- static {
48
- __name(this, "Pluggable");
49
- }
50
- isInitialized = false;
51
- shutdown;
52
- startup;
53
- static PLUGIN_INIT_TIMEOUT_MS = 15e3;
54
- constructor(shutdown, startup) {
55
- this.shutdown = shutdown;
56
- this.startup = startup;
57
- }
58
- async init() {
59
- if (this.isInitialized) return this;
60
- await this.startup.run();
61
- this.isInitialized = true;
62
- return this;
63
- }
64
- /**
65
- * Attaches a plugin to this instance
66
- *
67
- * Plugins provide external functionality and are initialized during the specified startup phase.
68
- * The plugin instance becomes available as a property in `core` wherever it's available.
69
- *
70
- * Make sure to augment the {@link Core} interface with the plugin type to ensure TypeScript recognizes it and provides intellisense.
71
- *
72
- * @typeParam Key - The property name for accessing the plugin
73
- * @typeParam Ctor - The plugin constructor type
74
- * @param key - Property name to access the plugin instance
75
- * @param Plugin - Plugin constructor class
76
- * @param startupPhase - When during startup to initialize this plugin ({@link StartupPhase})
77
- * @param args - Additional arguments to pass to the plugin constructor
78
- * @returns This instance with the plugin attached as a typed property
79
- * @throws An {@link Error} When called after initialization or if key already exists
80
- * @example
81
- * ```typescript
82
- * seedcord.attach('db', Mongo, StartupPhase.Configuration, { uri: 'mongodb://...', name: 'seedcord', dir: ... })
83
- * ```
84
- */
85
- attach(key, Plugin2, startupPhase, ...args) {
86
- if (this.isInitialized) throw new Error("Cannot attach a plugin after initialization.");
87
- if (this[key]) throw new Error(`Plugin with key "${key}" already exists.`);
88
- const instance = new Plugin2(this, ...args);
89
- const entry = {
90
- [key]: instance
37
+
38
+ // src/bot/decorators/Command.ts
39
+ var CommandMetadataKey = Symbol("command:metadata");
40
+ function RegisterCommand(scope, guilds = []) {
41
+ return (ctor) => {
42
+ const meta = scope === "global" ? {
43
+ scope
44
+ } : {
45
+ scope,
46
+ guilds
91
47
  };
92
- this.startup.addTask(startupPhase, `Plugin:${key}`, async () => {
93
- instance.logger.info(chalk4__default.default.bold("Initializing"));
94
- await instance.init();
95
- instance.logger.info(chalk4__default.default.bold("Initialized"));
96
- }, _Pluggable.PLUGIN_INIT_TIMEOUT_MS);
97
- return Object.assign(this, entry);
98
- }
99
- };
48
+ Reflect.defineMetadata(CommandMetadataKey, meta, ctor);
49
+ };
50
+ }
51
+ __name(RegisterCommand, "RegisterCommand");
100
52
  function parseEnvColor(raw, fallback) {
101
53
  if (!raw) return fallback;
102
54
  const toHex = /* @__PURE__ */ __name((n) => `#${n.toString(16).padStart(6, "0")}`, "toHex");
@@ -274,21 +226,6 @@ var CustomError = class extends Error {
274
226
  }
275
227
  };
276
228
 
277
- // src/bot/decorators/Command.ts
278
- var CommandMetadataKey = Symbol("command:metadata");
279
- function RegisterCommand(scope, guilds = []) {
280
- return (ctor) => {
281
- const meta = scope === "global" ? {
282
- scope
283
- } : {
284
- scope,
285
- guilds
286
- };
287
- Reflect.defineMetadata(CommandMetadataKey, meta, ctor);
288
- };
289
- }
290
- __name(RegisterCommand, "RegisterCommand");
291
-
292
229
  // src/bot/controllers/CommandRegistry.ts
293
230
  var CommandRegistry = class {
294
231
  static {
@@ -305,9 +242,9 @@ var CommandRegistry = class {
305
242
  async init() {
306
243
  if (this.isInitialised) return;
307
244
  this.isInitialised = true;
308
- this.logger.info(chalk4__default.default.bold(this.core.config.bot.commands.path));
245
+ this.logger.info(chalk3__default.default.bold(this.core.config.bot.commands.path));
309
246
  await this.loadCommands(this.core.config.bot.commands.path);
310
- this.logger.info(`${chalk4__default.default.bold.green("Loaded")}: ${chalk4__default.default.magenta.bold(this.globalCommands.length)} global, ${chalk4__default.default.magenta.bold(this.guildCommands.size)} guild groups`);
247
+ this.logger.info(`${chalk3__default.default.bold.green("Loaded")}: ${chalk3__default.default.magenta.bold(this.globalCommands.length)} global, ${chalk3__default.default.magenta.bold(this.guildCommands.size)} guild groups`);
311
248
  }
312
249
  async loadCommands(dir) {
313
250
  await utils.traverseDirectory(dir, (_full, rel, mod) => {
@@ -326,24 +263,24 @@ var CommandRegistry = class {
326
263
  const commandType = comp instanceof discord_js.SlashCommandBuilder ? "slash command" : "context menu command";
327
264
  if (meta.scope === "global") {
328
265
  this.globalCommands.push(comp);
329
- this.logger.info(`${chalk4__default.default.italic("Registered")} ${chalk4__default.default.bold.yellow(ctor.name)} from ${chalk4__default.default.gray(rel)}`);
330
- this.logger.info(` \u2192 Global ${commandType}: ${chalk4__default.default.bold.cyan(comp.name)}`);
266
+ this.logger.info(`${chalk3__default.default.italic("Registered")} ${chalk3__default.default.bold.yellow(ctor.name)} from ${chalk3__default.default.gray(rel)}`);
267
+ this.logger.info(`\u2192 Global ${commandType}: ${chalk3__default.default.bold.cyan(comp.name)}`);
331
268
  } else {
332
269
  for (const g of meta.guilds) {
333
270
  const arr = this.guildCommands.get(g) ?? [];
334
271
  arr.push(comp);
335
272
  this.guildCommands.set(g, arr);
336
273
  }
337
- this.logger.info(`${chalk4__default.default.italic("Registered")} ${chalk4__default.default.bold.yellow(ctor.name)} from ${chalk4__default.default.gray(rel)}`);
338
- this.logger.info(` \u2192 Guild ${commandType}: ${chalk4__default.default.bold.cyan(comp.name)} for ${chalk4__default.default.magenta.bold(meta.guilds.length)} guild(s)`);
274
+ this.logger.info(`${chalk3__default.default.italic("Registered")} ${chalk3__default.default.bold.yellow(ctor.name)} from ${chalk3__default.default.gray(rel)}`);
275
+ this.logger.info(`\u2192 Guild ${commandType}: ${chalk3__default.default.bold.cyan(comp.name)} for ${chalk3__default.default.magenta.bold(meta.guilds.length)} guild(s)`);
339
276
  }
340
277
  }
341
278
  async setCommands() {
342
279
  if (this.globalCommands.length > 0) {
343
280
  await this.core.bot.client.application?.commands.set(this.globalCommands);
344
281
  const tag = this.globalCommands.length === 1 ? "command" : "commands";
345
- this.logger.info(`${chalk4__default.default.bold.green("Configured")} ${chalk4__default.default.magenta.bold(this.globalCommands.length)} global ${tag}`);
346
- this.logger.info(` \u2192 ${this.globalCommands.map((command) => chalk4__default.default.bold.cyan(command.name)).join(", ")}`);
282
+ this.logger.info(`${chalk3__default.default.bold.green("Configured")} ${chalk3__default.default.magenta.bold(this.globalCommands.length)} global ${tag}`);
283
+ this.logger.info(`\u2192 ${this.globalCommands.map((command) => chalk3__default.default.bold.cyan(command.name)).join(", ")}`);
347
284
  }
348
285
  for (const [guildId, commands] of this.guildCommands.entries()) {
349
286
  const guild = this.core.bot.client.guilds.cache.get(guildId);
@@ -353,12 +290,62 @@ var CommandRegistry = class {
353
290
  }
354
291
  await guild.commands.set(commands);
355
292
  const tag = commands.length === 1 ? "command" : "commands";
356
- this.logger.info(`${chalk4__default.default.bold.green("Configured")} ${chalk4__default.default.magenta.bold(commands.length)} ${tag} for guild ${chalk4__default.default.bold.yellow(guild.name)}`);
357
- this.logger.info(` \u2192 ${commands.map((command) => chalk4__default.default.bold.cyan(command.name)).join(", ")}`);
293
+ this.logger.info(`${chalk3__default.default.bold.green("Configured")} ${chalk3__default.default.magenta.bold(commands.length)} ${tag} for guild ${chalk3__default.default.bold.yellow(guild.name)}`);
294
+ this.logger.info(` \u2192 ${commands.map((command) => chalk3__default.default.bold.cyan(command.name)).join(", ")}`);
358
295
  }
359
296
  }
360
297
  };
361
298
 
299
+ // src/bot/decorators/Events.ts
300
+ var EventMetadataKey = Symbol("event:metadata");
301
+ function RegisterEvent(events, options) {
302
+ return function(constructor) {
303
+ const saved = Reflect.getMetadata(EventMetadataKey, constructor);
304
+ const existing = saved ?? [];
305
+ const toStore = Array.isArray(events) ? events : [
306
+ events
307
+ ];
308
+ const frequency = options?.frequency ?? "on";
309
+ const entries = toStore.map((event) => ({
310
+ event,
311
+ frequency
312
+ }));
313
+ Reflect.defineMetadata(EventMetadataKey, [
314
+ ...existing,
315
+ ...entries
316
+ ], constructor);
317
+ };
318
+ }
319
+ __name(RegisterEvent, "RegisterEvent");
320
+
321
+ // src/bot/decorators/Middlewares.ts
322
+ var MiddlewareType = /* @__PURE__ */ (function(MiddlewareType2) {
323
+ MiddlewareType2["Interaction"] = "middleware:interaction";
324
+ MiddlewareType2["Event"] = "middleware:event";
325
+ return MiddlewareType2;
326
+ })({});
327
+ var MiddlewareMetadataKey = Symbol("middleware:metadata");
328
+ function Middleware(type, priority = 0, options = {}) {
329
+ return (ctor) => {
330
+ const normalizedPriority = Number(priority);
331
+ if (!Number.isFinite(normalizedPriority)) {
332
+ throw new TypeError("Middleware priority must be a finite number");
333
+ }
334
+ if (type === "middleware:interaction" && options.events?.length) {
335
+ throw new Error("Interaction middleware cannot specify event filters");
336
+ }
337
+ const metadata = {
338
+ priority: normalizedPriority,
339
+ type,
340
+ ...options.events ? {
341
+ events: options.events
342
+ } : {}
343
+ };
344
+ Reflect.defineMetadata(MiddlewareMetadataKey, metadata, ctor);
345
+ };
346
+ }
347
+ __name(Middleware, "Middleware");
348
+
362
349
  // src/interfaces/Handler.ts
363
350
  var BaseHandler = class {
364
351
  static {
@@ -458,51 +445,6 @@ function areRoutes(routes) {
458
445
  }
459
446
  __name(areRoutes, "areRoutes");
460
447
 
461
- // src/bot/decorators/Events.ts
462
- var EventMetadataKey = Symbol("event:metadata");
463
- function RegisterEvent(events) {
464
- return function(constructor) {
465
- const saved = Reflect.getMetadata(EventMetadataKey, constructor);
466
- const existing = areRoutes(saved) ? saved : [];
467
- const toStore = Array.isArray(events) ? events : [
468
- events
469
- ];
470
- Reflect.defineMetadata(EventMetadataKey, [
471
- ...existing,
472
- ...toStore
473
- ], constructor);
474
- };
475
- }
476
- __name(RegisterEvent, "RegisterEvent");
477
-
478
- // src/bot/decorators/Middlewares.ts
479
- var MiddlewareType = /* @__PURE__ */ (function(MiddlewareType2) {
480
- MiddlewareType2["Interaction"] = "middleware:interaction";
481
- MiddlewareType2["Event"] = "middleware:event";
482
- return MiddlewareType2;
483
- })({});
484
- var MiddlewareMetadataKey = Symbol("middleware:metadata");
485
- function Middleware(type, priority = 0, options = {}) {
486
- return (ctor) => {
487
- const normalizedPriority = Number(priority);
488
- if (!Number.isFinite(normalizedPriority)) {
489
- throw new TypeError("Middleware priority must be a finite number");
490
- }
491
- if (type === "middleware:interaction" && options.events?.length) {
492
- throw new Error("Interaction middleware cannot specify event filters");
493
- }
494
- const metadata = {
495
- priority: normalizedPriority,
496
- type,
497
- ...options.events ? {
498
- events: options.events
499
- } : {}
500
- };
501
- Reflect.defineMetadata(MiddlewareMetadataKey, metadata, ctor);
502
- };
503
- }
504
- __name(Middleware, "Middleware");
505
-
506
448
  // src/bot/controllers/EventController.ts
507
449
  var EventController = class {
508
450
  static {
@@ -522,27 +464,27 @@ var EventController = class {
522
464
  }
523
465
  this.isInitialized = true;
524
466
  const handlersDir = this.core.config.bot.events.path;
525
- this.logger.info(chalk4__default.default.bold(handlersDir));
467
+ this.logger.info(chalk3__default.default.bold(handlersDir));
526
468
  const middlewareDir = this.core.config.bot.events.middlewares;
527
469
  if (middlewareDir) {
528
- this.logger.info(`${chalk4__default.default.bold(middlewareDir)} ${chalk4__default.default.gray("(middlewares)")}`);
470
+ this.logger.info(`${chalk3__default.default.bold(middlewareDir)} ${chalk3__default.default.gray("(middlewares)")}`);
529
471
  await this.loadMiddlewares(middlewareDir);
530
472
  }
531
473
  await this.loadHandlers(handlersDir);
532
474
  this.attachToClient();
533
- this.logger.info(`\u2192 ${chalk4__default.default.magenta.bold(this.middlewares.length)} middlewares`);
475
+ this.logger.info(`\u2192 ${chalk3__default.default.magenta.bold(this.middlewares.length)} middlewares`);
534
476
  const loadedEventsArray = [];
535
477
  this.eventMap.forEach((handlers, eventName) => {
536
- loadedEventsArray.push(`${chalk4__default.default.magenta.bold(handlers.length)} ${eventName}`);
478
+ loadedEventsArray.push(`${chalk3__default.default.magenta.bold(handlers.length)} ${eventName}`);
537
479
  });
538
- this.logger.info(`${chalk4__default.default.bold.green("Loaded")}: ${this.eventMap.size > 0 ? loadedEventsArray.join(", ") : "none"}`);
480
+ this.logger.info(`${chalk3__default.default.bold.green("Loaded")}: ${this.eventMap.size > 0 ? loadedEventsArray.join(", ") : "none"}`);
539
481
  }
540
482
  async loadHandlers(dir) {
541
483
  await utils.traverseDirectory(dir, (_fullPath, relativePath, imported) => {
542
484
  for (const val of Object.values(imported)) {
543
485
  if (!this.isEventHandlerClass(val)) continue;
544
486
  this.registerHandler(val);
545
- this.logger.info(`${chalk4__default.default.italic("Registered")} ${chalk4__default.default.bold.yellow(val.name)} from ${chalk4__default.default.gray(relativePath)}`);
487
+ this.logger.info(`${chalk3__default.default.italic("Registered")} ${chalk3__default.default.bold.yellow(val.name)} from ${chalk3__default.default.gray(relativePath)}`);
546
488
  }
547
489
  }, this.logger);
548
490
  }
@@ -567,7 +509,7 @@ var EventController = class {
567
509
  } : {}
568
510
  });
569
511
  this.middlewares.sort((a, b) => a.priority - b.priority);
570
- this.logger.info(`${chalk4__default.default.italic("Registered event middleware")} ${chalk4__default.default.bold.yellow(middlewareCtor.name)} ${chalk4__default.default.gray(`(priority ${metadata.priority})`)} from ${chalk4__default.default.gray(relativePath)}`);
512
+ this.logger.info(`${chalk3__default.default.italic("Registered event middleware")} ${chalk3__default.default.bold.yellow(middlewareCtor.name)} ${chalk3__default.default.gray(`(priority ${metadata.priority})`)} from ${chalk3__default.default.gray(relativePath)}`);
571
513
  }
572
514
  async runMiddlewares(eventName, args) {
573
515
  for (const { ctor, events } of this.middlewares) {
@@ -595,6 +537,21 @@ var EventController = class {
595
537
  }
596
538
  registerHandler(handlerClass) {
597
539
  const raw = Reflect.getMetadata(EventMetadataKey, handlerClass);
540
+ if (Array.isArray(raw)) {
541
+ for (const entry of raw) {
542
+ const key = entry.event;
543
+ let handlers = this.eventMap.get(key);
544
+ if (!handlers) {
545
+ handlers = [];
546
+ this.eventMap.set(key, handlers);
547
+ }
548
+ handlers.push({
549
+ ctor: handlerClass,
550
+ frequency: entry.frequency
551
+ });
552
+ }
553
+ return;
554
+ }
598
555
  const names = areRoutes(raw) ? raw : typeof raw === "string" ? [
599
556
  raw
600
557
  ] : [];
@@ -606,38 +563,47 @@ var EventController = class {
606
563
  handlers = [];
607
564
  this.eventMap.set(key, handlers);
608
565
  }
609
- handlers.push(handlerClass);
566
+ handlers.push({
567
+ ctor: handlerClass,
568
+ frequency: "on"
569
+ });
610
570
  }
611
571
  }
612
572
  attachToClient() {
613
- for (const [eventName] of this.eventMap) {
614
- this.logger.debug(`Attaching ${chalk4__default.default.bold.green(eventName)} to ${chalk4__default.default.bold.yellow(this.core.bot.client.user?.username)}`);
615
- this.core.bot.client.on(eventName, (...args) => {
616
- void (async () => {
617
- await this.processEvent(eventName, args);
618
- })();
619
- });
573
+ for (const [eventName, handlerEntries] of this.eventMap) {
574
+ this.logger.debug(`Attaching ${chalk3__default.default.bold.green(eventName)} to ${chalk3__default.default.bold.yellow(this.core.bot.client.user?.username)}`);
575
+ for (const entry of handlerEntries) {
576
+ 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);
577
+ register(eventName, (...args) => {
578
+ void (async () => {
579
+ await this.processEvent(eventName, args, entry.ctor);
580
+ })();
581
+ });
582
+ }
620
583
  }
621
584
  }
622
- async processEvent(eventName, args) {
585
+ async processEvent(eventName, args, specificHandler) {
623
586
  const shouldContinue = await this.runMiddlewares(eventName, args);
624
587
  if (!shouldContinue) return;
625
- const handlerCtors = this.eventMap.get(eventName);
626
- if (!handlerCtors || handlerCtors.length === 0) return;
627
- for (const HandlerCtor of handlerCtors) {
628
- try {
629
- this.logger.debug(`Processing ${chalk4__default.default.bold.green(eventName)} with ${chalk4__default.default.gray(HandlerCtor.name)}`);
630
- const handler = new HandlerCtor(args, this.core);
631
- if (handler.hasChecks()) {
632
- await handler.runChecks();
633
- }
634
- if (handler.shouldBreak()) return;
635
- if (!handler.hasErrors()) {
636
- await handler.execute();
637
- }
638
- } catch (err) {
639
- this.logger.error(`Error in event ${String(eventName)} handler ${HandlerCtor.name}:`, err);
640
- }
588
+ if (specificHandler) {
589
+ await this.processHandler(eventName, specificHandler, args);
590
+ return;
591
+ }
592
+ const handlerEntries = this.eventMap.get(eventName);
593
+ if (!handlerEntries || handlerEntries.length === 0) return;
594
+ for (const entry of handlerEntries) {
595
+ await this.processHandler(eventName, entry.ctor, args);
596
+ }
597
+ }
598
+ async processHandler(eventName, ctor, args) {
599
+ try {
600
+ this.logger.debug(`Processing ${chalk3__default.default.bold.green(eventName)} with ${chalk3__default.default.gray(ctor.name)}`);
601
+ const handler = new ctor(args, this.core);
602
+ if (handler.hasChecks()) await handler.runChecks();
603
+ if (handler.shouldBreak()) return;
604
+ if (!handler.hasErrors()) await handler.execute();
605
+ } catch (err) {
606
+ this.logger.error(`Error in event ${String(eventName)} handler ${ctor.name}:`, err);
641
607
  }
642
608
  }
643
609
  };
@@ -734,6 +700,26 @@ function storeMetadata(symbol, routes, constructor) {
734
700
  Reflect.defineMetadata(InteractionMetadataKey, true, constructor);
735
701
  }
736
702
  __name(storeMetadata, "storeMetadata");
703
+ function buildSlashRoute(arg1, arg2, arg3) {
704
+ let command;
705
+ let sub;
706
+ let group;
707
+ if (typeof arg1 === "string") {
708
+ command = arg1;
709
+ sub = arg2;
710
+ group = arg3;
711
+ } else if (arg1 instanceof discord_js.ChatInputCommandInteraction || arg1 instanceof discord_js.AutocompleteInteraction) {
712
+ command = arg1.commandName;
713
+ group = arg1.options.getSubcommandGroup(false) ?? void 0;
714
+ sub = arg1.options.getSubcommand(false) ?? void 0;
715
+ } else {
716
+ throw new TypeError("Invalid argument passed to buildSlashRoute");
717
+ }
718
+ if (sub && group) return `${command}/${group}/${sub}`;
719
+ if (sub) return `${command}/${sub}`;
720
+ return command;
721
+ }
722
+ __name(buildSlashRoute, "buildSlashRoute");
737
723
 
738
724
  // src/bot/errors/Database.ts
739
725
  var DatabaseError = class extends CustomError {
@@ -899,26 +885,6 @@ _ts_decorate3([
899
885
  _ts_metadata3("design:paramtypes", []),
900
886
  _ts_metadata3("design:returntype", Promise)
901
887
  ], UnhandledEvent.prototype, "execute", null);
902
- function buildSlashRoute(arg1, arg2, arg3) {
903
- let command;
904
- let sub;
905
- let group;
906
- if (typeof arg1 === "string") {
907
- command = arg1;
908
- sub = arg2;
909
- group = arg3;
910
- } else if (arg1 instanceof discord_js.ChatInputCommandInteraction || arg1 instanceof discord_js.AutocompleteInteraction) {
911
- command = arg1.commandName;
912
- group = arg1.options.getSubcommandGroup(false) ?? void 0;
913
- sub = arg1.options.getSubcommand(false) ?? void 0;
914
- } else {
915
- throw new TypeError("Invalid argument passed to buildSlashRoute");
916
- }
917
- if (sub && group) return `${command}/${group}/${sub}`;
918
- if (sub) return `${command}/${sub}`;
919
- return command;
920
- }
921
- __name(buildSlashRoute, "buildSlashRoute");
922
888
 
923
889
  // src/bot/controllers/InteractionController.ts
924
890
  var InteractionController = class {
@@ -952,34 +918,34 @@ var InteractionController = class {
952
918
  if (this.isInitialized) return;
953
919
  this.isInitialized = true;
954
920
  const handlersDir = this.core.config.bot.interactions.path;
955
- this.logger.info(chalk4__default.default.bold(handlersDir));
921
+ this.logger.info(chalk3__default.default.bold(handlersDir));
956
922
  const middlewareDir = this.core.config.bot.interactions.middlewares;
957
923
  if (middlewareDir) {
958
- this.logger.info(`${chalk4__default.default.bold(middlewareDir)} ${chalk4__default.default.gray("(middlewares)")}`);
924
+ this.logger.info(`${chalk3__default.default.bold(middlewareDir)} ${chalk3__default.default.gray("(middlewares)")}`);
959
925
  await this.loadMiddlewares(middlewareDir);
960
926
  }
961
927
  await this.loadHandlers(handlersDir);
962
928
  this.attachToClient();
963
- this.logger.info(`${chalk4__default.default.bold.green("Loaded interaction handlers:")}`);
964
- this.logger.info(`\u2192 ${chalk4__default.default.magenta.bold(this.middlewares.length)} middlewares`);
965
- this.logger.info(`\u2192 ${chalk4__default.default.magenta.bold(this.slashMap.size)} slash commands`);
966
- this.logger.info(`\u2192 ${chalk4__default.default.magenta.bold(this.buttonMap.size)} buttons`);
967
- this.logger.info(`\u2192 ${chalk4__default.default.magenta.bold(this.modalMap.size)} modals`);
968
- this.logger.info(`\u2192 ${chalk4__default.default.magenta.bold(this.stringSelectMap.size)} string selects`);
969
- this.logger.info(`\u2192 ${chalk4__default.default.magenta.bold(this.userSelectMap.size)} user selects`);
970
- this.logger.info(`\u2192 ${chalk4__default.default.magenta.bold(this.roleSelectMap.size)} role selects`);
971
- this.logger.info(`\u2192 ${chalk4__default.default.magenta.bold(this.channelSelectMap.size)} channel selects`);
972
- this.logger.info(`\u2192 ${chalk4__default.default.magenta.bold(this.mentionableSelectMap.size)} mentionable selects`);
973
- this.logger.info(`\u2192 ${chalk4__default.default.magenta.bold(this.messageContextMenuMap.size)} message context menus`);
974
- this.logger.info(`\u2192 ${chalk4__default.default.magenta.bold(this.userContextMenuMap.size)} user context menus`);
975
- this.logger.info(`\u2192 ${chalk4__default.default.magenta.bold(this.autocompleteMap.size)} autocomplete`);
929
+ this.logger.info(`${chalk3__default.default.bold.green("Loaded interaction handlers:")}`);
930
+ this.logger.info(`\u2192 ${chalk3__default.default.magenta.bold(this.middlewares.length)} middlewares`);
931
+ this.logger.info(`\u2192 ${chalk3__default.default.magenta.bold(this.slashMap.size)} slash commands`);
932
+ this.logger.info(`\u2192 ${chalk3__default.default.magenta.bold(this.buttonMap.size)} buttons`);
933
+ this.logger.info(`\u2192 ${chalk3__default.default.magenta.bold(this.modalMap.size)} modals`);
934
+ this.logger.info(`\u2192 ${chalk3__default.default.magenta.bold(this.stringSelectMap.size)} string selects`);
935
+ this.logger.info(`\u2192 ${chalk3__default.default.magenta.bold(this.userSelectMap.size)} user selects`);
936
+ this.logger.info(`\u2192 ${chalk3__default.default.magenta.bold(this.roleSelectMap.size)} role selects`);
937
+ this.logger.info(`\u2192 ${chalk3__default.default.magenta.bold(this.channelSelectMap.size)} channel selects`);
938
+ this.logger.info(`\u2192 ${chalk3__default.default.magenta.bold(this.mentionableSelectMap.size)} mentionable selects`);
939
+ this.logger.info(`\u2192 ${chalk3__default.default.magenta.bold(this.messageContextMenuMap.size)} message context menus`);
940
+ this.logger.info(`\u2192 ${chalk3__default.default.magenta.bold(this.userContextMenuMap.size)} user context menus`);
941
+ this.logger.info(`\u2192 ${chalk3__default.default.magenta.bold(this.autocompleteMap.size)} autocomplete`);
976
942
  }
977
943
  async loadHandlers(dir) {
978
944
  await utils.traverseDirectory(dir, (_fullPath, relativePath, imported) => {
979
945
  for (const val of Object.values(imported)) {
980
946
  if (!this.isHandlerClass(val)) continue;
981
947
  this.registerHandler(val);
982
- this.logger.info(`${chalk4__default.default.italic("Registered")} ${chalk4__default.default.bold.yellow(val.name)} from ${chalk4__default.default.gray(relativePath)}`);
948
+ this.logger.info(`${chalk3__default.default.italic("Registered")} ${chalk3__default.default.bold.yellow(val.name)} from ${chalk3__default.default.gray(relativePath)}`);
983
949
  }
984
950
  }, this.logger);
985
951
  }
@@ -1001,7 +967,7 @@ var InteractionController = class {
1001
967
  priority: metadata.priority
1002
968
  });
1003
969
  this.middlewares.sort((a, b) => a.priority - b.priority);
1004
- this.logger.info(`${chalk4__default.default.italic("Registered middleware")} ${chalk4__default.default.bold.yellow(middlewareCtor.name)} ${chalk4__default.default.gray(`(priority ${metadata.priority})`)} from ${chalk4__default.default.gray(relativePath)}`);
970
+ this.logger.info(`${chalk3__default.default.italic("Registered middleware")} ${chalk3__default.default.bold.yellow(middlewareCtor.name)} ${chalk3__default.default.gray(`(priority ${metadata.priority})`)} from ${chalk3__default.default.gray(relativePath)}`);
1005
971
  }
1006
972
  isHandlerClass(obj) {
1007
973
  if (typeof obj !== "function") return false;
@@ -1068,7 +1034,7 @@ var InteractionController = class {
1068
1034
  attachToClient() {
1069
1035
  this.core.bot.client.on(discord_js.Events.InteractionCreate, (interaction) => {
1070
1036
  this.handleInteraction(interaction).catch((err) => {
1071
- this.logger.error(`[${chalk4__default.default.bold.red("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
1037
+ this.logger.error(`[${chalk3__default.default.bold.red("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
1072
1038
  });
1073
1039
  });
1074
1040
  }
@@ -1099,10 +1065,10 @@ var InteractionController = class {
1099
1065
  }
1100
1066
  let HandlerCtor = getHandler(key);
1101
1067
  if (!HandlerCtor) {
1102
- this.logger.warn(`No handler found for key ${chalk4__default.default.bold.cyan(key)}. Falling back to UnhandledEvent.`);
1068
+ this.logger.warn(`No handler found for key ${chalk3__default.default.bold.cyan(key)}. Falling back to UnhandledEvent.`);
1103
1069
  HandlerCtor = UnhandledEvent;
1104
1070
  }
1105
- this.logger.debug(`Processing ${chalk4__default.default.bold.green(key)} with ${chalk4__default.default.gray(HandlerCtor.name)}`);
1071
+ this.logger.debug(`Processing ${chalk3__default.default.bold.green(key)} with ${chalk3__default.default.gray(HandlerCtor.name)}`);
1106
1072
  const handler = new HandlerCtor(interaction, this.core, args);
1107
1073
  if (handler.hasChecks()) await handler.runChecks();
1108
1074
  if (handler.shouldBreak()) return;
@@ -1186,6 +1152,69 @@ var InteractionController = class {
1186
1152
  await this.processInteraction(interaction, () => autocompleteKey, (key) => this.autocompleteMap.get(key));
1187
1153
  }
1188
1154
  };
1155
+ var Plugin = class {
1156
+ static {
1157
+ __name(this, "Plugin");
1158
+ }
1159
+ pluggable;
1160
+ constructor(pluggable) {
1161
+ this.pluggable = pluggable;
1162
+ }
1163
+ };
1164
+ var Pluggable = class _Pluggable {
1165
+ static {
1166
+ __name(this, "Pluggable");
1167
+ }
1168
+ isInitialized = false;
1169
+ shutdown;
1170
+ startup;
1171
+ static PLUGIN_INIT_TIMEOUT_MS = 15e3;
1172
+ constructor(shutdown, startup) {
1173
+ this.shutdown = shutdown;
1174
+ this.startup = startup;
1175
+ }
1176
+ async init() {
1177
+ if (this.isInitialized) return this;
1178
+ await this.startup.run();
1179
+ this.isInitialized = true;
1180
+ return this;
1181
+ }
1182
+ /**
1183
+ * Attaches a plugin to this instance
1184
+ *
1185
+ * Plugins provide external functionality and are initialized during the specified startup phase.
1186
+ * The plugin instance becomes available as a property in `core` wherever it's available.
1187
+ *
1188
+ * Make sure to augment the {@link Core} interface with the plugin type to ensure TypeScript recognizes it and provides intellisense.
1189
+ *
1190
+ * @typeParam Key - The property name for accessing the plugin
1191
+ * @typeParam Ctor - The plugin constructor type
1192
+ * @param key - Property name to access the plugin instance
1193
+ * @param Plugin - Plugin constructor class
1194
+ * @param startupPhase - When during startup to initialize this plugin ({@link StartupPhase})
1195
+ * @param args - Additional arguments to pass to the plugin constructor
1196
+ * @returns This instance with the plugin attached as a typed property
1197
+ * @throws An {@link Error} When called after initialization or if key already exists
1198
+ * @example
1199
+ * ```typescript
1200
+ * seedcord.attach('db', Mongo, StartupPhase.Configuration, { uri: 'mongodb://...', name: 'seedcord', dir: ... })
1201
+ * ```
1202
+ */
1203
+ attach(key, Plugin2, startupPhase, ...args) {
1204
+ if (this.isInitialized) throw new Error("Cannot attach a plugin after initialization.");
1205
+ if (this[key]) throw new Error(`Plugin with key "${key}" already exists.`);
1206
+ const instance = new Plugin2(this, ...args);
1207
+ const entry = {
1208
+ [key]: instance
1209
+ };
1210
+ this.startup.addTask(startupPhase, `Plugin:${key}`, async () => {
1211
+ instance.logger.info(chalk3__default.default.bold("Initializing"));
1212
+ await instance.init();
1213
+ instance.logger.info(chalk3__default.default.bold("Initialized"));
1214
+ }, _Pluggable.PLUGIN_INIT_TIMEOUT_MS);
1215
+ return Object.assign(this, entry);
1216
+ }
1217
+ };
1189
1218
  var EmojiInjector = class {
1190
1219
  static {
1191
1220
  __name(this, "EmojiInjector");
@@ -1197,7 +1226,7 @@ var EmojiInjector = class {
1197
1226
  }
1198
1227
  async init() {
1199
1228
  if (!this.core.config.bot.emojis || Object.keys(this.core.config.bot.emojis).length === 0) {
1200
- this.logger.info(`${chalk4__default.default.bold.green("Loaded")}: ${chalk4__default.default.magenta.bold("0")} emojis`);
1229
+ this.logger.info(`${chalk3__default.default.bold.green("Loaded")}: ${chalk3__default.default.magenta.bold("0")} emojis`);
1201
1230
  return;
1202
1231
  }
1203
1232
  const configEmojis = this.core.config.bot.emojis;
@@ -1208,10 +1237,10 @@ var EmojiInjector = class {
1208
1237
  if (emoji) {
1209
1238
  configEmojis[key] = `<${emoji.identifier}>`;
1210
1239
  foundCount++;
1211
- this.logger.debug(`${chalk4__default.default.bold.green("Found")}: ${chalk4__default.default.magenta.bold(emojiName)} (${emoji.id})`);
1240
+ this.logger.debug(`${chalk3__default.default.bold.green("Found")}: ${chalk3__default.default.magenta.bold(emojiName)} (${emoji.id})`);
1212
1241
  }
1213
1242
  });
1214
- this.logger.info(`${chalk4__default.default.bold.green("Loaded")}: ${chalk4__default.default.magenta.bold(foundCount)} emojis`);
1243
+ this.logger.info(`${chalk3__default.default.bold.green("Loaded")}: ${chalk3__default.default.magenta.bold(foundCount)} emojis`);
1215
1244
  }
1216
1245
  };
1217
1246
 
@@ -1277,7 +1306,7 @@ var Bot = class extends Plugin {
1277
1306
  */
1278
1307
  async login() {
1279
1308
  await this._client.login(this.botToken);
1280
- this.logger.info(`Logged in as ${chalk4__default.default.bold.magenta(this._client.user?.username)}!`);
1309
+ this.logger.info(`Logged in as ${chalk3__default.default.bold.magenta(this._client.user?.username)}!`);
1281
1310
  return this;
1282
1311
  }
1283
1312
  /**
@@ -1285,7 +1314,7 @@ var Bot = class extends Plugin {
1285
1314
  */
1286
1315
  async logout() {
1287
1316
  await this._client.destroy();
1288
- this.logger.info(chalk4__default.default.bold.red("Logged out of Discord!"));
1317
+ this.logger.info(chalk3__default.default.bold.red("Logged out of Discord!"));
1289
1318
  }
1290
1319
  get client() {
1291
1320
  return this._client;
@@ -1303,14 +1332,18 @@ _ts_decorate4([
1303
1332
 
1304
1333
  // src/effects/decorators/RegisterEffect.ts
1305
1334
  var EffectMetadataKey = Symbol("effect:metadata");
1306
- function RegisterEffect(effect) {
1335
+ function RegisterEffect(effect, options) {
1307
1336
  return function(constructor) {
1308
- Reflect.defineMetadata(EffectMetadataKey, effect, constructor);
1337
+ const meta = {
1338
+ effect,
1339
+ frequency: options?.frequency
1340
+ };
1341
+ Reflect.defineMetadata(EffectMetadataKey, meta, constructor);
1309
1342
  };
1310
1343
  }
1311
1344
  __name(RegisterEffect, "RegisterEffect");
1312
1345
 
1313
- // src/effects/interfaces/EffectsHandler.ts
1346
+ // src/effects/EffectsHandler.ts
1314
1347
  var EffectsHandler = class {
1315
1348
  static {
1316
1349
  __name(this, "EffectsHandler");
@@ -1331,7 +1364,7 @@ var EffectsHandler = class {
1331
1364
  }
1332
1365
  };
1333
1366
 
1334
- // src/effects/interfaces/abstracts/WebhookLog.ts
1367
+ // src/effects/bases/WebhookLog.ts
1335
1368
  var WebhookLog = class extends EffectsHandler {
1336
1369
  static {
1337
1370
  __name(this, "WebhookLog");
@@ -1507,51 +1540,56 @@ var EffectsRegistry = class extends Plugin {
1507
1540
  if (this.isInitialized) return;
1508
1541
  this.isInitialized = true;
1509
1542
  const effectsDir = this.core.config.effects.path;
1510
- this.logger.info(chalk4__default.default.bold(effectsDir));
1511
- this.registerEffect("unknownException", exports.UnknownException);
1543
+ this.logger.info(chalk3__default.default.bold(effectsDir));
1544
+ this.registerEffect(exports.UnknownException, {
1545
+ effect: "unknownException",
1546
+ frequency: "on"
1547
+ });
1512
1548
  await this.loadEffects(effectsDir);
1513
1549
  this.attachEffects();
1514
1550
  const totalEffects = Array.from(this.effectsMap.values()).reduce((acc, handlers) => acc + handlers.length, 0);
1515
- this.logger.info(`${chalk4__default.default.bold.green("Loaded")}: ${chalk4__default.default.bold.magenta(totalEffects)} side effects`);
1551
+ this.logger.info(`${chalk3__default.default.bold.green("Loaded")}: ${chalk3__default.default.bold.magenta(totalEffects)} side effects`);
1516
1552
  }
1517
1553
  async loadEffects(dir) {
1518
1554
  await utils.traverseDirectory(dir, (_fullPath, relativePath, imported) => {
1519
1555
  for (const exportName of Object.keys(imported)) {
1520
1556
  const val = imported[exportName];
1521
1557
  if (this.isEffectHandler(val)) {
1522
- const effectName = Reflect.getMetadata(EffectMetadataKey, val);
1523
- if (effectName) {
1524
- this.registerEffect(effectName, val);
1525
- this.logger.info(`${chalk4__default.default.italic("Registered")} ${chalk4__default.default.bold.yellow(val.name)} from ${chalk4__default.default.gray(relativePath)}`);
1526
- }
1558
+ const meta = Reflect.getMetadata(EffectMetadataKey, val);
1559
+ this.registerEffect(val, meta);
1560
+ this.logger.info(`${chalk3__default.default.italic("Registered")} ${chalk3__default.default.bold.yellow(val.name)} from ${chalk3__default.default.gray(relativePath)}`);
1527
1561
  }
1528
1562
  }
1529
1563
  }, this.logger);
1530
1564
  }
1531
- registerEffect(effectName, handler) {
1532
- let handlers = this.effectsMap.get(effectName);
1565
+ registerEffect(handler, options) {
1566
+ let handlers = this.effectsMap.get(options.effect);
1533
1567
  if (!handlers) {
1534
1568
  handlers = [];
1535
- this.effectsMap.set(effectName, handlers);
1569
+ this.effectsMap.set(options.effect, handlers);
1536
1570
  }
1537
- handlers.push(handler);
1571
+ handlers.push({
1572
+ ctor: handler,
1573
+ frequency: options.frequency ?? "on"
1574
+ });
1538
1575
  }
1539
1576
  isEffectHandler(obj) {
1540
1577
  if (typeof obj !== "function") return false;
1541
- return obj.prototype instanceof EffectsHandler;
1578
+ return obj.prototype instanceof EffectsHandler && Reflect.hasMetadata(EffectMetadataKey, obj);
1542
1579
  }
1543
1580
  attachEffects() {
1544
- for (const [effectName, handlerCtors] of this.effectsMap) {
1545
- this.emitter.on(effectName, (data) => {
1546
- for (const HandlerCtor of handlerCtors) {
1581
+ for (const [effectName, handlerEntries] of this.effectsMap) {
1582
+ for (const entry of handlerEntries) {
1583
+ const register = entry.frequency === "once" ? this.emitter.once.bind(this.emitter) : this.emitter.on.bind(this.emitter);
1584
+ register(effectName, (data) => {
1547
1585
  try {
1548
- const instance = new HandlerCtor(data, this.core);
1586
+ const instance = new entry.ctor(data, this.core);
1549
1587
  void instance.execute();
1550
1588
  } catch (err) {
1551
- this.logger.error(`Error in side effect ${String(effectName)} handler ${HandlerCtor.name}:`, err);
1589
+ this.logger.error(`Error in side effect ${String(effectName)} handler ${entry.ctor.name}:`, err);
1552
1590
  }
1553
- }
1554
- });
1591
+ });
1592
+ }
1555
1593
  }
1556
1594
  }
1557
1595
  emit(event, data) {
@@ -1603,19 +1641,19 @@ var Seedcord = class _Seedcord extends Pluggable {
1603
1641
  */
1604
1642
  registerStartupTasks() {
1605
1643
  this.startup.addTask(services.StartupPhase.Configuration, "Effect Initialization", async () => {
1606
- this.effects.logger.info(chalk4__default.default.bold("Initializing"));
1644
+ this.effects.logger.info(chalk3__default.default.bold("Initializing"));
1607
1645
  await this.effects.init();
1608
- this.effects.logger.info(chalk4__default.default.bold("Initialized"));
1646
+ this.effects.logger.info(chalk3__default.default.bold("Initialized"));
1609
1647
  });
1610
1648
  this.startup.addTask(services.StartupPhase.Instantiation, "Bot Initialization", async () => {
1611
- this.bot.logger.info(chalk4__default.default.bold("Initializing"));
1649
+ this.bot.logger.info(chalk3__default.default.bold("Initializing"));
1612
1650
  await this.bot.init();
1613
- this.bot.logger.info(chalk4__default.default.bold("Initialized"));
1651
+ this.bot.logger.info(chalk3__default.default.bold("Initialized"));
1614
1652
  });
1615
1653
  this.startup.addTask(services.StartupPhase.Ready, "Health Check", async () => {
1616
- this.healthCheck.logger.info(chalk4__default.default.bold("Initializing"));
1654
+ this.healthCheck.logger.info(chalk3__default.default.bold("Initializing"));
1617
1655
  await this.healthCheck.init();
1618
- this.healthCheck.logger.info(chalk4__default.default.bold("Initialized"));
1656
+ this.healthCheck.logger.info(chalk3__default.default.bold("Initialized"));
1619
1657
  });
1620
1658
  }
1621
1659
  /**