vimcord 1.0.0 → 1.0.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
@@ -31,14 +31,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  BaseCommandBuilder: () => BaseCommandBuilder,
34
+ BetterCollector: () => BetterCollector,
34
35
  BetterContainer: () => BetterContainer,
35
36
  BetterEmbed: () => BetterEmbed,
36
37
  BetterModal: () => BetterModal,
37
38
  CLI: () => CLI,
39
+ CollectorTimeoutType: () => CollectorTimeoutType,
38
40
  CommandType: () => CommandType,
39
41
  ContextCommandBuilder: () => ContextCommandBuilder,
40
42
  DynaSend: () => DynaSend,
41
43
  EventBuilder: () => EventBuilder,
44
+ LOGGER_COLORS: () => LOGGER_COLORS,
42
45
  LogLevel: () => LogLevel,
43
46
  Logger: () => Logger,
44
47
  MissingPermissionReason: () => MissingPermissionReason,
@@ -67,6 +70,7 @@ __export(index_exports, {
67
70
  clientInstances: () => clientInstances,
68
71
  createClient: () => createClient,
69
72
  createMongoSchema: () => createMongoSchema,
73
+ createMongoSession: () => createMongoSession,
70
74
  createToolsConfig: () => createToolsConfig,
71
75
  createVimcordAppConfig: () => createVimcordAppConfig,
72
76
  createVimcordContextCommandConfig: () => createVimcordContextCommandConfig,
@@ -100,16 +104,17 @@ __export(index_exports, {
100
104
  useClient: () => useClient,
101
105
  useMongoDatabase: () => useMongoDatabase,
102
106
  useReadyClient: () => useReadyClient,
107
+ useReadyMongoDatabase: () => useReadyMongoDatabase,
103
108
  validateCommandPermissions: () => validateCommandPermissions
104
109
  });
105
110
  module.exports = __toCommonJS(index_exports);
106
111
 
107
112
  // src/types/command.base.ts
108
- var CommandType = /* @__PURE__ */ ((CommandType3) => {
109
- CommandType3[CommandType3["Slash"] = 0] = "Slash";
110
- CommandType3[CommandType3["Prefix"] = 1] = "Prefix";
111
- CommandType3[CommandType3["Context"] = 2] = "Context";
112
- return CommandType3;
113
+ var CommandType = /* @__PURE__ */ ((CommandType2) => {
114
+ CommandType2[CommandType2["Slash"] = 0] = "Slash";
115
+ CommandType2[CommandType2["Prefix"] = 1] = "Prefix";
116
+ CommandType2[CommandType2["Context"] = 2] = "Context";
117
+ return CommandType2;
113
118
  })(CommandType || {});
114
119
  var MissingPermissionReason = /* @__PURE__ */ ((MissingPermissionReason2) => {
115
120
  MissingPermissionReason2[MissingPermissionReason2["User"] = 0] = "User";
@@ -233,270 +238,239 @@ var import_lodash = __toESM(require("lodash"));
233
238
  var BaseCommandBuilder = class {
234
239
  uuid = (0, import_node_crypto.randomUUID)();
235
240
  commandType;
236
- client;
237
- enabled;
238
- conditions;
239
- permissions;
240
- metadata;
241
- rateLimit;
242
- beforeExecute;
243
- execute;
244
- afterExecute;
245
- onRateLimit;
246
- onMissingPermissions;
247
- onConditionsNotMet;
248
- onUsedWhenDisabled;
249
- onError;
250
- globalRateLimitData = { executions: 0, timestamp: 0 };
251
- userRateLimitData = /* @__PURE__ */ new Map();
252
- guildRateLimitData = /* @__PURE__ */ new Map();
253
- channelRateLimitData = /* @__PURE__ */ new Map();
254
- constructor(type, config) {
241
+ /** Local command configuration and hooks */
242
+ options;
243
+ /** Internal state for rate limiting across different scopes */
244
+ rlStores = {
245
+ [3 /* Global */]: { executions: 0, timestamp: 0 },
246
+ [0 /* User */]: /* @__PURE__ */ new Map(),
247
+ [1 /* Guild */]: /* @__PURE__ */ new Map(),
248
+ [2 /* Channel */]: /* @__PURE__ */ new Map()
249
+ };
250
+ /** * Mapping of CommandTypes to their respective config keys in the Vimcord client
251
+ */
252
+ typeConfigMapping = {
253
+ [0 /* Slash */]: "slashCommands",
254
+ [1 /* Prefix */]: "prefixCommands",
255
+ [2 /* Context */]: "contextCommands"
256
+ };
257
+ constructor(type, options = {}) {
255
258
  this.commandType = type;
256
- this.enabled = config?.enabled ?? true;
257
- this.conditions = config?.conditions;
258
- this.permissions = config?.permissions;
259
- this.metadata = config?.metadata;
260
- this.rateLimit = config?.rateLimit;
261
- this.beforeExecute = config?.beforeExecute;
262
- this.execute = config?.execute;
263
- this.afterExecute = config?.afterExecute;
264
- this.onRateLimit = config?.onRateLimit;
265
- this.onMissingPermissions = config?.onMissingPermissions;
266
- this.onConditionsNotMet = config?.onConditionsNotMet;
267
- this.onUsedWhenDisabled = config?.onUsedWhenDisabled;
268
- this.onError = config?.onError;
269
- this.validateBaseConfig();
259
+ this.options = { enabled: true, ...options };
270
260
  }
271
261
  validateBaseConfig() {
272
- if (this.rateLimit) {
273
- if (this.rateLimit.max <= 0) {
274
- throw new Error("DJSSlashCommandBuilder: Rate limit max must be greater than 0");
262
+ if (this.options.rateLimit) {
263
+ const { max, interval } = this.options.rateLimit;
264
+ if (max <= 0 || interval <= 0) {
265
+ throw new Error(`[Vimcord:${this.constructor.name}] Rate limit values must be positive.`);
275
266
  }
276
- if (this.rateLimit.interval <= 0) {
277
- throw new Error("DJSSlashCommandBuilder: Rate limit interval must be greater than 0");
267
+ }
268
+ }
269
+ /**
270
+ * Resolves the final configuration by merging layers:
271
+ * Client Defaults < Client Type-Specific < Local Command Options
272
+ */
273
+ resolveConfig(client) {
274
+ const typeKey = this.typeConfigMapping[this.commandType];
275
+ const typeSpecificGlobals = client.config?.[typeKey] || {};
276
+ return import_lodash.default.merge({}, typeSpecificGlobals, this.options);
277
+ }
278
+ /**
279
+ * Executes the command lifecycle.
280
+ * Merges global client config with local command options at runtime.
281
+ */
282
+ async run(client, ...args) {
283
+ const config = this.resolveConfig(client);
284
+ const ctx = this.extractContext(args);
285
+ try {
286
+ if (!config.enabled) {
287
+ return await config.onUsedWhenDisabled?.(...args);
288
+ }
289
+ if (this.isRateLimited(config, ctx)) {
290
+ return await config.onRateLimit?.(...args);
291
+ }
292
+ const perms = this.checkPermissions(client, ctx.member || ctx.user, args[1]);
293
+ if (!perms.validated) {
294
+ return await config.onMissingPermissions?.(perms, ...args);
278
295
  }
296
+ if (!await this.checkConditions(config, ...args)) {
297
+ return await config.onConditionsNotMet?.(...args);
298
+ }
299
+ await config.beforeExecute?.(...args);
300
+ if (config.logExecution !== false) {
301
+ const cmdName = this.options.name || this.builder?.name || "Unknown";
302
+ const location = ctx.guild ? `${ctx.guild.name} (${ctx.guild.id})` : "Direct Messages";
303
+ client.logger.commandExecuted(cmdName, ctx.user.username, location);
304
+ }
305
+ const result = await config.execute?.(...args);
306
+ await config.afterExecute?.(result, ...args);
307
+ } catch (error) {
308
+ await this.handleError(error, config, ...args);
279
309
  }
280
310
  }
281
- toConfig() {
311
+ /**
312
+ * Internal logic to determine if a command execution should be throttled.
313
+ * @param config The merged configuration to use for limits.
314
+ * @param ctx Extracted Discord context (User, Guild, Channel).
315
+ */
316
+ isRateLimited(config, ctx) {
317
+ if (!config.rateLimit) return false;
318
+ const { scope, interval, max } = config.rateLimit;
319
+ const now = Date.now();
320
+ const key = this.getScopeKey(scope, ctx);
321
+ if (scope !== 3 /* Global */ && !key) return false;
322
+ let data;
323
+ if (scope === 3 /* Global */) {
324
+ data = this.rlStores[3 /* Global */];
325
+ } else {
326
+ const store = this.rlStores[scope];
327
+ data = store.get(key) ?? { executions: 0, timestamp: now };
328
+ store.set(key, data);
329
+ }
330
+ if (now - data.timestamp > interval) {
331
+ data.executions = 0;
332
+ data.timestamp = now;
333
+ }
334
+ if (data.executions >= max) return true;
335
+ data.executions++;
336
+ return false;
337
+ }
338
+ /**
339
+ * Validates if the user has required permissions.
340
+ */
341
+ checkPermissions(client, user, target) {
342
+ if (!this.options.permissions) return { validated: true };
343
+ return validateCommandPermissions(this.options.permissions, client, user, target);
344
+ }
345
+ /**
346
+ * Evaluates all custom conditions defined for the command.
347
+ */
348
+ async checkConditions(config, ...args) {
349
+ if (!config.conditions?.length) return true;
350
+ const results = await Promise.all(config.conditions.map((c) => c(...args)));
351
+ return results.every(Boolean);
352
+ }
353
+ /**
354
+ * Normalizes the trigger arguments into a standard context object.
355
+ */
356
+ extractContext(args) {
357
+ const event = args[1];
282
358
  return {
283
- enabled: this.enabled,
284
- conditions: this.conditions,
285
- permissions: this.permissions,
286
- metadata: this.metadata,
287
- rateLimit: this.rateLimit,
288
- beforeExecute: this.beforeExecute,
289
- execute: this.execute,
290
- afterExecute: this.afterExecute,
291
- onMissingPermissions: this.onMissingPermissions,
292
- onConditionsNotMet: this.onConditionsNotMet,
293
- onUsedWhenDisabled: this.onUsedWhenDisabled,
294
- onRateLimit: this.onRateLimit,
295
- onError: this.onError
359
+ user: event.user || event.author,
360
+ member: event.member,
361
+ guild: event.guild,
362
+ channel: event.channel
296
363
  };
297
364
  }
365
+ /**
366
+ * Resolves the storage key based on the RateLimit scope.
367
+ */
368
+ getScopeKey(scope, ctx) {
369
+ switch (scope) {
370
+ case 0 /* User */:
371
+ return ctx.user.id;
372
+ case 1 /* Guild */:
373
+ return ctx.guild?.id ?? null;
374
+ case 2 /* Channel */:
375
+ return ctx.channel?.id ?? null;
376
+ default:
377
+ return null;
378
+ }
379
+ }
380
+ /**
381
+ * Handles command errors by checking local handlers before falling back to global handlers.
382
+ */
383
+ async handleError(err, config, ...args) {
384
+ if (config.onError) return config.onError(err, ...args);
385
+ throw err;
386
+ }
387
+ /** Toggle command availability */
298
388
  setEnabled(enabled) {
299
- this.enabled = enabled;
389
+ this.options.enabled = enabled;
300
390
  return this;
301
391
  }
392
+ /** Merge new permission requirements into the existing ones */
393
+ setPermissions(perms) {
394
+ this.options.permissions = import_lodash.default.merge(this.options.permissions || {}, perms);
395
+ return this;
396
+ }
397
+ /** Add custom logic checks that run before execution */
302
398
  addConditions(...conditions) {
303
- if (!this.conditions) this.conditions = [];
304
- this.conditions.push(...conditions);
399
+ this.options.conditions = [...this.options.conditions || [], ...conditions];
305
400
  return this;
306
401
  }
307
- setConditions(conditions) {
308
- this.conditions = conditions;
402
+ /** Set the primary command execution logic */
403
+ setExecute(fn) {
404
+ this.options.execute = fn;
309
405
  return this;
310
406
  }
311
- setPermissions(permissions) {
312
- this.permissions = import_lodash.default.merge(this.permissions, permissions);
407
+ /** * Set the custom conditions that must be met for this command to execute
408
+ */
409
+ setConditions(conditions) {
410
+ this.options.conditions = conditions;
313
411
  return this;
314
412
  }
413
+ /** * Set the command metadata configuration
414
+ */
315
415
  setMetadata(metadata) {
316
- this.metadata = import_lodash.default.merge(this.metadata, metadata);
416
+ this.options.metadata = import_lodash.default.merge(this.options.metadata || {}, metadata);
317
417
  return this;
318
418
  }
419
+ /** * Set the rate limiting options for this command
420
+ */
319
421
  setRateLimit(options) {
320
- this.rateLimit = options;
422
+ this.options.rateLimit = options;
423
+ this.validateBaseConfig();
321
424
  return this;
322
425
  }
323
- setBeforeExecute(callback) {
324
- this.beforeExecute = callback;
426
+ /** * Set whether to log whenever this command is executed
427
+ * @default true
428
+ */
429
+ setLogExecution(log) {
430
+ this.options.logExecution = log;
325
431
  return this;
326
432
  }
327
- setExecute(callback) {
328
- this.execute = callback;
433
+ /** * Set the function to execute before the main command logic
434
+ */
435
+ setBeforeExecute(callback) {
436
+ this.options.beforeExecute = callback;
329
437
  return this;
330
438
  }
439
+ /** * Set the function to execute after successful command execution
440
+ */
331
441
  setAfterExecute(callback) {
332
- this.afterExecute = callback;
333
- return this;
334
- }
335
- setOnRateLimit(callback) {
336
- this.onRateLimit = callback;
442
+ this.options.afterExecute = callback;
337
443
  return this;
338
444
  }
445
+ /** * Set the function to execute when the required permissions are not met
446
+ */
339
447
  setOnMissingPermissions(callback) {
340
- this.onMissingPermissions = callback;
448
+ this.options.onMissingPermissions = callback;
341
449
  return this;
342
450
  }
451
+ /** * Set the function to execute when the required conditions are not met
452
+ */
343
453
  setOnConditionsNotMet(callback) {
344
- this.onConditionsNotMet = callback;
454
+ this.options.onConditionsNotMet = callback;
345
455
  return this;
346
456
  }
457
+ /** * Set the function to execute when this command is used when its disabled
458
+ */
347
459
  setOnUsedWhenDisabled(callback) {
348
- this.onUsedWhenDisabled = callback;
460
+ this.options.onUsedWhenDisabled = callback;
349
461
  return this;
350
462
  }
351
- setOnError(callback) {
352
- this.onError = callback;
463
+ /** * Set the function to execute when the rate limit is hit
464
+ */
465
+ setOnRateLimit(callback) {
466
+ this.options.onRateLimit = callback;
353
467
  return this;
354
468
  }
355
- getRateLimitInfo(user, guild, channel) {
356
- if (!this.rateLimit) return null;
357
- let rateLimitData;
358
- switch (this.rateLimit.scope) {
359
- case 0 /* User */:
360
- if (!user) return null;
361
- rateLimitData = this.userRateLimitData.get(user.id) ?? { executions: 0, timestamp: 0 };
362
- break;
363
- case 1 /* Guild */:
364
- if (!guild) return null;
365
- rateLimitData = this.guildRateLimitData.get(guild.id) ?? { executions: 0, timestamp: 0 };
366
- break;
367
- case 2 /* Channel */:
368
- if (!channel) return null;
369
- rateLimitData = this.channelRateLimitData.get(channel.id) ?? { executions: 0, timestamp: 0 };
370
- break;
371
- case 3 /* Global */:
372
- rateLimitData = this.globalRateLimitData;
373
- break;
374
- default:
375
- return null;
376
- }
377
- return { ...rateLimitData, isLimited: this.isRateLimited(user, guild, channel, false) };
378
- }
379
- isRateLimited(user, guild, channel, updateExecutions = true) {
380
- if (!this.rateLimit) return false;
381
- const now = Date.now();
382
- let rateLimitData;
383
- switch (this.rateLimit.scope) {
384
- case 0 /* User */:
385
- if (!user) return false;
386
- rateLimitData = this.userRateLimitData.get(user.id) ?? { executions: 0, timestamp: 0 };
387
- if (now - rateLimitData.timestamp >= this.rateLimit.interval) {
388
- rateLimitData.executions = 0;
389
- rateLimitData.timestamp = now;
390
- }
391
- if (updateExecutions) rateLimitData.executions++;
392
- this.userRateLimitData.set(user.id, rateLimitData);
393
- break;
394
- case 1 /* Guild */:
395
- if (!guild) return false;
396
- rateLimitData = this.guildRateLimitData.get(guild.id) ?? { executions: 0, timestamp: 0 };
397
- if (now - rateLimitData.timestamp >= this.rateLimit.interval) {
398
- rateLimitData.executions = 0;
399
- rateLimitData.timestamp = now;
400
- }
401
- if (updateExecutions) rateLimitData.executions++;
402
- this.guildRateLimitData.set(guild.id, rateLimitData);
403
- break;
404
- case 2 /* Channel */:
405
- if (!channel) return false;
406
- rateLimitData = this.guildRateLimitData.get(channel.id) ?? { executions: 0, timestamp: 0 };
407
- if (now - rateLimitData.timestamp >= this.rateLimit.interval) {
408
- rateLimitData.executions = 0;
409
- rateLimitData.timestamp = now;
410
- }
411
- if (updateExecutions) rateLimitData.executions++;
412
- this.channelRateLimitData.set(channel.id, rateLimitData);
413
- break;
414
- case 3 /* Global */:
415
- rateLimitData = this.globalRateLimitData;
416
- if (now - rateLimitData.timestamp >= this.rateLimit.interval) {
417
- rateLimitData.executions = 0;
418
- rateLimitData.timestamp = now;
419
- }
420
- if (updateExecutions) rateLimitData.executions++;
421
- this.globalRateLimitData = rateLimitData;
422
- break;
423
- }
424
- return rateLimitData.executions >= this.rateLimit.max;
425
- }
426
- checkPermissions(client, user, command) {
427
- if (!this.permissions) return { validated: true };
428
- return validateCommandPermissions(this.permissions, client, user, command);
429
- }
430
- async checkConditions(...args) {
431
- if (!this.conditions?.length) return true;
432
- const results = await Promise.all(this.conditions.map((condition) => condition(...args)));
433
- return results.every(Boolean);
434
- }
435
- async _runPreflight(clientConfig, command, ...args) {
436
- try {
437
- if (!this.enabled) {
438
- await clientConfig.onUsedWhenDisabled?.(...args);
439
- await this.onUsedWhenDisabled?.(...args);
440
- return;
441
- }
442
- if (this.isRateLimited(args[1]?.member || args[1]?.author, args[1].guild, args[1].channel)) {
443
- await clientConfig.onRateLimit?.(...args);
444
- await this.rateLimit?.onRateLimit?.(...args);
445
- return;
446
- }
447
- const permissionResults = this.checkPermissions(
448
- args[0],
449
- args[1]?.member || args[1]?.author || args[1]?.user,
450
- command
451
- );
452
- if (!permissionResults.validated) {
453
- await clientConfig.onMissingPermissions?.(permissionResults, ...args);
454
- await this.onMissingPermissions?.(permissionResults, ...args);
455
- return;
456
- }
457
- if (!await this.checkConditions(...args)) {
458
- await clientConfig.onConditionsNotMet?.(...args);
459
- await this.onConditionsNotMet?.(...args);
460
- return;
461
- }
462
- } catch (err) {
463
- if (this.onError) {
464
- return this.onError(err, ...args);
465
- } else if (clientConfig.onError) {
466
- return clientConfig.onError(err, ...args);
467
- } else {
468
- throw err;
469
- }
470
- }
471
- }
472
- async _runCabinCheck(clientConfig, ...args) {
473
- try {
474
- await clientConfig.beforeExecute?.(...args);
475
- await this.beforeExecute?.(...args);
476
- } catch (err) {
477
- if (this.onError) {
478
- return this.onError(err, ...args);
479
- } else if (clientConfig.onError) {
480
- return clientConfig.onError(err, ...args);
481
- } else {
482
- throw err;
483
- }
484
- }
485
- }
486
- async _runFlyAndLand(clientConfig, ...args) {
487
- try {
488
- const result = await this.execute?.(...args);
489
- await clientConfig?.afterExecute?.(result, ...args);
490
- await this.afterExecute?.(result, ...args);
491
- } catch (err) {
492
- if (this.onError) {
493
- return this.onError(err, ...args);
494
- } else if (clientConfig.onError) {
495
- return clientConfig.onError(err, ...args);
496
- } else {
497
- throw err;
498
- }
499
- }
469
+ /** * Set a custom error handler for this command
470
+ */
471
+ setOnError(callback) {
472
+ this.options.onError = callback;
473
+ return this;
500
474
  }
501
475
  };
502
476
 
@@ -505,73 +479,48 @@ var import_discord2 = require("discord.js");
505
479
  var import_lodash2 = __toESM(require("lodash"));
506
480
  var ContextCommandBuilder = class extends BaseCommandBuilder {
507
481
  builder;
508
- deferReply;
509
- deployment;
510
482
  constructor(config) {
511
483
  super(2 /* Context */, config);
512
484
  this.setBuilder(config.builder);
513
- this.deferReply = config?.deferReply;
514
- this.deployment = config?.deployment;
515
- this.validateConfig();
485
+ const originalExecute = this.options.execute;
486
+ this.options.execute = async (client, interaction) => {
487
+ return await this.handleExecution(client, interaction, originalExecute);
488
+ };
516
489
  }
517
- validateBuilder() {
518
- if (!this.builder.name) {
519
- throw new Error("VContextCommandBuilder: Command name is required");
520
- }
521
- try {
522
- this.builder.toJSON();
523
- } catch (err) {
524
- throw new Error("VContextCommandBuilder: Invalid command builder", { cause: err });
490
+ async handleExecution(client, interaction, originalExecute) {
491
+ const config = this.resolveConfig(client);
492
+ if (config.deferReply && !interaction.replied && !interaction.deferred) {
493
+ await interaction.deferReply(typeof config.deferReply === "object" ? config.deferReply : void 0);
525
494
  }
495
+ return await originalExecute?.(client, interaction);
526
496
  }
527
- validateConfig() {
528
- }
529
- toConfig() {
530
- return {
531
- ...super.toConfig(),
532
- builder: this.builder,
533
- deferReply: this.deferReply,
534
- deployment: this.deployment
535
- };
497
+ validateBuilder() {
498
+ if (!this.builder.name) throw new Error(`[Vimcord] ContextCommandBuilder: Command name is required.`);
499
+ this.builder.toJSON();
536
500
  }
501
+ // --- Specialized Fluent API ---
537
502
  setBuilder(builder) {
538
- if (typeof builder === "function") {
539
- this.builder = builder(new import_discord2.ContextMenuCommandBuilder());
540
- } else {
541
- this.builder = builder;
542
- }
503
+ this.builder = typeof builder === "function" ? builder(new import_discord2.ContextMenuCommandBuilder()) : builder;
543
504
  this.validateBuilder();
544
505
  return this;
545
506
  }
546
507
  setDeferReply(defer) {
547
- this.deferReply = defer;
508
+ this.options.deferReply = defer;
548
509
  return this;
549
510
  }
550
511
  setDeployment(deployment) {
551
- this.deployment = import_lodash2.default.merge(this.deployment, deployment);
512
+ this.options.deployment = import_lodash2.default.merge(this.options.deployment || {}, deployment);
552
513
  return this;
553
514
  }
554
- async executeCommand(client, interaction) {
555
- try {
556
- await this._runPreflight(client.config.contextCommands, interaction, client, interaction);
557
- await this._runCabinCheck(client.config.contextCommands, client, interaction);
558
- if (this.deferReply && !interaction.replied && !interaction.deferred) {
559
- await interaction.deferReply(typeof this.deferReply === "object" ? this.deferReply : void 0);
560
- }
561
- return this._runFlyAndLand(client.config.contextCommands, client, interaction);
562
- } catch (err) {
563
- if (this.onError) {
564
- return this.onError(err, client, interaction);
565
- } else if (client.config.contextCommands.onError) {
566
- return client.config.contextCommands.onError(err, client, interaction);
567
- } else {
568
- throw err;
569
- }
570
- } finally {
571
- if (client.config.contextCommands.logExecution) {
572
- client.logger.commandExecuted(this.builder.name, interaction.user.username, interaction.guild?.name);
573
- }
574
- }
515
+ setExecute(fn) {
516
+ const originalExecute = fn;
517
+ this.options.execute = async (client, interaction) => {
518
+ return await this.handleExecution(client, interaction, originalExecute);
519
+ };
520
+ return this;
521
+ }
522
+ toConfig() {
523
+ return { ...this.options, builder: this.builder };
575
524
  }
576
525
  };
577
526
 
@@ -818,39 +767,74 @@ var EventBuilder = class _EventBuilder {
818
767
 
819
768
  // src/builders/prefixCommand.builder.ts
820
769
  var PrefixCommandBuilder = class extends BaseCommandBuilder {
821
- name;
822
- aliases;
823
- description;
824
770
  constructor(config) {
825
771
  super(1 /* Prefix */, config);
826
- this.name = config.name;
827
- this.description = config.description;
828
- this.validateConfig();
772
+ const originalExecute = this.options.execute;
773
+ this.options.execute = async (client, message) => {
774
+ return await this.handleExecution(client, message, originalExecute);
775
+ };
776
+ this.validatePrefixConfig();
777
+ }
778
+ /**
779
+ * Specialized execution logic for Prefix Commands.
780
+ */
781
+ async handleExecution(client, message, originalExecute) {
782
+ return await originalExecute?.(client, message);
783
+ }
784
+ validatePrefixConfig() {
785
+ if (!this.options.name) {
786
+ throw new Error(`[Vimcord] PrefixCommandBuilder: Command name is required.`);
787
+ }
788
+ }
789
+ // --- Fluent API (Prefix Specific Only) ---
790
+ /**
791
+ * Set the primary name of the command.
792
+ */
793
+ setName(name) {
794
+ this.options.name = name;
795
+ return this;
796
+ }
797
+ /**
798
+ * Set or replace the command aliases.
799
+ */
800
+ setAliases(aliases) {
801
+ this.options.aliases = aliases;
802
+ return this;
803
+ }
804
+ /**
805
+ * Add additional aliases without clearing existing ones.
806
+ */
807
+ addAliases(...aliases) {
808
+ this.options.aliases = [...this.options.aliases || [], ...aliases];
809
+ return this;
829
810
  }
830
- validateConfig() {
811
+ /**
812
+ * Set the command description.
813
+ */
814
+ setDescription(description) {
815
+ this.options.description = description;
816
+ return this;
831
817
  }
818
+ // --- Overrides ---
819
+ /**
820
+ * Override setExecute to ensure handleExecution remains the entry point.
821
+ * This is the only base method we need to redefine.
822
+ */
823
+ setExecute(fn) {
824
+ const originalExecute = fn;
825
+ this.options.execute = async (client, message) => {
826
+ return await this.handleExecution(client, message, originalExecute);
827
+ };
828
+ return this;
829
+ }
830
+ /**
831
+ * Converts the current builder state back into a config object.
832
+ */
832
833
  toConfig() {
833
834
  return {
834
- ...super.toConfig(),
835
- name: this.name,
836
- description: this.description
835
+ ...this.options
837
836
  };
838
837
  }
839
- async executeCommand(client, message) {
840
- try {
841
- await this._runPreflight(client.config.prefixCommands, this.name, client, message);
842
- await this._runCabinCheck(client.config.prefixCommands, client, message);
843
- return this._runFlyAndLand(client.config.prefixCommands, client, message);
844
- } catch (err) {
845
- this.onError?.(err, client, message);
846
- client.config.prefixCommands.onError?.(err, client, message);
847
- throw err;
848
- } finally {
849
- if (client.config.prefixCommands.logExecution) {
850
- client.logger.commandExecuted(this.name, message.author.username, message.guild?.name);
851
- }
852
- }
853
- }
854
838
  };
855
839
 
856
840
  // src/builders/slashCommand.builder.ts
@@ -858,110 +842,81 @@ var import_discord3 = require("discord.js");
858
842
  var import_lodash4 = __toESM(require("lodash"));
859
843
  var SlashCommandBuilder = class extends BaseCommandBuilder {
860
844
  builder;
861
- deferReply;
862
- deployment;
863
845
  routes = /* @__PURE__ */ new Map();
864
- onUnknownRouteHandler;
865
846
  constructor(config) {
866
847
  super(0 /* Slash */, config);
867
848
  this.setBuilder(config.builder);
868
- this.setRoutes(...config.routes ?? []);
869
- this.deferReply = config?.deferReply;
870
- this.deployment = config?.deployment;
871
- this.validateConfig();
849
+ if (config.routes) this.addRoutes(...config.routes);
850
+ const originalExecute = this.options.execute;
851
+ this.options.execute = async (client, interaction) => {
852
+ return await this.handleExecution(client, interaction, originalExecute);
853
+ };
872
854
  }
873
- validateBuilder() {
874
- if (!this.builder.name) {
875
- throw new Error("VSlashCommandBuilder: Command name is required");
855
+ async handleExecution(client, interaction, originalExecute) {
856
+ const config = this.resolveConfig(client);
857
+ if (config.deferReply && !interaction.replied && !interaction.deferred) {
858
+ await interaction.deferReply(typeof config.deferReply === "object" ? config.deferReply : void 0);
876
859
  }
877
- if (!this.builder.description) {
878
- throw new Error("VSlashCommandBuilder: Command description is required");
860
+ const subCommand = interaction.options.getSubcommand(false);
861
+ if (subCommand) {
862
+ const handler = this.routes.get(subCommand.toLowerCase());
863
+ if (handler) return await handler(client, interaction);
864
+ if (config.onUnknownRouteHandler) return await config.onUnknownRouteHandler(client, interaction);
879
865
  }
880
- try {
881
- this.builder.toJSON();
882
- } catch (err) {
883
- throw new Error("VSlashCommandBuilder: Invalid command builder", { cause: err });
884
- }
885
- }
886
- validateConfig() {
866
+ return await originalExecute?.(client, interaction);
887
867
  }
888
- toConfig() {
889
- return {
890
- ...super.toConfig(),
891
- builder: this.builder,
892
- deferReply: this.deferReply,
893
- deployment: this.deployment,
894
- routes: this.routes.size ? Array.from(this.routes.entries()).map(([k, v]) => ({ name: k, handler: v })) : void 0,
895
- onUnknownRouteHandler: this.onUnknownRouteHandler
896
- };
868
+ validateBuilder() {
869
+ if (!this.builder.name) throw new Error(`[Vimcord] SlashCommandBuilder: Command name is required.`);
870
+ if (!this.builder.description) throw new Error(`[Vimcord] SlashCommandBuilder: Command description is required.`);
871
+ this.builder.toJSON();
897
872
  }
873
+ // --- Specialized Fluent API ---
898
874
  setBuilder(builder) {
899
- if (typeof builder === "function") {
900
- this.builder = builder(new import_discord3.SlashCommandBuilder());
901
- } else {
902
- this.builder = builder;
903
- }
875
+ this.builder = typeof builder === "function" ? builder(new import_discord3.SlashCommandBuilder()) : builder;
904
876
  this.validateBuilder();
905
877
  return this;
906
878
  }
907
879
  setDeferReply(defer) {
908
- this.deferReply = defer;
880
+ this.options.deferReply = defer;
909
881
  return this;
910
882
  }
911
883
  setDeployment(deployment) {
912
- this.deployment = import_lodash4.default.merge(this.deployment, deployment);
884
+ this.options.deployment = import_lodash4.default.merge(this.options.deployment || {}, deployment);
913
885
  return this;
914
886
  }
915
887
  setRoutes(...routes) {
916
- if (routes.length) {
917
- for (const route of routes) {
918
- this.routes.set(route.name.toLowerCase(), route.handler);
919
- }
920
- } else {
921
- this.routes = /* @__PURE__ */ new Map();
922
- }
888
+ this.routes.clear();
889
+ this.addRoutes(...routes);
923
890
  return this;
924
891
  }
925
892
  addRoutes(...routes) {
893
+ if (!this.options.routes) this.options.routes = [];
926
894
  for (const route of routes) {
927
- this.routes.set(route.name.toLowerCase(), route.handler);
895
+ const name = route.name.toLowerCase();
896
+ this.routes.set(name, route.handler);
897
+ const existingIndex = this.options.routes.findIndex((r) => r.name.toLowerCase() === name);
898
+ if (existingIndex > -1) this.options.routes[existingIndex] = route;
899
+ else this.options.routes.push(route);
928
900
  }
929
901
  return this;
930
902
  }
931
903
  setUnknownRouteHandler(handler) {
932
- this.onUnknownRouteHandler = handler;
904
+ this.options.onUnknownRouteHandler = handler;
933
905
  return this;
934
906
  }
935
- async executeCommand(client, interaction) {
936
- try {
937
- await this._runPreflight(client.config.slashCommands, interaction, client, interaction);
938
- await this._runCabinCheck(client.config.slashCommands, client, interaction);
939
- if (this.deferReply && !interaction.replied && !interaction.deferred) {
940
- await interaction.deferReply(typeof this.deferReply === "object" ? this.deferReply : void 0);
941
- }
942
- if (this.routes.size) {
943
- const subCommand = interaction.options.getSubcommand();
944
- const route = this.routes.get(subCommand);
945
- if (route) {
946
- const result = await route(client, interaction);
947
- await this.afterExecute?.(result, client, interaction);
948
- return result;
949
- } else if (this.onUnknownRouteHandler) {
950
- const result = await this.onUnknownRouteHandler?.(client, interaction);
951
- await this.afterExecute?.(result, client, interaction);
952
- return result;
953
- }
954
- }
955
- return this._runFlyAndLand(client.config.slashCommands, client, interaction);
956
- } catch (err) {
957
- this.onError?.(err, client, interaction);
958
- client.config.slashCommands.onError?.(err, client, interaction);
959
- throw err;
960
- } finally {
961
- if (client.config.slashCommands.logExecution) {
962
- client.logger.commandExecuted(this.builder.name, interaction.user.username, interaction.guild?.name);
963
- }
964
- }
907
+ setExecute(fn) {
908
+ const originalExecute = fn;
909
+ this.options.execute = async (client, interaction) => {
910
+ return await this.handleExecution(client, interaction, originalExecute);
911
+ };
912
+ return this;
913
+ }
914
+ toConfig() {
915
+ return {
916
+ ...this.options,
917
+ builder: this.builder,
918
+ routes: Array.from(this.routes.entries()).map(([name, handler]) => ({ name, handler }))
919
+ };
965
920
  }
966
921
  };
967
922
 
@@ -976,12 +931,19 @@ var globalVimcordToolsConfig = {
976
931
  embedColor: [],
977
932
  embedColorDev: [],
978
933
  timeouts: {
934
+ collectorTimeout: 6e4,
935
+ collectorIdle: 6e4,
979
936
  pagination: 6e4,
980
937
  prompt: 3e4,
981
938
  modalSubmit: 6e4
982
939
  },
940
+ collector: {
941
+ notAParticipantMessage: "You are not allowed to use this.",
942
+ userLockMessage: "Please wait until your current action is finished.",
943
+ notAParticipantWarningCooldown: 5e3
944
+ },
983
945
  paginator: {
984
- notAParticipantMessage: "You are not allowed to use this!",
946
+ notAParticipantMessage: "You are not allowed to use this.",
985
947
  jumpableThreshold: 5,
986
948
  longThreshold: 4,
987
949
  buttons: {
@@ -994,7 +956,7 @@ var globalVimcordToolsConfig = {
994
956
  },
995
957
  prompt: {
996
958
  defaultTitle: "Are you sure?",
997
- defaultDescription: "Make sure you know what you're doing!",
959
+ defaultDescription: "Make sure you know what you're doing.",
998
960
  confirmLabel: "Confirm",
999
961
  rejectLabel: "Cancel"
1000
962
  }
@@ -1084,7 +1046,7 @@ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
1084
1046
  LogLevel2[LogLevel2["ERROR"] = 4] = "ERROR";
1085
1047
  return LogLevel2;
1086
1048
  })(LogLevel || {});
1087
- var DEFAULT_COLORS = {
1049
+ var LOGGER_COLORS = {
1088
1050
  primary: "#5865F2",
1089
1051
  success: "#57F287",
1090
1052
  warn: "#FEE75C",
@@ -1105,7 +1067,7 @@ var Logger = class {
1105
1067
  this.minLevel = minLevel;
1106
1068
  this.showTimestamp = showTimestamp;
1107
1069
  this.colorScheme = {
1108
- ...DEFAULT_COLORS,
1070
+ ...LOGGER_COLORS,
1109
1071
  ...options?.colors
1110
1072
  };
1111
1073
  }
@@ -1166,7 +1128,7 @@ var Logger = class {
1166
1128
  }
1167
1129
  setColors(colors) {
1168
1130
  this.colorScheme = {
1169
- ...DEFAULT_COLORS,
1131
+ ...LOGGER_COLORS,
1170
1132
  ...colors
1171
1133
  };
1172
1134
  return this;
@@ -1561,236 +1523,187 @@ var VimcordStatusManager = class {
1561
1523
 
1562
1524
  // src/modules/command.manager.ts
1563
1525
  var import_discord5 = require("discord.js");
1564
- var VimcordCommandManager = class {
1565
- slash;
1566
- prefix;
1567
- context;
1568
- constructor(client) {
1569
- this.slash = new VimcordSlashCommandManager(client);
1570
- this.prefix = new VimcordPrefixCommandManager(client);
1571
- this.context = new VimcordContextCommandManager(client);
1572
- }
1573
- };
1574
- var VimcordSlashCommandManager = class {
1575
- constructor(client) {
1526
+ var VimcordAppCommandManager = class {
1527
+ constructor(client, typeName) {
1576
1528
  this.client = client;
1577
- client.whenReady().then((c) => this.rest = new import_discord5.REST().setToken(c.token));
1529
+ this.typeName = typeName;
1530
+ this.client.whenReady().then((c) => this.rest = new import_discord5.REST().setToken(c.token));
1578
1531
  }
1579
1532
  commands = /* @__PURE__ */ new Map();
1580
1533
  rest;
1581
1534
  get(name) {
1582
1535
  return this.commands.get(name);
1583
1536
  }
1537
+ /**
1538
+ * Filters and returns commands based on deployment options
1539
+ */
1584
1540
  getAll(options) {
1585
1541
  const matchedCommands = /* @__PURE__ */ new Map();
1542
+ const isDev = this.client.config.app.devMode;
1586
1543
  for (const cmd of this.commands.values()) {
1587
- let matched = null;
1588
- if (options?.names || options?.fuzzyNames || options?.guilds) {
1589
- const nameMatched = (
1590
- // Exact name match
1591
- options.names?.includes(cmd.builder.name) || // Fuzzy match
1592
- options.fuzzyNames?.some((fuzzyCommand) => cmd.builder.name.includes(fuzzyCommand))
1593
- );
1594
- if ((options.names || options.fuzzyNames) && !nameMatched) continue;
1595
- const guildMatched = cmd.deployment?.guilds?.some((guildId) => options.guilds?.includes(guildId));
1596
- if (options.guilds && !guildMatched) continue;
1597
- matched = cmd;
1598
- } else {
1599
- matched = cmd;
1544
+ const config = cmd.toConfig();
1545
+ const name = cmd.builder.name;
1546
+ if (options?.names || options?.fuzzyNames) {
1547
+ const nameMatched = options.names?.includes(name) || options.fuzzyNames?.some((fuzzy) => name.includes(fuzzy));
1548
+ if (!nameMatched) continue;
1600
1549
  }
1601
- if (!matched) continue;
1602
1550
  if (options?.ignoreDeploymentOptions) {
1603
- if (matchedCommands.has(matched.builder.name)) continue;
1604
- matchedCommands.set(matched.builder.name, matched);
1551
+ matchedCommands.set(name, cmd);
1605
1552
  continue;
1606
1553
  }
1607
- const isProperEnvironment = matched?.deployment?.environments?.includes(
1608
- this.client.config.app.devMode ? "development" : "production"
1609
- );
1610
- if ((isProperEnvironment ?? true) && ((options?.globalOnly && (matched?.deployment?.global ?? true)) ?? true)) {
1611
- matchedCommands.set(matched.builder.name, matched);
1612
- }
1554
+ const deployment = config.deployment || {};
1555
+ const isProperEnv = !deployment.environments || deployment.environments.includes(isDev ? "development" : "production");
1556
+ if (!isProperEnv) continue;
1557
+ if (options?.globalOnly && deployment.global === false) continue;
1558
+ matchedCommands.set(name, cmd);
1613
1559
  }
1614
1560
  return matchedCommands;
1615
1561
  }
1616
- /** Import command modules that end with `.slash` */
1617
- async importFrom(dir, replaceAll) {
1618
- if (replaceAll) {
1619
- this.commands.clear();
1562
+ async registerGlobal(options) {
1563
+ const client = await this.client.whenReady();
1564
+ const commands = Array.from(
1565
+ this.getAll({
1566
+ names: options?.commands,
1567
+ fuzzyNames: options?.fuzzyCommands,
1568
+ globalOnly: true
1569
+ }).values()
1570
+ ).map((cmd) => cmd.builder.toJSON());
1571
+ if (!commands.length) {
1572
+ console.log(`[${this.typeName}] No commands to register`);
1573
+ return;
1620
1574
  }
1621
- dir = Array.isArray(dir) ? dir : [dir];
1622
- const commandModules = await Promise.all(
1623
- dir.map((dir2) => importModulesFromDir(dir2, "slash"))
1624
- );
1625
- let importedCommands = 0;
1626
- for (const command of commandModules.flat()) {
1627
- const isProperEnvironment = command.module.default.deployment?.environments?.includes(
1628
- this.client.config.app.devMode ? "development" : "production"
1629
- );
1630
- if (isProperEnvironment ?? true) {
1631
- this.commands.set(command.module.default.builder.name, command.module.default);
1632
- importedCommands++;
1633
- }
1575
+ console.log(`[${this.typeName}] Registering ${commands.length} commands globally...`);
1576
+ try {
1577
+ await this.rest.put(import_discord5.Routes.applicationCommands(client.user.id), { body: commands });
1578
+ console.log(`[${this.typeName}] \u2714 Registered app commands globally`);
1579
+ } catch (err) {
1580
+ console.log(`[${this.typeName}] \u2716 Failed to register app commands globally`, err);
1634
1581
  }
1635
- this.client.logger.moduleLoaded("Slash Commands", importedCommands);
1636
- return this.commands;
1637
1582
  }
1638
1583
  async registerGuild(options) {
1639
1584
  const client = await this.client.whenReady();
1640
- const commandsToRegister = Array.from(
1585
+ const commands = Array.from(
1641
1586
  this.getAll({
1642
1587
  names: options?.commands,
1643
1588
  fuzzyNames: options?.fuzzyCommands
1644
1589
  }).values()
1645
1590
  ).map((cmd) => cmd.builder.toJSON());
1646
- if (!commandsToRegister.length) {
1647
- console.log("[SlashCommandManager] No commands to register");
1591
+ if (!commands.length) {
1592
+ console.log(`[${this.typeName}] No commands to register`);
1648
1593
  return;
1649
1594
  }
1650
- const guildIds = options?.guilds || client.guilds.cache.map((guild) => guild.id);
1651
- console.log(
1652
- `[SlashCommandManager] Registering ${commandsToRegister.length} app (/) ${commandsToRegister.length === 1 ? "command" : "commands"} for ${guildIds.length} ${guildIds.length === 1 ? "guild" : "guilds"}...`
1653
- );
1595
+ const guildIds = options?.guilds || client.guilds.cache.map((g) => g.id);
1596
+ console.log(`[${this.typeName}] Registering ${commands.length} commands for ${guildIds.length} guilds...`);
1654
1597
  await Promise.all(
1655
1598
  guildIds.map(
1656
- (guildId) => this.rest.put(import_discord5.Routes.applicationGuildCommands(client.user.id, guildId), {
1657
- body: commandsToRegister
1658
- }).then(
1659
- () => console.log(
1660
- `[SlashCommandManager] \u2714 Set app (/) commands in guild: ${guildId} (${client.guilds.cache.find((g) => g.id === guildId)?.name || "n/a"})`
1661
- )
1662
- ).catch(
1663
- (err) => console.log(
1664
- `[SlashCommandManager] \u2716 Failed to set app (/) commands in guild: ${guildId} (${client.guilds.cache.find((g) => g.id === guildId)?.name || "n/a"})`,
1665
- err
1666
- )
1667
- )
1599
+ (guildId) => this.rest.put(import_discord5.Routes.applicationGuildCommands(client.user.id, guildId), { body: commands }).then(() => {
1600
+ const gName = client.guilds.cache.get(guildId)?.name || "n/a";
1601
+ console.log(`[${this.typeName}] \u2714 Set app commands in guild: ${guildId} (${gName})`);
1602
+ }).catch((err) => {
1603
+ const gName = client.guilds.cache.get(guildId)?.name || "n/a";
1604
+ console.log(`[${this.typeName}] \u2716 Failed to set app commands in guild: ${guildId} (${gName})`, err);
1605
+ })
1668
1606
  )
1669
1607
  );
1670
- console.log(
1671
- `[SlashCommandManager] \u2714 Finished registering app (/) commands for ${guildIds.length} ${guildIds.length === 1 ? "guild" : "guilds"}`
1672
- );
1673
1608
  }
1674
1609
  async unregisterGuild(options) {
1675
1610
  const client = await this.client.whenReady();
1676
- const guildIds = options?.guilds || client.guilds.cache.map((guild) => guild.id);
1677
- console.log(
1678
- `[SlashCommandManager] Unregistering app (/) commands from ${guildIds.length} ${guildIds.length === 1 ? "guild" : "guilds"}...`
1679
- );
1611
+ const guildIds = options?.guilds || client.guilds.cache.map((g) => g.id);
1612
+ console.log(`[${this.typeName}] Unregistering commands from ${guildIds.length} guilds...`);
1680
1613
  await Promise.all(
1681
1614
  guildIds.map(
1682
- (guildId) => this.rest.put(import_discord5.Routes.applicationGuildCommands(client.user.id, guildId), {
1683
- body: []
1684
- }).then(
1685
- () => console.log(
1686
- `[SlashCommandManager] \u2714 Removed app (/) commands in guild: ${guildId} (${client.guilds.cache.find((g) => g.id === guildId)?.name || "n/a"})`
1687
- )
1688
- ).catch(
1689
- (err) => console.log(
1690
- `[SlashCommandManager] \u2716 Failed to remove app (/) commands in guild: ${guildId} (${client.guilds.cache.find((g) => g.id === guildId)?.name || "n/a"})`,
1691
- err
1692
- )
1615
+ (guildId) => this.rest.put(import_discord5.Routes.applicationGuildCommands(client.user.id, guildId), { body: [] }).then(() => console.log(`[${this.typeName}] \u2714 Removed app commands in guild: ${guildId}`)).catch(
1616
+ (err) => console.log(`[${this.typeName}] \u2716 Failed to remove app commands in guild: ${guildId}`, err)
1693
1617
  )
1694
1618
  )
1695
1619
  );
1696
- console.log(
1697
- `[SlashCommandManager] \u2714 Finished unregistering app (/) commands for ${guildIds.length} ${guildIds.length === 1 ? "guild" : "guilds"}`
1698
- );
1699
- }
1700
- async registerGlobal(options) {
1701
- const client = await this.client.whenReady();
1702
- const commandsToRegister = Array.from(
1703
- this.getAll({
1704
- names: options?.commands,
1705
- fuzzyNames: options?.fuzzyCommands,
1706
- globalOnly: true
1707
- }).values()
1708
- ).map((cmd) => cmd.builder.toJSON());
1709
- if (!commandsToRegister.length) {
1710
- console.log("[SlashCommandManager] No commands to register");
1711
- return;
1712
- }
1713
- console.log(
1714
- `[SlashCommandManager] Registering ${commandsToRegister.length} app (/) ${commandsToRegister.length === 1 ? "command" : "commands"} globally...`
1715
- );
1716
- try {
1717
- await this.rest.put(import_discord5.Routes.applicationCommands(client.user.id), { body: commandsToRegister });
1718
- console.log("[SlashCommandManager] \u2714 Registered app (/) commands globally");
1719
- } catch (err) {
1720
- console.log("[SlashCommandManager] \u2716 Failed to register app (/) commands globally", err);
1721
- }
1722
1620
  }
1723
1621
  async unregisterGlobal() {
1724
1622
  const client = await this.client.whenReady();
1725
- console.log("[SlashCommandManager] Unregistering app (/) commands globally...");
1726
1623
  try {
1727
1624
  await this.rest.put(import_discord5.Routes.applicationCommands(client.user.id), { body: [] });
1728
- console.log("[SlashCommandManager] \u2714 Removed app (/) commands globally");
1625
+ console.log(`[${this.typeName}] \u2714 Removed app commands globally`);
1729
1626
  } catch (err) {
1730
- console.log("[SlashCommandManager] \u2716 Failed to remove app (/) commands globally", err);
1627
+ console.log(`[${this.typeName}] \u2716 Failed to remove app commands globally`, err);
1731
1628
  }
1732
1629
  }
1733
1630
  };
1734
- var VimcordPrefixCommandManager = class {
1631
+ var VimcordSlashCommandManager = class extends VimcordAppCommandManager {
1735
1632
  constructor(client) {
1736
- this.client = client;
1737
- }
1738
- commands = /* @__PURE__ */ new Map();
1739
- get(name) {
1740
- if (this.client.config.prefixCommands.allowCaseInsensitiveCommandNames) {
1741
- name = name.toLowerCase();
1742
- return Array.from(this.commands.values()).find((cmd) => cmd.name.toLowerCase() === name);
1743
- }
1744
- return this.commands.get(name);
1745
- }
1746
- getByAlias(alias) {
1747
- if (this.client.config.prefixCommands.allowCaseInsensitiveCommandNames) {
1748
- alias = alias.toLowerCase();
1749
- return Array.from(this.commands.values()).find((cmd) => cmd.aliases?.includes(alias));
1750
- }
1751
- return Array.from(this.commands.values()).find((cmd) => cmd.aliases?.includes(alias));
1633
+ super(client, "SlashCommandManager");
1634
+ }
1635
+ async importFrom(dir, replaceAll = false) {
1636
+ if (replaceAll) this.commands.clear();
1637
+ const dirs = Array.isArray(dir) ? dir : [dir];
1638
+ const modules = (await Promise.all(
1639
+ dirs.map((d) => importModulesFromDir(d, "slash"))
1640
+ )).flat();
1641
+ for (const { module: module2 } of modules) {
1642
+ this.commands.set(module2.default.builder.name, module2.default);
1643
+ }
1644
+ this.client.logger.moduleLoaded("Slash Commands", modules.length);
1645
+ return this.commands;
1752
1646
  }
1753
- /** Import command modules that end with `.prefix` */
1754
- async importFrom(dir, replaceAll) {
1755
- if (replaceAll) {
1756
- this.commands.clear();
1757
- }
1758
- dir = Array.isArray(dir) ? dir : [dir];
1759
- const commandModules = await Promise.all(
1760
- dir.map((dir2) => importModulesFromDir(dir2, "prefix"))
1761
- );
1762
- let importedCommands = 0;
1763
- for (const command of commandModules.flat()) {
1764
- this.commands.set(command.module.default.name, command.module.default);
1765
- importedCommands++;
1766
- }
1767
- this.client.logger.moduleLoaded("Prefix Commands", importedCommands);
1647
+ };
1648
+ var VimcordContextCommandManager = class extends VimcordAppCommandManager {
1649
+ constructor(client) {
1650
+ super(client, "ContextCommandManager");
1651
+ }
1652
+ async importFrom(dir, replaceAll = false) {
1653
+ if (replaceAll) this.commands.clear();
1654
+ const dirs = Array.isArray(dir) ? dir : [dir];
1655
+ const modules = (await Promise.all(
1656
+ dirs.map((d) => importModulesFromDir(d, "ctx"))
1657
+ )).flat();
1658
+ for (const { module: module2 } of modules) {
1659
+ this.commands.set(module2.default.builder.name, module2.default);
1660
+ }
1661
+ this.client.logger.moduleLoaded("Context Commands", modules.length);
1768
1662
  return this.commands;
1769
1663
  }
1770
1664
  };
1771
- var VimcordContextCommandManager = class {
1665
+ var VimcordPrefixCommandManager = class {
1772
1666
  constructor(client) {
1773
1667
  this.client = client;
1774
1668
  }
1775
1669
  commands = /* @__PURE__ */ new Map();
1776
- /** Import command modules that end with `.ctx` */
1777
- async importFrom(dir, replaceAll) {
1778
- if (replaceAll) {
1779
- this.commands.clear();
1780
- }
1781
- dir = Array.isArray(dir) ? dir : [dir];
1782
- const commandModules = await Promise.all(
1783
- dir.map((dir2) => importModulesFromDir(dir2, "ctx"))
1784
- );
1785
- let importedCommands = 0;
1786
- for (const command of commandModules.flat()) {
1787
- this.commands.set(command.module.default.builder.name, command.module.default);
1788
- importedCommands++;
1670
+ resolve(trigger) {
1671
+ const config = this.client.config.prefixCommands;
1672
+ const search = config.allowCaseInsensitiveCommandNames ? trigger.toLowerCase() : trigger;
1673
+ return Array.from(this.commands.values()).find((cmd) => {
1674
+ const opts = cmd.toConfig();
1675
+ const name = config.allowCaseInsensitiveCommandNames ? opts.name.toLowerCase() : opts.name;
1676
+ if (name === search) return true;
1677
+ return opts.aliases?.some(
1678
+ (a) => config.allowCaseInsensitiveCommandNames ? a.toLowerCase() === search : a === search
1679
+ );
1680
+ });
1681
+ }
1682
+ async importFrom(dir, replaceAll = false) {
1683
+ if (replaceAll) this.commands.clear();
1684
+ const dirs = Array.isArray(dir) ? dir : [dir];
1685
+ const modules = (await Promise.all(
1686
+ dirs.map(
1687
+ (d) => importModulesFromDir(d, "prefix")
1688
+ )
1689
+ )).flat();
1690
+ for (const { module: module2 } of modules) {
1691
+ this.commands.set(module2.default.toConfig().name, module2.default);
1789
1692
  }
1790
- this.client.logger.moduleLoaded("Context Commands", importedCommands);
1693
+ this.client.logger.moduleLoaded("Prefix Commands", modules.length);
1791
1694
  return this.commands;
1792
1695
  }
1793
1696
  };
1697
+ var VimcordCommandManager = class {
1698
+ slash;
1699
+ prefix;
1700
+ context;
1701
+ constructor(client) {
1702
+ this.slash = new VimcordSlashCommandManager(client);
1703
+ this.prefix = new VimcordPrefixCommandManager(client);
1704
+ this.context = new VimcordContextCommandManager(client);
1705
+ }
1706
+ };
1794
1707
 
1795
1708
  // src/modules/event.manager.ts
1796
1709
  var import_discord6 = require("discord.js");
@@ -2168,7 +2081,7 @@ var BetterEmbed = class _BetterEmbed {
2168
2081
  }
2169
2082
  if (this.data.description) {
2170
2083
  this.data.description = formatString(
2171
- Array.isArray(this.data.description) ? this.data.description.join("\n") : this.data.description
2084
+ Array.isArray(this.data.description) ? this.data.description.filter((s) => s !== null && s !== void 0).join("\n") : this.data.description
2172
2085
  );
2173
2086
  }
2174
2087
  if (this.data.footer && typeof this.data.footer === "object") {
@@ -2180,7 +2093,7 @@ var BetterEmbed = class _BetterEmbed {
2180
2093
  if (this.data.imageUrl) {
2181
2094
  this.data.imageUrl = formatString(this.data.imageUrl);
2182
2095
  }
2183
- this.data.fields = this.data.fields.map((field) => ({
2096
+ this.data.fields = this.data.fields.filter(Boolean).map((field) => ({
2184
2097
  ...field,
2185
2098
  name: formatString(field.name),
2186
2099
  value: formatString(field.value)
@@ -2405,7 +2318,7 @@ async function retryExponentialBackoff(fn, maxRetries = 3, retryDelay = 1e3) {
2405
2318
  }
2406
2319
 
2407
2320
  // package.json
2408
- var version = "1.0.0";
2321
+ var version = "1.0.1";
2409
2322
 
2410
2323
  // src/client.ts
2411
2324
  var import_node_crypto3 = require("crypto");
@@ -2671,62 +2584,63 @@ var Vimcord = class _Vimcord extends import_discord10.Client {
2671
2584
  return fetchGuild(client, id);
2672
2585
  }
2673
2586
  };
2674
- var defaultSlashCommandHandler = new EventBuilder({
2675
- event: "interactionCreate",
2676
- name: "SlashCommandHandler",
2677
- async execute(client, interaction) {
2678
- if (!interaction.isChatInputCommand()) return;
2679
- const command = client.commands.slash.get(interaction.commandName);
2680
- if (!command) {
2681
- return interaction.reply({
2682
- content: `**/\`${interaction.commandName}\`** is not a command`,
2683
- flags: "Ephemeral"
2684
- });
2685
- }
2686
- if (command.deferReply && !interaction.replied && !interaction.deferred) {
2687
- await interaction.deferReply(typeof command.deferReply === "object" ? command.deferReply : void 0);
2688
- }
2689
- try {
2690
- return command.executeCommand(client, interaction);
2691
- } catch (err) {
2692
- sendCommandErrorEmbed(client, err, interaction.guild, interaction);
2693
- throw err;
2694
- }
2695
- }
2696
- });
2697
2587
  var defaultPrefixCommandHandler = new EventBuilder({
2698
2588
  event: "messageCreate",
2699
2589
  name: "PrefixCommandHandler",
2700
2590
  async execute(client, message) {
2701
- if (message.author.bot) return;
2591
+ if (message.author.bot || !message.guild) return;
2592
+ const config = client.config.prefixCommands;
2593
+ let activePrefix = config.defaultPrefix;
2594
+ if (config.guildPrefixResolver) {
2595
+ try {
2596
+ const customPrefix = await config.guildPrefixResolver(client, message.guild.id);
2597
+ if (customPrefix) activePrefix = customPrefix;
2598
+ } catch (err) {
2599
+ client.logger.error(`Error in guildPrefixResolver for guild ${message.guild.id}:`, err);
2600
+ }
2601
+ }
2702
2602
  let prefixUsed;
2703
- if (!message.content.startsWith(client.config.prefixCommands.defaultPrefix)) {
2704
- if (client.config.prefixCommands.allowMentionAsPrefix) {
2705
- if (!message.content.startsWith(`${(0, import_discord10.userMention)(client.user.id)} `)) {
2706
- return;
2707
- } else {
2708
- prefixUsed = `${(0, import_discord10.userMention)(client.user.id)} `;
2709
- }
2710
- } else {
2711
- return;
2603
+ if (message.content.startsWith(activePrefix)) {
2604
+ prefixUsed = activePrefix;
2605
+ } else if (config.allowMentionAsPrefix) {
2606
+ const mention = (0, import_discord10.userMention)(client.user.id);
2607
+ if (message.content.startsWith(mention)) {
2608
+ prefixUsed = message.content.startsWith(`${mention} `) ? `${mention} ` : mention;
2712
2609
  }
2713
- } else {
2714
- prefixUsed = client.config.prefixCommands.defaultPrefix;
2715
2610
  }
2716
- message.content = message.content.slice(prefixUsed.length);
2717
- let commandName = message.content.split(" ")[0];
2718
- if (!commandName) {
2719
- return;
2611
+ if (!prefixUsed) return;
2612
+ const contentWithoutPrefix = message.content.slice(prefixUsed.length).trim();
2613
+ const args = contentWithoutPrefix.split(/\s+/);
2614
+ const trigger = args.shift();
2615
+ if (!trigger) return;
2616
+ const command = client.commands.prefix.resolve(trigger);
2617
+ if (!command) return;
2618
+ message.content = args.join(" ");
2619
+ try {
2620
+ return await command.run(client, client, message);
2621
+ } catch (err) {
2622
+ await sendCommandErrorEmbed(client, err, message.guild, message);
2623
+ throw err;
2720
2624
  }
2721
- const command = client.commands.prefix.get(commandName) || client.commands.prefix.getByAlias(commandName);
2625
+ }
2626
+ });
2627
+ var defaultSlashCommandHandler = new EventBuilder({
2628
+ event: "interactionCreate",
2629
+ name: "SlashCommandHandler",
2630
+ async execute(client, interaction) {
2631
+ if (!interaction.isChatInputCommand()) return;
2632
+ const command = client.commands.slash.get(interaction.commandName);
2722
2633
  if (!command) {
2723
- return;
2634
+ const content = `**/\`${interaction.commandName}\`** is not a registered command.`;
2635
+ if (interaction.replied || interaction.deferred) {
2636
+ return interaction.followUp({ content, flags: "Ephemeral" });
2637
+ }
2638
+ return interaction.reply({ content, flags: "Ephemeral" });
2724
2639
  }
2725
- message.content = message.content.slice(commandName.length + 1);
2726
2640
  try {
2727
- return command.executeCommand(client, message);
2641
+ return await command.run(client, client, interaction);
2728
2642
  } catch (err) {
2729
- sendCommandErrorEmbed(client, err, message.guild, message);
2643
+ await sendCommandErrorEmbed(client, err, interaction.guild, interaction);
2730
2644
  throw err;
2731
2645
  }
2732
2646
  }
@@ -2734,9 +2648,22 @@ var defaultPrefixCommandHandler = new EventBuilder({
2734
2648
  var defaultContextCommandHandler = new EventBuilder({
2735
2649
  event: "interactionCreate",
2736
2650
  name: "ContextCommandHandler",
2737
- execute(client, interaction) {
2651
+ async execute(client, interaction) {
2738
2652
  if (!interaction.isContextMenuCommand()) return;
2739
- interaction.reply({ content: "This handler is not yet implemented" });
2653
+ const command = client.commands.context.get(interaction.commandName);
2654
+ if (!command) {
2655
+ const content = `**${interaction.commandName}** is not a registered context command.`;
2656
+ if (interaction.replied || interaction.deferred) {
2657
+ return interaction.followUp({ content, flags: "Ephemeral" });
2658
+ }
2659
+ return interaction.reply({ content, flags: "Ephemeral" });
2660
+ }
2661
+ try {
2662
+ return await command.run(client, client, interaction);
2663
+ } catch (err) {
2664
+ await sendCommandErrorEmbed(client, err, interaction.guild, interaction);
2665
+ throw err;
2666
+ }
2740
2667
  }
2741
2668
  });
2742
2669
 
@@ -2751,8 +2678,8 @@ try {
2751
2678
  }
2752
2679
  var globalInstanceEmitter = new import_node_events2.default();
2753
2680
  var instances = [];
2754
- async function useMongoDatabase(index) {
2755
- const instance = instances.at(index ?? 0);
2681
+ async function useMongoDatabase(instanceIndex) {
2682
+ const instance = instances.at(instanceIndex ?? 0);
2756
2683
  if (!instance) {
2757
2684
  return new Promise((resolve, reject) => {
2758
2685
  const timeout = setTimeout(() => reject("useMongoDatabase timed out"), 45e3);
@@ -2764,6 +2691,15 @@ async function useMongoDatabase(index) {
2764
2691
  }
2765
2692
  return instance;
2766
2693
  }
2694
+ async function useReadyMongoDatabase(instanceIndex) {
2695
+ const instance = await useMongoDatabase(instanceIndex);
2696
+ await instance.waitForReady();
2697
+ return instance;
2698
+ }
2699
+ async function createMongoSession(instanceIndex, options) {
2700
+ const instance = await useReadyMongoDatabase(instanceIndex);
2701
+ return instance.mongoose.startSession(options);
2702
+ }
2767
2703
  var MongoDatabase = class {
2768
2704
  constructor(client, options) {
2769
2705
  this.client = client;
@@ -2918,6 +2854,7 @@ var MongoSchemaBuilder = class {
2918
2854
  this.eventEmitter.off(event, listener);
2919
2855
  return this;
2920
2856
  }
2857
+ /** Execute a function while ensuring the connection is ready. On error it will retry using an exponential backoff. */
2921
2858
  async execute(fn) {
2922
2859
  try {
2923
2860
  if (!this.isReady) {
@@ -2954,9 +2891,9 @@ var MongoSchemaBuilder = class {
2954
2891
  return id;
2955
2892
  });
2956
2893
  }
2957
- async count(filter) {
2894
+ async count(filter, options) {
2958
2895
  return await this.execute(async () => {
2959
- return this.model.countDocuments(filter);
2896
+ return this.model.countDocuments(filter, options);
2960
2897
  });
2961
2898
  }
2962
2899
  async exists(filter) {
@@ -2964,29 +2901,29 @@ var MongoSchemaBuilder = class {
2964
2901
  return await this.model.exists(filter) ? true : false;
2965
2902
  });
2966
2903
  }
2967
- async create(query) {
2904
+ async create(query, options) {
2968
2905
  return await this.execute(async () => {
2969
- return this.model.create(query);
2970
- });
2906
+ return this.model.create(query, options);
2907
+ }) ?? [];
2971
2908
  }
2972
2909
  async upsert(filter, query, options) {
2973
2910
  return await this.execute(async () => {
2974
2911
  return this.model.findOneAndUpdate(filter, query, { ...options, upsert: true, new: true });
2975
2912
  });
2976
2913
  }
2977
- async delete(filter) {
2914
+ async delete(filter, options) {
2978
2915
  return await this.execute(async () => {
2979
- return this.model.deleteOne(filter);
2916
+ return this.model.deleteOne(filter, options);
2980
2917
  });
2981
2918
  }
2982
- async deleteAll(filter) {
2919
+ async deleteAll(filter, options) {
2983
2920
  return await this.execute(async () => {
2984
- return this.model.deleteMany(filter);
2921
+ return this.model.deleteMany(filter, options);
2985
2922
  });
2986
2923
  }
2987
- async distinct(key, filter) {
2924
+ async distinct(key, filter, options) {
2988
2925
  return await this.execute(async () => {
2989
- return this.model.distinct(key, filter);
2926
+ return this.model.distinct(key, filter, options);
2990
2927
  });
2991
2928
  }
2992
2929
  async fetch(filter, projection, options) {
@@ -3017,6 +2954,215 @@ var MongoSchemaBuilder = class {
3017
2954
  }
3018
2955
  };
3019
2956
 
2957
+ // src/tools/BetterCollector.ts
2958
+ var CollectorTimeoutType = /* @__PURE__ */ ((CollectorTimeoutType2) => {
2959
+ CollectorTimeoutType2[CollectorTimeoutType2["DisableComponents"] = 0] = "DisableComponents";
2960
+ CollectorTimeoutType2[CollectorTimeoutType2["DeleteMessage"] = 1] = "DeleteMessage";
2961
+ CollectorTimeoutType2[CollectorTimeoutType2["DoNothing"] = 2] = "DoNothing";
2962
+ return CollectorTimeoutType2;
2963
+ })(CollectorTimeoutType || {});
2964
+ var BetterCollector = class _BetterCollector {
2965
+ message;
2966
+ collector;
2967
+ options;
2968
+ activeUsers = /* @__PURE__ */ new Set();
2969
+ participantWarningCooldowns = /* @__PURE__ */ new Map();
2970
+ config;
2971
+ events = {
2972
+ collectId: /* @__PURE__ */ new Map(),
2973
+ collect: [],
2974
+ end: [],
2975
+ timeout: []
2976
+ };
2977
+ static create(message, options) {
2978
+ return new _BetterCollector(message, options);
2979
+ }
2980
+ async validateParticipant(interaction, participants) {
2981
+ const allowedParticipants = participants || this.options?.participants || [];
2982
+ if (!allowedParticipants.length) return true;
2983
+ const isAllowed = allowedParticipants.some((user) => {
2984
+ if (typeof user === "string") return user === interaction.user.id;
2985
+ if (typeof user === "object" && "id" in user) return user.id === interaction.user.id;
2986
+ return false;
2987
+ });
2988
+ if (!isAllowed) {
2989
+ const now = Date.now();
2990
+ const lastWarned = this.participantWarningCooldowns.get(interaction.user.id) || 0;
2991
+ if (now - lastWarned > this.config.collector.notAParticipantWarningCooldown) {
2992
+ this.participantWarningCooldowns.set(interaction.user.id, now);
2993
+ if (this.config.collector.notAParticipantMessage) {
2994
+ await interaction.reply({
2995
+ content: this.config.collector.notAParticipantMessage,
2996
+ flags: "Ephemeral"
2997
+ }).catch(() => {
2998
+ });
2999
+ } else {
3000
+ await interaction.deferUpdate().catch(() => {
3001
+ });
3002
+ }
3003
+ } else {
3004
+ await interaction.deferUpdate().catch(() => {
3005
+ });
3006
+ }
3007
+ }
3008
+ return isAllowed;
3009
+ }
3010
+ build() {
3011
+ if (!this.message) return;
3012
+ if (this.collector) return;
3013
+ this.collector = this.message.createMessageComponentCollector({
3014
+ idle: this.options?.idle ?? this.config.timeouts.collectorIdle,
3015
+ time: this.options?.timeout ?? this.config.timeouts.collectorTimeout,
3016
+ componentType: this.options?.type,
3017
+ max: this.options?.max,
3018
+ maxComponents: this.options?.maxComponents,
3019
+ maxUsers: this.options?.maxUsers
3020
+ });
3021
+ this.setupListeners();
3022
+ }
3023
+ setupListeners() {
3024
+ if (!this.collector) return;
3025
+ this.collector.on("collect", async (interaction) => {
3026
+ if (this.options?.userLock && this.activeUsers.has(interaction.user.id)) {
3027
+ return interaction.reply({ content: this.config.collector.userLockMessage, flags: "Ephemeral" }).catch(() => {
3028
+ });
3029
+ }
3030
+ if (this.options?.userLock) {
3031
+ this.activeUsers.add(interaction.user.id);
3032
+ }
3033
+ const globalListeners = this.events.collect;
3034
+ const idListeners = this.events.collectId.get(interaction.customId) || [];
3035
+ const allListeners = [...globalListeners, ...idListeners];
3036
+ const shouldBeDeferred = allListeners.find((l) => l.options?.defer)?.options?.defer;
3037
+ const validListeners = [];
3038
+ for (const listener of allListeners) {
3039
+ const isAllowed = await this.validateParticipant(interaction, listener.options?.participants);
3040
+ if (isAllowed) validListeners.push(listener);
3041
+ }
3042
+ if (validListeners.length === 0) return;
3043
+ try {
3044
+ if (shouldBeDeferred) {
3045
+ if (typeof shouldBeDeferred === "object") {
3046
+ if (shouldBeDeferred.update) {
3047
+ await interaction.deferUpdate().catch(Boolean);
3048
+ } else {
3049
+ await interaction.deferReply({ ephemeral: shouldBeDeferred.ephemeral }).catch(Boolean);
3050
+ }
3051
+ } else {
3052
+ await interaction.deferReply().catch(Boolean);
3053
+ }
3054
+ }
3055
+ if (this.options?.sequential) {
3056
+ for (const listener of allListeners) {
3057
+ try {
3058
+ const isAllowed = await this.validateParticipant(interaction, listener.options?.participants);
3059
+ if (!isAllowed) return;
3060
+ await listener.fn(interaction);
3061
+ } catch (err) {
3062
+ this.handleListenerError(err);
3063
+ }
3064
+ }
3065
+ } else {
3066
+ Promise.all(
3067
+ allListeners.map((l) => {
3068
+ const isAllowed = this.validateParticipant(interaction, l.options?.participants);
3069
+ if (!isAllowed) return;
3070
+ return l.fn(interaction).catch(this.handleListenerError);
3071
+ })
3072
+ );
3073
+ }
3074
+ } finally {
3075
+ if (this.options?.userLock) {
3076
+ this.activeUsers.delete(interaction.user.id);
3077
+ }
3078
+ }
3079
+ });
3080
+ this.collector.on("end", async (collected, reason) => {
3081
+ if (this.options?.sequential) {
3082
+ for (const listener of this.events.end) {
3083
+ try {
3084
+ await listener.fn(collected, reason);
3085
+ } catch (err) {
3086
+ this.handleListenerError(err);
3087
+ }
3088
+ }
3089
+ } else {
3090
+ Promise.all(this.events.end.map((l) => l.fn(collected, reason).catch(this.handleListenerError)));
3091
+ }
3092
+ switch (this.options?.onTimeout) {
3093
+ case 0 /* DisableComponents */:
3094
+ if (!this.message?.editable) break;
3095
+ try {
3096
+ const disabledRows = this.message.components.map((row) => {
3097
+ const updatedRow = row.toJSON();
3098
+ if ("components" in updatedRow) {
3099
+ updatedRow.components = updatedRow.components.map((component) => ({
3100
+ ...component,
3101
+ disabled: true
3102
+ }));
3103
+ }
3104
+ return updatedRow;
3105
+ });
3106
+ await this.message.edit({ components: disabledRows });
3107
+ } catch (err) {
3108
+ if (!(err instanceof Error && err.message.includes("Unknown Message"))) {
3109
+ this.handleListenerError(err);
3110
+ }
3111
+ }
3112
+ break;
3113
+ case 1 /* DeleteMessage */:
3114
+ if (!this.message?.deletable) break;
3115
+ await this.message.delete().catch(Boolean);
3116
+ break;
3117
+ case 2 /* DoNothing */:
3118
+ default:
3119
+ break;
3120
+ }
3121
+ });
3122
+ }
3123
+ handleListenerError(err) {
3124
+ console.error("[BetterCollector] Listener Error:", err);
3125
+ }
3126
+ constructor(message, options) {
3127
+ this.config = options?.config || globalVimcordToolsConfig;
3128
+ this.message = message || void 0;
3129
+ this.options = options;
3130
+ this.build();
3131
+ }
3132
+ on(idOrFunc, fnOrOptions, options) {
3133
+ let finalFn;
3134
+ let finalOptions;
3135
+ let customId;
3136
+ if (typeof idOrFunc === "function") {
3137
+ finalFn = idOrFunc;
3138
+ finalOptions = fnOrOptions;
3139
+ } else {
3140
+ if (typeof fnOrOptions !== "function") {
3141
+ throw new Error("[BetterCollector] Second argument must be a function when a customId is provided.");
3142
+ }
3143
+ customId = idOrFunc;
3144
+ finalFn = fnOrOptions;
3145
+ finalOptions = options;
3146
+ }
3147
+ if (customId) {
3148
+ const listeners = this.events.collectId.get(customId) || [];
3149
+ listeners.push({ fn: finalFn, options: finalOptions });
3150
+ this.events.collectId.set(customId, listeners);
3151
+ } else {
3152
+ this.events.collect.push({ fn: finalFn, options: finalOptions });
3153
+ }
3154
+ return this;
3155
+ }
3156
+ onEnd(fn, options) {
3157
+ this.events.end.push({ fn, options });
3158
+ return this;
3159
+ }
3160
+ /** Manually stop the collector and trigger cleanup */
3161
+ stop(reason = "manual") {
3162
+ this.collector?.stop(reason);
3163
+ }
3164
+ };
3165
+
3020
3166
  // src/tools/BetterContainer.ts
3021
3167
  var import_discord11 = require("discord.js");
3022
3168
  var BetterContainer = class {
@@ -3057,7 +3203,9 @@ var BetterContainer = class {
3057
3203
  return this;
3058
3204
  }
3059
3205
  addText(text) {
3060
- this.container.addTextDisplayComponents((tdb) => tdb.setContent(Array.isArray(text) ? text.join("\n") : text));
3206
+ this.container.addTextDisplayComponents(
3207
+ (tdb) => tdb.setContent(Array.isArray(text) ? text.filter((t) => t !== null && t !== void 0).join("\n") : text)
3208
+ );
3061
3209
  return this;
3062
3210
  }
3063
3211
  addMedia(...media) {
@@ -3081,7 +3229,9 @@ var BetterContainer = class {
3081
3229
  this.container.addSectionComponents((sb) => {
3082
3230
  if (data.text) {
3083
3231
  sb.addTextDisplayComponents(
3084
- (tdb) => tdb.setContent(Array.isArray(data.text) ? data.text.join("\n") : data.text)
3232
+ (tdb) => tdb.setContent(
3233
+ Array.isArray(data.text) ? data.text.filter((t) => t !== null && t !== void 0).join("\n") : data.text
3234
+ )
3085
3235
  );
3086
3236
  }
3087
3237
  if (data.thumbnail) sb.setThumbnailAccessory(new import_discord11.ThumbnailBuilder(data.thumbnail));
@@ -3312,6 +3462,7 @@ var PaginationTimeoutType = /* @__PURE__ */ ((PaginationTimeoutType2) => {
3312
3462
  PaginationTimeoutType2[PaginationTimeoutType2["DisableComponents"] = 0] = "DisableComponents";
3313
3463
  PaginationTimeoutType2[PaginationTimeoutType2["ClearComponents"] = 1] = "ClearComponents";
3314
3464
  PaginationTimeoutType2[PaginationTimeoutType2["DeleteMessage"] = 2] = "DeleteMessage";
3465
+ PaginationTimeoutType2[PaginationTimeoutType2["DoNothing"] = 3] = "DoNothing";
3315
3466
  return PaginationTimeoutType2;
3316
3467
  })(PaginationTimeoutType || {});
3317
3468
  function wrapPositive(num, max) {
@@ -3392,7 +3543,8 @@ var Paginator = class {
3392
3543
  last: [],
3393
3544
  collect: [],
3394
3545
  react: [],
3395
- timeout: []
3546
+ preTimeout: [],
3547
+ postTimeout: []
3396
3548
  };
3397
3549
  if (this.options.pages.length) {
3398
3550
  this.addChapter(this.options.pages, { label: "Default" });
@@ -3459,16 +3611,23 @@ var Paginator = class {
3459
3611
  embeds: [],
3460
3612
  components: [],
3461
3613
  flags: [],
3614
+ files: [],
3615
+ // Explicitly empty to clear previous files on page switch
3462
3616
  ...options,
3463
3617
  withResponse: true
3464
3618
  };
3465
3619
  const page = this.data.page.current;
3620
+ const currentChapter = this.chapters[this.data.page.index.chapter];
3621
+ const chapterFile = currentChapter?.files?.[this.data.page.index.nested];
3622
+ if (chapterFile) sendOptions.files.push(chapterFile);
3466
3623
  if (Array.isArray(page)) {
3467
3624
  sendOptions.embeds.push(...page);
3468
3625
  } else if (typeof page === "string") {
3469
3626
  sendOptions.content = page;
3470
3627
  } else if (isEmbed(page)) {
3471
3628
  sendOptions.embeds.push(page);
3629
+ } else if (page instanceof import_discord13.AttachmentBuilder) {
3630
+ sendOptions.files.push(page);
3472
3631
  } else if (page instanceof import_discord13.ContainerBuilder || page instanceof BetterContainer) {
3473
3632
  sendOptions.components.push(page);
3474
3633
  if (!sendOptions.flags.includes("IsComponentsV2")) {
@@ -3480,7 +3639,7 @@ var Paginator = class {
3480
3639
  }
3481
3640
  async handlePostTimeout() {
3482
3641
  if (!this.data.message) return;
3483
- this.callEventStack("timeout", this.data.message);
3642
+ this.callEventStack("preTimeout", this.data.message);
3484
3643
  this.data.collectors.component?.stop();
3485
3644
  this.data.collectors.reaction?.stop();
3486
3645
  switch (this.options.onTimeout) {
@@ -3524,7 +3683,10 @@ var Paginator = class {
3524
3683
  case 2 /* DeleteMessage */:
3525
3684
  await this.data.message.delete().catch(Boolean);
3526
3685
  break;
3686
+ case 3 /* DoNothing */:
3687
+ break;
3527
3688
  }
3689
+ this.callEventStack("postTimeout", this.data.message);
3528
3690
  }
3529
3691
  async nav_removeFromMessage() {
3530
3692
  if (!this.data.message?.editable) return;
@@ -3571,35 +3733,43 @@ var Paginator = class {
3571
3733
  await i.reply({ content: "Jump not implemented yet.", flags: "Ephemeral" });
3572
3734
  return;
3573
3735
  }
3574
- await i.deferUpdate().catch(Boolean);
3575
3736
  switch (i.customId) {
3576
3737
  case "ssm_chapterSelect":
3738
+ await i.deferUpdate().catch(Boolean);
3577
3739
  const chapterIndex = this.chapters.findIndex(
3578
3740
  (c) => c.id === i.values[0]
3579
3741
  );
3580
3742
  await this.setPage(chapterIndex, 0);
3743
+ await this.refresh();
3581
3744
  break;
3582
3745
  case "btn_first":
3746
+ await i.deferUpdate().catch(Boolean);
3583
3747
  this.callEventStack("first", this.data.page.current, this.data.page.index);
3584
3748
  await this.setPage(this.data.page.index.chapter, 0);
3749
+ await this.refresh();
3585
3750
  break;
3586
3751
  case "btn_back":
3752
+ await i.deferUpdate().catch(Boolean);
3587
3753
  this.callEventStack("back", this.data.page.current, this.data.page.index);
3588
3754
  await this.setPage(this.data.page.index.chapter, this.data.page.index.nested - 1);
3755
+ await this.refresh();
3589
3756
  break;
3590
3757
  case "btn_next":
3758
+ await i.deferUpdate().catch(Boolean);
3591
3759
  this.callEventStack("next", this.data.page.current, this.data.page.index);
3592
3760
  await this.setPage(this.data.page.index.chapter, this.data.page.index.nested + 1);
3761
+ await this.refresh();
3593
3762
  break;
3594
3763
  case "btn_last":
3764
+ await i.deferUpdate().catch(Boolean);
3595
3765
  this.callEventStack("last", this.data.page.current, this.data.page.index);
3596
3766
  await this.setPage(
3597
3767
  this.data.page.index.chapter,
3598
3768
  this.chapters[this.data.page.index.chapter].pages.length - 1
3599
3769
  );
3770
+ await this.refresh();
3600
3771
  break;
3601
3772
  }
3602
- await this.refresh();
3603
3773
  } catch (err) {
3604
3774
  console.error("[Paginator] Component navigation error", err);
3605
3775
  }
@@ -3639,7 +3809,7 @@ var Paginator = class {
3639
3809
  data.value = `ssm_c:${this.chapters.length}`;
3640
3810
  }
3641
3811
  const normalizedPages = resolvePages(pages);
3642
- this.chapters.push({ id: data.value, pages: normalizedPages });
3812
+ this.chapters.push({ id: data.value, pages: normalizedPages, files: data.files });
3643
3813
  this.data.components.chapterSelect.addOptions(data);
3644
3814
  return this;
3645
3815
  }
@@ -4016,7 +4186,7 @@ CLI.addCommand("help", "View information about a command, or the available CLI o
4016
4186
  }
4017
4187
  CLI.logger.table("(help)", helpList);
4018
4188
  });
4019
- CLI.addCommand("register", "Register app commands globally, or per guild", async (args, content) => {
4189
+ CLI.addCommand("register", "Register app commands (slash & context) globally, or per guild", async (args, content) => {
4020
4190
  const client = CLI.getClientInstance(content);
4021
4191
  if (!client) return;
4022
4192
  const mode = args[0]?.toLowerCase() || "";
@@ -4027,10 +4197,14 @@ CLI.addCommand("register", "Register app commands globally, or per guild", async
4027
4197
  if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
4028
4198
  switch (mode) {
4029
4199
  case "guild":
4200
+ CLI.logger.info("Registering guild commands...");
4030
4201
  await client.commands.slash.registerGuild({ guilds: guildIds });
4202
+ await client.commands.context.registerGuild({ guilds: guildIds });
4031
4203
  break;
4032
4204
  case "global":
4205
+ CLI.logger.info("Registering global commands...");
4033
4206
  await client.commands.slash.registerGlobal();
4207
+ await client.commands.context.registerGlobal();
4034
4208
  break;
4035
4209
  }
4036
4210
  });
@@ -4045,10 +4219,14 @@ CLI.addCommand("unregister", "Unregister app commands globally, or per guild", a
4045
4219
  if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
4046
4220
  switch (mode) {
4047
4221
  case "guild":
4222
+ CLI.logger.info("Unregistering guild commands...");
4048
4223
  await client.commands.slash.unregisterGuild({ guilds: guildIds });
4224
+ await client.commands.context.unregisterGuild({ guilds: guildIds });
4049
4225
  break;
4050
4226
  case "global":
4227
+ CLI.logger.info("Unregistering global commands...");
4051
4228
  await client.commands.slash.unregisterGlobal();
4229
+ await client.commands.context.unregisterGlobal();
4052
4230
  break;
4053
4231
  }
4054
4232
  });
@@ -4063,46 +4241,48 @@ CLI.addCommand("stats", "View statistics about a client instance", (args, conten
4063
4241
  "Memory Usage:": `${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)} MB`
4064
4242
  });
4065
4243
  });
4066
- CLI.addCommand("cmds", "List the loaded commands", (args, content) => {
4244
+ CLI.addCommand("cmds", "List the loaded commands", async (args, content) => {
4067
4245
  const client = CLI.getClientInstance(content);
4068
4246
  if (!client) return;
4069
- const mode = args[0] || "slash";
4247
+ const mode = (args[0] || "slash").toLowerCase();
4070
4248
  switch (mode) {
4071
- case "slash":
4072
- const slashCommands = Array.from(client.commands.slash.commands.values());
4073
- slashCommands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
4074
- const slashCommands_f = {};
4075
- for (const cmd of slashCommands) {
4076
- slashCommands_f[`/${cmd.builder.name}`] = `~ ${cmd.builder.description || "No description"}`;
4249
+ case "slash": {
4250
+ const commands = Array.from(client.commands.slash.commands.values());
4251
+ commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
4252
+ const tableData = {};
4253
+ for (const cmd of commands) {
4254
+ tableData[`/${cmd.builder.name}`] = `~ ${cmd.builder.description || "No description"}`;
4077
4255
  }
4078
- return CLI.logger.table(
4079
- `(cmds) ~ slash (${import_jstools2.default.formatThousands(client.commands.slash.commands.size)})`,
4080
- slashCommands_f
4081
- );
4082
- case "prefix":
4083
- const prefixCommands = Array.from(client.commands.prefix.commands.values());
4084
- prefixCommands.sort((a, b) => a.name.localeCompare(b.name));
4085
- const prefixCommands_f = {};
4086
- for (const cmd of prefixCommands) {
4087
- prefixCommands_f[`${client.config.prefixCommands.defaultPrefix}${cmd.name}`] = `~ ${cmd.description || "No description"}`;
4256
+ return CLI.logger.table(`(cmds) ~ slash (${import_jstools2.default.formatThousands(commands.length)})`, tableData);
4257
+ }
4258
+ case "prefix": {
4259
+ const commands = Array.from(client.commands.prefix.commands.values());
4260
+ commands.sort((a, b) => {
4261
+ const nameA = a.toConfig().name;
4262
+ const nameB = b.toConfig().name;
4263
+ return nameA.localeCompare(nameB);
4264
+ });
4265
+ const tableData = {};
4266
+ const defaultPrefix = client.config.prefixCommands.defaultPrefix;
4267
+ for (const cmd of commands) {
4268
+ const config = cmd.toConfig();
4269
+ const aliasIndicator = config.aliases?.length ? ` [${config.aliases.join(", ")}]` : "";
4270
+ tableData[`${defaultPrefix}${config.name}${aliasIndicator}`] = `~ ${config.description || "No description"}`;
4088
4271
  }
4089
- return CLI.logger.table(
4090
- `(cmds) ~ prefix (${import_jstools2.default.formatThousands(client.commands.prefix.commands.size)})`,
4091
- prefixCommands_f
4092
- );
4093
- case "ctx":
4094
- const contextCommands = Array.from(client.commands.context.commands.values());
4095
- contextCommands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
4096
- const contextCommands_f = {};
4097
- for (const cmd of contextCommands) {
4098
- contextCommands_f[`${cmd.builder.name}`] = "";
4272
+ return CLI.logger.table(`(cmds) ~ prefix (${import_jstools2.default.formatThousands(commands.length)})`, tableData);
4273
+ }
4274
+ case "ctx": {
4275
+ const commands = Array.from(client.commands.context.commands.values());
4276
+ commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
4277
+ const tableData = {};
4278
+ for (const cmd of commands) {
4279
+ const type = cmd.builder.type === 2 ? "User" : "Msg";
4280
+ tableData[`[${type}] ${cmd.builder.name}`] = "";
4099
4281
  }
4100
- return CLI.logger.table(
4101
- `(cmds) ~ ctx (${import_jstools2.default.formatThousands(client.commands.context.commands.size)})`,
4102
- contextCommands_f
4103
- );
4282
+ return CLI.logger.table(`(cmds) ~ ctx (${import_jstools2.default.formatThousands(commands.length)})`, tableData);
4283
+ }
4104
4284
  default:
4105
- return CLI.logger.error(`'${mode}' is not a valid option. Your options are [slash|prefix|ctx]`);
4285
+ return CLI.logger.error(`'${mode}' is not a valid option. Valid options: [slash|prefix|ctx]`);
4106
4286
  }
4107
4287
  });
4108
4288
  function initCLI() {
@@ -4129,14 +4309,17 @@ function getClientInstances() {
4129
4309
  // Annotate the CommonJS export names for ESM import in node:
4130
4310
  0 && (module.exports = {
4131
4311
  BaseCommandBuilder,
4312
+ BetterCollector,
4132
4313
  BetterContainer,
4133
4314
  BetterEmbed,
4134
4315
  BetterModal,
4135
4316
  CLI,
4317
+ CollectorTimeoutType,
4136
4318
  CommandType,
4137
4319
  ContextCommandBuilder,
4138
4320
  DynaSend,
4139
4321
  EventBuilder,
4322
+ LOGGER_COLORS,
4140
4323
  LogLevel,
4141
4324
  Logger,
4142
4325
  MissingPermissionReason,
@@ -4165,6 +4348,7 @@ function getClientInstances() {
4165
4348
  clientInstances,
4166
4349
  createClient,
4167
4350
  createMongoSchema,
4351
+ createMongoSession,
4168
4352
  createToolsConfig,
4169
4353
  createVimcordAppConfig,
4170
4354
  createVimcordContextCommandConfig,
@@ -4198,6 +4382,7 @@ function getClientInstances() {
4198
4382
  useClient,
4199
4383
  useMongoDatabase,
4200
4384
  useReadyClient,
4385
+ useReadyMongoDatabase,
4201
4386
  validateCommandPermissions
4202
4387
  });
4203
4388
  //# sourceMappingURL=index.cjs.map