vimcord 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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);
278
291
  }
292
+ const perms = this.checkPermissions(client, ctx.member || ctx.user, args[1]);
293
+ if (!perms.validated) {
294
+ return await config.onMissingPermissions?.(perms, ...args);
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;
390
+ return this;
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);
300
395
  return this;
301
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,79 +479,54 @@ 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
 
578
527
  // src/utils/dir.ts
579
- var import_jstools = __toESM(require("jstools"));
580
528
  var import_node_path = __toESM(require("path"));
529
+ var import_qznt = require("qznt");
581
530
  function getProcessDir() {
582
531
  const mainPath = process.argv[1];
583
532
  if (!mainPath) return "";
@@ -587,7 +536,7 @@ async function importModulesFromDir(dir, fnPrefix) {
587
536
  const cwd = getProcessDir();
588
537
  const MODULE_RELATIVE_PATH = import_node_path.default.join(cwd, dir);
589
538
  const MODULE_LOG_PATH = dir;
590
- const files = import_jstools.default.readDir(MODULE_RELATIVE_PATH, { recursive: true }).filter(
539
+ const files = import_qznt.$.fs.readDir(MODULE_RELATIVE_PATH).filter(
591
540
  (fn) => fn.endsWith(`${fnPrefix ? `.${fnPrefix}` : ""}.js`) || fn.endsWith(`${fnPrefix ? `.${fnPrefix}` : ""}.ts`)
592
541
  );
593
542
  if (!files.length) {
@@ -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();
829
777
  }
830
- validateConfig() {
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
+ }
831
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;
810
+ }
811
+ /**
812
+ * Set the command description.
813
+ */
814
+ setDescription(description) {
815
+ this.options.description = description;
816
+ return this;
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;
@@ -1379,28 +1341,9 @@ function formatThousands(num, sep = ",") {
1379
1341
  return `${num}`.replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, sep);
1380
1342
  }
1381
1343
 
1382
- // src/utils/random.ts
1383
- function pickRandom(arr, options) {
1384
- const _rnd = () => {
1385
- return arr[Math.floor(Math.random() * arr.length)];
1386
- };
1387
- let att = 0;
1388
- let candidate = _rnd();
1389
- if (options?.notEqualTo !== void 0 && arr.length > 1) {
1390
- while (candidate === options.notEqualTo) {
1391
- if (att < (options?.maxRerollAttempts ?? 100)) {
1392
- throw new Error(`pickRandom reached max reroll attempts (${options?.maxRerollAttempts ?? 100})`);
1393
- }
1394
- candidate = _rnd();
1395
- att++;
1396
- }
1397
- }
1398
- return options?.clone ? structuredClone(candidate) : candidate;
1399
- }
1400
-
1401
1344
  // src/modules/status.manager.ts
1402
- var import_node_cron = __toESM(require("node-cron"));
1403
1345
  var import_node_events = __toESM(require("events"));
1346
+ var import_qznt2 = require("qznt");
1404
1347
  var VimcordStatusManager = class {
1405
1348
  client;
1406
1349
  logger;
@@ -1421,24 +1364,9 @@ var VimcordStatusManager = class {
1421
1364
  this.logger.debug("Status cleared");
1422
1365
  }
1423
1366
  });
1424
- this.emitter.on("rotation", () => {
1425
- if (this.client.config.app.verbose) {
1426
- this.logger.debug("Status rotated");
1427
- }
1428
- });
1429
- this.emitter.on("rotationStarted", () => {
1430
- if (this.client.config.app.verbose) {
1431
- this.logger.debug("Status rotation resumed \u25B6\uFE0F");
1432
- }
1433
- });
1434
- this.emitter.on("rotationPaused", () => {
1435
- if (this.client.config.app.verbose) {
1436
- this.logger.debug("Status rotation paused \u23F8\uFE0F");
1437
- }
1438
- });
1439
1367
  }
1440
1368
  clearData() {
1441
- this.task?.destroy();
1369
+ this.task?.stop();
1442
1370
  this.task = null;
1443
1371
  this.lastActivity = null;
1444
1372
  this.lastActivityIndex = 0;
@@ -1471,11 +1399,8 @@ var VimcordStatusManager = class {
1471
1399
  }
1472
1400
  async statusRotationTask(clientStatus) {
1473
1401
  let activity;
1474
- if (clientStatus.randomize) {
1475
- activity = pickRandom(clientStatus.activity, {
1476
- notEqualTo: this.lastActivity,
1477
- clone: true
1478
- });
1402
+ if (clientStatus.randomize && Array.isArray(clientStatus.activity)) {
1403
+ activity = import_qznt2.$.rnd.choice(clientStatus.activity, { not: this.lastActivity });
1479
1404
  this.lastActivity = activity;
1480
1405
  } else {
1481
1406
  const activityIndex = (this.lastActivityIndex + 1) % clientStatus.activity.length;
@@ -1487,34 +1412,22 @@ var VimcordStatusManager = class {
1487
1412
  }
1488
1413
  async scheduleStatusRotation(clientStatus) {
1489
1414
  if (!clientStatus.interval) throw new Error("Cannot create client activity interval without interval time");
1490
- this.task?.destroy();
1415
+ this.task?.stop();
1491
1416
  this.task = null;
1492
- await this.statusRotationTask(clientStatus);
1493
- this.task = import_node_cron.default.createTask(
1494
- `*/${clientStatus.interval} * * * * *`,
1495
- async () => await this.statusRotationTask(clientStatus),
1496
- { noOverlap: true }
1497
- );
1417
+ this.task = new import_qznt2.$.Loop(() => this.statusRotationTask(clientStatus), import_qznt2.$.math.ms(clientStatus.interval), true);
1498
1418
  this.start();
1499
- this.emitter.emit("rotationStarted", this.task);
1500
1419
  }
1501
1420
  start() {
1502
1421
  if (this.task) {
1503
1422
  this.task.start();
1504
- this.emitter.emit("rotationStarted", this.task);
1505
- if (this.client.config.app.verbose) {
1506
- this.logger.debug("Status rotation started");
1507
- }
1423
+ this.emitter.emit("started", this.task);
1508
1424
  }
1509
1425
  return this;
1510
1426
  }
1511
1427
  pause() {
1512
1428
  if (this.task) {
1513
1429
  this.task.stop();
1514
- this.emitter.emit("rotationPaused", this.task);
1515
- if (this.client.config.app.verbose) {
1516
- this.logger.debug("Status rotation paused");
1517
- }
1430
+ this.emitter.emit("paused", this.task);
1518
1431
  }
1519
1432
  return this;
1520
1433
  }
@@ -1528,21 +1441,16 @@ var VimcordStatusManager = class {
1528
1441
  }
1529
1442
  if (!clientStatus.interval) {
1530
1443
  await this.setActivity(Array.isArray(clientStatus.activity) ? clientStatus.activity[0] : clientStatus.activity);
1531
- return this;
1532
- }
1533
- if (clientStatus.interval) {
1444
+ } else {
1534
1445
  await this.scheduleStatusRotation(clientStatus);
1535
1446
  }
1536
1447
  return this;
1537
1448
  }
1538
1449
  async destroy() {
1539
1450
  if (this.task) {
1540
- this.task.destroy();
1451
+ this.task.stop();
1541
1452
  this.task = null;
1542
- this.emitter.emit("rotationDestroyed");
1543
- if (this.client.config.app.verbose) {
1544
- this.logger.debug("Status rotation destroyed");
1545
- }
1453
+ this.emitter.emit("destroyed");
1546
1454
  await this.clear();
1547
1455
  }
1548
1456
  return this;
@@ -1552,245 +1460,193 @@ var VimcordStatusManager = class {
1552
1460
  this.clearData();
1553
1461
  client.user.setActivity({ name: "" });
1554
1462
  this.emitter.emit("cleared");
1555
- if (this.client.config.app.verbose) {
1556
- this.logger.debug("Status cleared");
1557
- }
1558
1463
  return this;
1559
1464
  }
1560
1465
  };
1561
1466
 
1562
1467
  // src/modules/command.manager.ts
1563
1468
  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) {
1469
+ var VimcordAppCommandManager = class {
1470
+ constructor(client, typeName) {
1576
1471
  this.client = client;
1577
- client.whenReady().then((c) => this.rest = new import_discord5.REST().setToken(c.token));
1472
+ this.typeName = typeName;
1473
+ this.client.whenReady().then((c) => this.rest = new import_discord5.REST().setToken(c.token));
1578
1474
  }
1579
1475
  commands = /* @__PURE__ */ new Map();
1580
1476
  rest;
1581
1477
  get(name) {
1582
1478
  return this.commands.get(name);
1583
1479
  }
1480
+ /**
1481
+ * Filters and returns commands based on deployment options
1482
+ */
1584
1483
  getAll(options) {
1585
1484
  const matchedCommands = /* @__PURE__ */ new Map();
1485
+ const isDev = this.client.config.app.devMode;
1586
1486
  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;
1487
+ const config = cmd.toConfig();
1488
+ const name = cmd.builder.name;
1489
+ if (options?.names || options?.fuzzyNames) {
1490
+ const nameMatched = options.names?.includes(name) || options.fuzzyNames?.some((fuzzy) => name.includes(fuzzy));
1491
+ if (!nameMatched) continue;
1600
1492
  }
1601
- if (!matched) continue;
1602
1493
  if (options?.ignoreDeploymentOptions) {
1603
- if (matchedCommands.has(matched.builder.name)) continue;
1604
- matchedCommands.set(matched.builder.name, matched);
1494
+ matchedCommands.set(name, cmd);
1605
1495
  continue;
1606
1496
  }
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
- }
1497
+ const deployment = config.deployment || {};
1498
+ const isProperEnv = !deployment.environments || deployment.environments.includes(isDev ? "development" : "production");
1499
+ if (!isProperEnv) continue;
1500
+ if (options?.globalOnly && deployment.global === false) continue;
1501
+ matchedCommands.set(name, cmd);
1613
1502
  }
1614
1503
  return matchedCommands;
1615
1504
  }
1616
- /** Import command modules that end with `.slash` */
1617
- async importFrom(dir, replaceAll) {
1618
- if (replaceAll) {
1619
- this.commands.clear();
1505
+ async registerGlobal(options) {
1506
+ const client = await this.client.whenReady();
1507
+ const commands = Array.from(
1508
+ this.getAll({
1509
+ names: options?.commands,
1510
+ fuzzyNames: options?.fuzzyCommands,
1511
+ globalOnly: true
1512
+ }).values()
1513
+ ).map((cmd) => cmd.builder.toJSON());
1514
+ if (!commands.length) {
1515
+ console.log(`[${this.typeName}] No commands to register`);
1516
+ return;
1620
1517
  }
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
- }
1518
+ console.log(`[${this.typeName}] Registering ${commands.length} commands globally...`);
1519
+ try {
1520
+ await this.rest.put(import_discord5.Routes.applicationCommands(client.user.id), { body: commands });
1521
+ console.log(`[${this.typeName}] \u2714 Registered app commands globally`);
1522
+ } catch (err) {
1523
+ console.log(`[${this.typeName}] \u2716 Failed to register app commands globally`, err);
1634
1524
  }
1635
- this.client.logger.moduleLoaded("Slash Commands", importedCommands);
1636
- return this.commands;
1637
1525
  }
1638
1526
  async registerGuild(options) {
1639
1527
  const client = await this.client.whenReady();
1640
- const commandsToRegister = Array.from(
1528
+ const commands = Array.from(
1641
1529
  this.getAll({
1642
1530
  names: options?.commands,
1643
1531
  fuzzyNames: options?.fuzzyCommands
1644
1532
  }).values()
1645
1533
  ).map((cmd) => cmd.builder.toJSON());
1646
- if (!commandsToRegister.length) {
1647
- console.log("[SlashCommandManager] No commands to register");
1534
+ if (!commands.length) {
1535
+ console.log(`[${this.typeName}] No commands to register`);
1648
1536
  return;
1649
1537
  }
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
- );
1538
+ const guildIds = options?.guilds || client.guilds.cache.map((g) => g.id);
1539
+ console.log(`[${this.typeName}] Registering ${commands.length} commands for ${guildIds.length} guilds...`);
1654
1540
  await Promise.all(
1655
1541
  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
- )
1542
+ (guildId) => this.rest.put(import_discord5.Routes.applicationGuildCommands(client.user.id, guildId), { body: commands }).then(() => {
1543
+ const gName = client.guilds.cache.get(guildId)?.name || "n/a";
1544
+ console.log(`[${this.typeName}] \u2714 Set app commands in guild: ${guildId} (${gName})`);
1545
+ }).catch((err) => {
1546
+ const gName = client.guilds.cache.get(guildId)?.name || "n/a";
1547
+ console.log(`[${this.typeName}] \u2716 Failed to set app commands in guild: ${guildId} (${gName})`, err);
1548
+ })
1668
1549
  )
1669
1550
  );
1670
- console.log(
1671
- `[SlashCommandManager] \u2714 Finished registering app (/) commands for ${guildIds.length} ${guildIds.length === 1 ? "guild" : "guilds"}`
1672
- );
1673
1551
  }
1674
1552
  async unregisterGuild(options) {
1675
1553
  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
- );
1554
+ const guildIds = options?.guilds || client.guilds.cache.map((g) => g.id);
1555
+ console.log(`[${this.typeName}] Unregistering commands from ${guildIds.length} guilds...`);
1680
1556
  await Promise.all(
1681
1557
  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
- )
1558
+ (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(
1559
+ (err) => console.log(`[${this.typeName}] \u2716 Failed to remove app commands in guild: ${guildId}`, err)
1693
1560
  )
1694
1561
  )
1695
1562
  );
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
1563
  }
1723
1564
  async unregisterGlobal() {
1724
1565
  const client = await this.client.whenReady();
1725
- console.log("[SlashCommandManager] Unregistering app (/) commands globally...");
1726
1566
  try {
1727
1567
  await this.rest.put(import_discord5.Routes.applicationCommands(client.user.id), { body: [] });
1728
- console.log("[SlashCommandManager] \u2714 Removed app (/) commands globally");
1568
+ console.log(`[${this.typeName}] \u2714 Removed app commands globally`);
1729
1569
  } catch (err) {
1730
- console.log("[SlashCommandManager] \u2716 Failed to remove app (/) commands globally", err);
1570
+ console.log(`[${this.typeName}] \u2716 Failed to remove app commands globally`, err);
1731
1571
  }
1732
1572
  }
1733
1573
  };
1734
- var VimcordPrefixCommandManager = class {
1574
+ var VimcordSlashCommandManager = class extends VimcordAppCommandManager {
1735
1575
  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));
1576
+ super(client, "SlashCommandManager");
1577
+ }
1578
+ async importFrom(dir, replaceAll = false) {
1579
+ if (replaceAll) this.commands.clear();
1580
+ const dirs = Array.isArray(dir) ? dir : [dir];
1581
+ const modules = (await Promise.all(
1582
+ dirs.map((d) => importModulesFromDir(d, "slash"))
1583
+ )).flat();
1584
+ for (const { module: module2 } of modules) {
1585
+ this.commands.set(module2.default.builder.name, module2.default);
1586
+ }
1587
+ this.client.logger.moduleLoaded("Slash Commands", modules.length);
1588
+ return this.commands;
1752
1589
  }
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);
1590
+ };
1591
+ var VimcordContextCommandManager = class extends VimcordAppCommandManager {
1592
+ constructor(client) {
1593
+ super(client, "ContextCommandManager");
1594
+ }
1595
+ async importFrom(dir, replaceAll = false) {
1596
+ if (replaceAll) this.commands.clear();
1597
+ const dirs = Array.isArray(dir) ? dir : [dir];
1598
+ const modules = (await Promise.all(
1599
+ dirs.map((d) => importModulesFromDir(d, "ctx"))
1600
+ )).flat();
1601
+ for (const { module: module2 } of modules) {
1602
+ this.commands.set(module2.default.builder.name, module2.default);
1603
+ }
1604
+ this.client.logger.moduleLoaded("Context Commands", modules.length);
1768
1605
  return this.commands;
1769
1606
  }
1770
1607
  };
1771
- var VimcordContextCommandManager = class {
1608
+ var VimcordPrefixCommandManager = class {
1772
1609
  constructor(client) {
1773
1610
  this.client = client;
1774
1611
  }
1775
1612
  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++;
1613
+ resolve(trigger) {
1614
+ const config = this.client.config.prefixCommands;
1615
+ const search = config.allowCaseInsensitiveCommandNames ? trigger.toLowerCase() : trigger;
1616
+ return Array.from(this.commands.values()).find((cmd) => {
1617
+ const opts = cmd.toConfig();
1618
+ const name = config.allowCaseInsensitiveCommandNames ? opts.name.toLowerCase() : opts.name;
1619
+ if (name === search) return true;
1620
+ return opts.aliases?.some(
1621
+ (a) => config.allowCaseInsensitiveCommandNames ? a.toLowerCase() === search : a === search
1622
+ );
1623
+ });
1624
+ }
1625
+ async importFrom(dir, replaceAll = false) {
1626
+ if (replaceAll) this.commands.clear();
1627
+ const dirs = Array.isArray(dir) ? dir : [dir];
1628
+ const modules = (await Promise.all(
1629
+ dirs.map(
1630
+ (d) => importModulesFromDir(d, "prefix")
1631
+ )
1632
+ )).flat();
1633
+ for (const { module: module2 } of modules) {
1634
+ this.commands.set(module2.default.toConfig().name, module2.default);
1789
1635
  }
1790
- this.client.logger.moduleLoaded("Context Commands", importedCommands);
1636
+ this.client.logger.moduleLoaded("Prefix Commands", modules.length);
1791
1637
  return this.commands;
1792
1638
  }
1793
1639
  };
1640
+ var VimcordCommandManager = class {
1641
+ slash;
1642
+ prefix;
1643
+ context;
1644
+ constructor(client) {
1645
+ this.slash = new VimcordSlashCommandManager(client);
1646
+ this.prefix = new VimcordPrefixCommandManager(client);
1647
+ this.context = new VimcordContextCommandManager(client);
1648
+ }
1649
+ };
1794
1650
 
1795
1651
  // src/modules/event.manager.ts
1796
1652
  var import_discord6 = require("discord.js");
@@ -2168,7 +2024,7 @@ var BetterEmbed = class _BetterEmbed {
2168
2024
  }
2169
2025
  if (this.data.description) {
2170
2026
  this.data.description = formatString(
2171
- Array.isArray(this.data.description) ? this.data.description.join("\n") : this.data.description
2027
+ Array.isArray(this.data.description) ? this.data.description.filter((s) => s !== null && s !== void 0).join("\n") : this.data.description
2172
2028
  );
2173
2029
  }
2174
2030
  if (this.data.footer && typeof this.data.footer === "object") {
@@ -2180,7 +2036,7 @@ var BetterEmbed = class _BetterEmbed {
2180
2036
  if (this.data.imageUrl) {
2181
2037
  this.data.imageUrl = formatString(this.data.imageUrl);
2182
2038
  }
2183
- this.data.fields = this.data.fields.map((field) => ({
2039
+ this.data.fields = this.data.fields.filter(Boolean).map((field) => ({
2184
2040
  ...field,
2185
2041
  name: formatString(field.name),
2186
2042
  value: formatString(field.value)
@@ -2405,7 +2261,7 @@ async function retryExponentialBackoff(fn, maxRetries = 3, retryDelay = 1e3) {
2405
2261
  }
2406
2262
 
2407
2263
  // package.json
2408
- var version = "1.0.0";
2264
+ var version = "1.0.1";
2409
2265
 
2410
2266
  // src/client.ts
2411
2267
  var import_node_crypto3 = require("crypto");
@@ -2671,62 +2527,63 @@ var Vimcord = class _Vimcord extends import_discord10.Client {
2671
2527
  return fetchGuild(client, id);
2672
2528
  }
2673
2529
  };
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
2530
  var defaultPrefixCommandHandler = new EventBuilder({
2698
2531
  event: "messageCreate",
2699
2532
  name: "PrefixCommandHandler",
2700
2533
  async execute(client, message) {
2701
- if (message.author.bot) return;
2534
+ if (message.author.bot || !message.guild) return;
2535
+ const config = client.config.prefixCommands;
2536
+ let activePrefix = config.defaultPrefix;
2537
+ if (config.guildPrefixResolver) {
2538
+ try {
2539
+ const customPrefix = await config.guildPrefixResolver(client, message.guild.id);
2540
+ if (customPrefix) activePrefix = customPrefix;
2541
+ } catch (err) {
2542
+ client.logger.error(`Error in guildPrefixResolver for guild ${message.guild.id}:`, err);
2543
+ }
2544
+ }
2702
2545
  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;
2546
+ if (message.content.startsWith(activePrefix)) {
2547
+ prefixUsed = activePrefix;
2548
+ } else if (config.allowMentionAsPrefix) {
2549
+ const mention = (0, import_discord10.userMention)(client.user.id);
2550
+ if (message.content.startsWith(mention)) {
2551
+ prefixUsed = message.content.startsWith(`${mention} `) ? `${mention} ` : mention;
2712
2552
  }
2713
- } else {
2714
- prefixUsed = client.config.prefixCommands.defaultPrefix;
2715
2553
  }
2716
- message.content = message.content.slice(prefixUsed.length);
2717
- let commandName = message.content.split(" ")[0];
2718
- if (!commandName) {
2719
- return;
2554
+ if (!prefixUsed) return;
2555
+ const contentWithoutPrefix = message.content.slice(prefixUsed.length).trim();
2556
+ const args = contentWithoutPrefix.split(/\s+/);
2557
+ const trigger = args.shift();
2558
+ if (!trigger) return;
2559
+ const command = client.commands.prefix.resolve(trigger);
2560
+ if (!command) return;
2561
+ message.content = args.join(" ");
2562
+ try {
2563
+ return await command.run(client, client, message);
2564
+ } catch (err) {
2565
+ await sendCommandErrorEmbed(client, err, message.guild, message);
2566
+ throw err;
2720
2567
  }
2721
- const command = client.commands.prefix.get(commandName) || client.commands.prefix.getByAlias(commandName);
2568
+ }
2569
+ });
2570
+ var defaultSlashCommandHandler = new EventBuilder({
2571
+ event: "interactionCreate",
2572
+ name: "SlashCommandHandler",
2573
+ async execute(client, interaction) {
2574
+ if (!interaction.isChatInputCommand()) return;
2575
+ const command = client.commands.slash.get(interaction.commandName);
2722
2576
  if (!command) {
2723
- return;
2577
+ const content = `**/\`${interaction.commandName}\`** is not a registered command.`;
2578
+ if (interaction.replied || interaction.deferred) {
2579
+ return interaction.followUp({ content, flags: "Ephemeral" });
2580
+ }
2581
+ return interaction.reply({ content, flags: "Ephemeral" });
2724
2582
  }
2725
- message.content = message.content.slice(commandName.length + 1);
2726
2583
  try {
2727
- return command.executeCommand(client, message);
2584
+ return await command.run(client, client, interaction);
2728
2585
  } catch (err) {
2729
- sendCommandErrorEmbed(client, err, message.guild, message);
2586
+ await sendCommandErrorEmbed(client, err, interaction.guild, interaction);
2730
2587
  throw err;
2731
2588
  }
2732
2589
  }
@@ -2734,9 +2591,22 @@ var defaultPrefixCommandHandler = new EventBuilder({
2734
2591
  var defaultContextCommandHandler = new EventBuilder({
2735
2592
  event: "interactionCreate",
2736
2593
  name: "ContextCommandHandler",
2737
- execute(client, interaction) {
2594
+ async execute(client, interaction) {
2738
2595
  if (!interaction.isContextMenuCommand()) return;
2739
- interaction.reply({ content: "This handler is not yet implemented" });
2596
+ const command = client.commands.context.get(interaction.commandName);
2597
+ if (!command) {
2598
+ const content = `**${interaction.commandName}** is not a registered context command.`;
2599
+ if (interaction.replied || interaction.deferred) {
2600
+ return interaction.followUp({ content, flags: "Ephemeral" });
2601
+ }
2602
+ return interaction.reply({ content, flags: "Ephemeral" });
2603
+ }
2604
+ try {
2605
+ return await command.run(client, client, interaction);
2606
+ } catch (err) {
2607
+ await sendCommandErrorEmbed(client, err, interaction.guild, interaction);
2608
+ throw err;
2609
+ }
2740
2610
  }
2741
2611
  });
2742
2612
 
@@ -2751,8 +2621,8 @@ try {
2751
2621
  }
2752
2622
  var globalInstanceEmitter = new import_node_events2.default();
2753
2623
  var instances = [];
2754
- async function useMongoDatabase(index) {
2755
- const instance = instances.at(index ?? 0);
2624
+ async function useMongoDatabase(instanceIndex) {
2625
+ const instance = instances.at(instanceIndex ?? 0);
2756
2626
  if (!instance) {
2757
2627
  return new Promise((resolve, reject) => {
2758
2628
  const timeout = setTimeout(() => reject("useMongoDatabase timed out"), 45e3);
@@ -2764,6 +2634,15 @@ async function useMongoDatabase(index) {
2764
2634
  }
2765
2635
  return instance;
2766
2636
  }
2637
+ async function useReadyMongoDatabase(instanceIndex) {
2638
+ const instance = await useMongoDatabase(instanceIndex);
2639
+ await instance.waitForReady();
2640
+ return instance;
2641
+ }
2642
+ async function createMongoSession(instanceIndex, options) {
2643
+ const instance = await useReadyMongoDatabase(instanceIndex);
2644
+ return instance.mongoose.startSession(options);
2645
+ }
2767
2646
  var MongoDatabase = class {
2768
2647
  constructor(client, options) {
2769
2648
  this.client = client;
@@ -2918,6 +2797,7 @@ var MongoSchemaBuilder = class {
2918
2797
  this.eventEmitter.off(event, listener);
2919
2798
  return this;
2920
2799
  }
2800
+ /** Execute a function while ensuring the connection is ready. On error it will retry using an exponential backoff. */
2921
2801
  async execute(fn) {
2922
2802
  try {
2923
2803
  if (!this.isReady) {
@@ -2954,9 +2834,9 @@ var MongoSchemaBuilder = class {
2954
2834
  return id;
2955
2835
  });
2956
2836
  }
2957
- async count(filter) {
2837
+ async count(filter, options) {
2958
2838
  return await this.execute(async () => {
2959
- return this.model.countDocuments(filter);
2839
+ return this.model.countDocuments(filter, options);
2960
2840
  });
2961
2841
  }
2962
2842
  async exists(filter) {
@@ -2964,29 +2844,29 @@ var MongoSchemaBuilder = class {
2964
2844
  return await this.model.exists(filter) ? true : false;
2965
2845
  });
2966
2846
  }
2967
- async create(query) {
2847
+ async create(query, options) {
2968
2848
  return await this.execute(async () => {
2969
- return this.model.create(query);
2970
- });
2849
+ return this.model.create(query, options);
2850
+ }) ?? [];
2971
2851
  }
2972
2852
  async upsert(filter, query, options) {
2973
2853
  return await this.execute(async () => {
2974
2854
  return this.model.findOneAndUpdate(filter, query, { ...options, upsert: true, new: true });
2975
2855
  });
2976
2856
  }
2977
- async delete(filter) {
2857
+ async delete(filter, options) {
2978
2858
  return await this.execute(async () => {
2979
- return this.model.deleteOne(filter);
2859
+ return this.model.deleteOne(filter, options);
2980
2860
  });
2981
2861
  }
2982
- async deleteAll(filter) {
2862
+ async deleteAll(filter, options) {
2983
2863
  return await this.execute(async () => {
2984
- return this.model.deleteMany(filter);
2864
+ return this.model.deleteMany(filter, options);
2985
2865
  });
2986
2866
  }
2987
- async distinct(key, filter) {
2867
+ async distinct(key, filter, options) {
2988
2868
  return await this.execute(async () => {
2989
- return this.model.distinct(key, filter);
2869
+ return this.model.distinct(key, filter, options);
2990
2870
  });
2991
2871
  }
2992
2872
  async fetch(filter, projection, options) {
@@ -3017,6 +2897,215 @@ var MongoSchemaBuilder = class {
3017
2897
  }
3018
2898
  };
3019
2899
 
2900
+ // src/tools/BetterCollector.ts
2901
+ var CollectorTimeoutType = /* @__PURE__ */ ((CollectorTimeoutType2) => {
2902
+ CollectorTimeoutType2[CollectorTimeoutType2["DisableComponents"] = 0] = "DisableComponents";
2903
+ CollectorTimeoutType2[CollectorTimeoutType2["DeleteMessage"] = 1] = "DeleteMessage";
2904
+ CollectorTimeoutType2[CollectorTimeoutType2["DoNothing"] = 2] = "DoNothing";
2905
+ return CollectorTimeoutType2;
2906
+ })(CollectorTimeoutType || {});
2907
+ var BetterCollector = class _BetterCollector {
2908
+ message;
2909
+ collector;
2910
+ options;
2911
+ activeUsers = /* @__PURE__ */ new Set();
2912
+ participantWarningCooldowns = /* @__PURE__ */ new Map();
2913
+ config;
2914
+ events = {
2915
+ collectId: /* @__PURE__ */ new Map(),
2916
+ collect: [],
2917
+ end: [],
2918
+ timeout: []
2919
+ };
2920
+ static create(message, options) {
2921
+ return new _BetterCollector(message, options);
2922
+ }
2923
+ async validateParticipant(interaction, participants) {
2924
+ const allowedParticipants = participants || this.options?.participants || [];
2925
+ if (!allowedParticipants.length) return true;
2926
+ const isAllowed = allowedParticipants.some((user) => {
2927
+ if (typeof user === "string") return user === interaction.user.id;
2928
+ if (typeof user === "object" && "id" in user) return user.id === interaction.user.id;
2929
+ return false;
2930
+ });
2931
+ if (!isAllowed) {
2932
+ const now = Date.now();
2933
+ const lastWarned = this.participantWarningCooldowns.get(interaction.user.id) || 0;
2934
+ if (now - lastWarned > this.config.collector.notAParticipantWarningCooldown) {
2935
+ this.participantWarningCooldowns.set(interaction.user.id, now);
2936
+ if (this.config.collector.notAParticipantMessage) {
2937
+ await interaction.reply({
2938
+ content: this.config.collector.notAParticipantMessage,
2939
+ flags: "Ephemeral"
2940
+ }).catch(() => {
2941
+ });
2942
+ } else {
2943
+ await interaction.deferUpdate().catch(() => {
2944
+ });
2945
+ }
2946
+ } else {
2947
+ await interaction.deferUpdate().catch(() => {
2948
+ });
2949
+ }
2950
+ }
2951
+ return isAllowed;
2952
+ }
2953
+ build() {
2954
+ if (!this.message) return;
2955
+ if (this.collector) return;
2956
+ this.collector = this.message.createMessageComponentCollector({
2957
+ idle: this.options?.idle ?? this.config.timeouts.collectorIdle,
2958
+ time: this.options?.timeout ?? this.config.timeouts.collectorTimeout,
2959
+ componentType: this.options?.type,
2960
+ max: this.options?.max,
2961
+ maxComponents: this.options?.maxComponents,
2962
+ maxUsers: this.options?.maxUsers
2963
+ });
2964
+ this.setupListeners();
2965
+ }
2966
+ setupListeners() {
2967
+ if (!this.collector) return;
2968
+ this.collector.on("collect", async (interaction) => {
2969
+ if (this.options?.userLock && this.activeUsers.has(interaction.user.id)) {
2970
+ return interaction.reply({ content: this.config.collector.userLockMessage, flags: "Ephemeral" }).catch(() => {
2971
+ });
2972
+ }
2973
+ if (this.options?.userLock) {
2974
+ this.activeUsers.add(interaction.user.id);
2975
+ }
2976
+ const globalListeners = this.events.collect;
2977
+ const idListeners = this.events.collectId.get(interaction.customId) || [];
2978
+ const allListeners = [...globalListeners, ...idListeners];
2979
+ const shouldBeDeferred = allListeners.find((l) => l.options?.defer)?.options?.defer;
2980
+ const validListeners = [];
2981
+ for (const listener of allListeners) {
2982
+ const isAllowed = await this.validateParticipant(interaction, listener.options?.participants);
2983
+ if (isAllowed) validListeners.push(listener);
2984
+ }
2985
+ if (validListeners.length === 0) return;
2986
+ try {
2987
+ if (shouldBeDeferred) {
2988
+ if (typeof shouldBeDeferred === "object") {
2989
+ if (shouldBeDeferred.update) {
2990
+ await interaction.deferUpdate().catch(Boolean);
2991
+ } else {
2992
+ await interaction.deferReply({ flags: shouldBeDeferred.ephemeral ? "Ephemeral" : void 0 }).catch(Boolean);
2993
+ }
2994
+ } else {
2995
+ await interaction.deferReply().catch(Boolean);
2996
+ }
2997
+ }
2998
+ if (this.options?.sequential) {
2999
+ for (const listener of allListeners) {
3000
+ try {
3001
+ const isAllowed = await this.validateParticipant(interaction, listener.options?.participants);
3002
+ if (!isAllowed) return;
3003
+ await listener.fn(interaction).finally(() => listener.options?.finally?.(interaction));
3004
+ } catch (err) {
3005
+ this.handleListenerError(err);
3006
+ }
3007
+ }
3008
+ } else {
3009
+ Promise.all(
3010
+ allListeners.map((l) => {
3011
+ const isAllowed = this.validateParticipant(interaction, l.options?.participants);
3012
+ if (!isAllowed) return;
3013
+ return l.fn(interaction).catch(this.handleListenerError).finally(() => l.options?.finally?.(interaction));
3014
+ })
3015
+ );
3016
+ }
3017
+ } finally {
3018
+ if (this.options?.userLock) {
3019
+ this.activeUsers.delete(interaction.user.id);
3020
+ }
3021
+ }
3022
+ });
3023
+ this.collector.on("end", async (collected, reason) => {
3024
+ if (this.options?.sequential) {
3025
+ for (const listener of this.events.end) {
3026
+ try {
3027
+ await listener.fn(collected, reason);
3028
+ } catch (err) {
3029
+ this.handleListenerError(err);
3030
+ }
3031
+ }
3032
+ } else {
3033
+ Promise.all(this.events.end.map((l) => l.fn(collected, reason).catch(this.handleListenerError)));
3034
+ }
3035
+ switch (this.options?.onTimeout) {
3036
+ case 0 /* DisableComponents */:
3037
+ if (!this.message?.editable) break;
3038
+ try {
3039
+ const disabledRows = this.message.components.map((row) => {
3040
+ const updatedRow = row.toJSON();
3041
+ if ("components" in updatedRow) {
3042
+ updatedRow.components = updatedRow.components.map((component) => ({
3043
+ ...component,
3044
+ disabled: true
3045
+ }));
3046
+ }
3047
+ return updatedRow;
3048
+ });
3049
+ await this.message.edit({ components: disabledRows });
3050
+ } catch (err) {
3051
+ if (!(err instanceof Error && err.message.includes("Unknown Message"))) {
3052
+ this.handleListenerError(err);
3053
+ }
3054
+ }
3055
+ break;
3056
+ case 1 /* DeleteMessage */:
3057
+ if (!this.message?.deletable) break;
3058
+ await this.message.delete().catch(Boolean);
3059
+ break;
3060
+ case 2 /* DoNothing */:
3061
+ default:
3062
+ break;
3063
+ }
3064
+ });
3065
+ }
3066
+ handleListenerError(err) {
3067
+ console.error("[BetterCollector] Listener Error:", err);
3068
+ }
3069
+ constructor(message, options) {
3070
+ this.config = options?.config || globalVimcordToolsConfig;
3071
+ this.message = message || void 0;
3072
+ this.options = options;
3073
+ this.build();
3074
+ }
3075
+ on(idOrFunc, fnOrOptions, options) {
3076
+ let finalFn;
3077
+ let finalOptions;
3078
+ let customId;
3079
+ if (typeof idOrFunc === "function") {
3080
+ finalFn = idOrFunc;
3081
+ finalOptions = fnOrOptions;
3082
+ } else {
3083
+ if (typeof fnOrOptions !== "function") {
3084
+ throw new Error("[BetterCollector] Second argument must be a function when a customId is provided.");
3085
+ }
3086
+ customId = idOrFunc;
3087
+ finalFn = fnOrOptions;
3088
+ finalOptions = options;
3089
+ }
3090
+ if (customId) {
3091
+ const listeners = this.events.collectId.get(customId) || [];
3092
+ listeners.push({ fn: finalFn, options: finalOptions });
3093
+ this.events.collectId.set(customId, listeners);
3094
+ } else {
3095
+ this.events.collect.push({ fn: finalFn, options: finalOptions });
3096
+ }
3097
+ return this;
3098
+ }
3099
+ onEnd(fn, options) {
3100
+ this.events.end.push({ fn, options });
3101
+ return this;
3102
+ }
3103
+ /** Manually stop the collector and trigger cleanup */
3104
+ stop(reason = "manual") {
3105
+ this.collector?.stop(reason);
3106
+ }
3107
+ };
3108
+
3020
3109
  // src/tools/BetterContainer.ts
3021
3110
  var import_discord11 = require("discord.js");
3022
3111
  var BetterContainer = class {
@@ -3057,7 +3146,9 @@ var BetterContainer = class {
3057
3146
  return this;
3058
3147
  }
3059
3148
  addText(text) {
3060
- this.container.addTextDisplayComponents((tdb) => tdb.setContent(Array.isArray(text) ? text.join("\n") : text));
3149
+ this.container.addTextDisplayComponents(
3150
+ (tdb) => tdb.setContent(Array.isArray(text) ? text.filter((t) => t !== null && t !== void 0).join("\n") : text)
3151
+ );
3061
3152
  return this;
3062
3153
  }
3063
3154
  addMedia(...media) {
@@ -3081,7 +3172,9 @@ var BetterContainer = class {
3081
3172
  this.container.addSectionComponents((sb) => {
3082
3173
  if (data.text) {
3083
3174
  sb.addTextDisplayComponents(
3084
- (tdb) => tdb.setContent(Array.isArray(data.text) ? data.text.join("\n") : data.text)
3175
+ (tdb) => tdb.setContent(
3176
+ Array.isArray(data.text) ? data.text.filter((t) => t !== null && t !== void 0).join("\n") : data.text
3177
+ )
3085
3178
  );
3086
3179
  }
3087
3180
  if (data.thumbnail) sb.setThumbnailAccessory(new import_discord11.ThumbnailBuilder(data.thumbnail));
@@ -3312,6 +3405,7 @@ var PaginationTimeoutType = /* @__PURE__ */ ((PaginationTimeoutType2) => {
3312
3405
  PaginationTimeoutType2[PaginationTimeoutType2["DisableComponents"] = 0] = "DisableComponents";
3313
3406
  PaginationTimeoutType2[PaginationTimeoutType2["ClearComponents"] = 1] = "ClearComponents";
3314
3407
  PaginationTimeoutType2[PaginationTimeoutType2["DeleteMessage"] = 2] = "DeleteMessage";
3408
+ PaginationTimeoutType2[PaginationTimeoutType2["DoNothing"] = 3] = "DoNothing";
3315
3409
  return PaginationTimeoutType2;
3316
3410
  })(PaginationTimeoutType || {});
3317
3411
  function wrapPositive(num, max) {
@@ -3392,7 +3486,8 @@ var Paginator = class {
3392
3486
  last: [],
3393
3487
  collect: [],
3394
3488
  react: [],
3395
- timeout: []
3489
+ preTimeout: [],
3490
+ postTimeout: []
3396
3491
  };
3397
3492
  if (this.options.pages.length) {
3398
3493
  this.addChapter(this.options.pages, { label: "Default" });
@@ -3459,16 +3554,23 @@ var Paginator = class {
3459
3554
  embeds: [],
3460
3555
  components: [],
3461
3556
  flags: [],
3557
+ files: [],
3558
+ // Explicitly empty to clear previous files on page switch
3462
3559
  ...options,
3463
3560
  withResponse: true
3464
3561
  };
3465
3562
  const page = this.data.page.current;
3563
+ const currentChapter = this.chapters[this.data.page.index.chapter];
3564
+ const chapterFile = currentChapter?.files?.[this.data.page.index.nested];
3565
+ if (chapterFile) sendOptions.files.push(chapterFile);
3466
3566
  if (Array.isArray(page)) {
3467
3567
  sendOptions.embeds.push(...page);
3468
3568
  } else if (typeof page === "string") {
3469
3569
  sendOptions.content = page;
3470
3570
  } else if (isEmbed(page)) {
3471
3571
  sendOptions.embeds.push(page);
3572
+ } else if (page instanceof import_discord13.AttachmentBuilder) {
3573
+ sendOptions.files.push(page);
3472
3574
  } else if (page instanceof import_discord13.ContainerBuilder || page instanceof BetterContainer) {
3473
3575
  sendOptions.components.push(page);
3474
3576
  if (!sendOptions.flags.includes("IsComponentsV2")) {
@@ -3480,7 +3582,7 @@ var Paginator = class {
3480
3582
  }
3481
3583
  async handlePostTimeout() {
3482
3584
  if (!this.data.message) return;
3483
- this.callEventStack("timeout", this.data.message);
3585
+ this.callEventStack("preTimeout", this.data.message);
3484
3586
  this.data.collectors.component?.stop();
3485
3587
  this.data.collectors.reaction?.stop();
3486
3588
  switch (this.options.onTimeout) {
@@ -3524,7 +3626,10 @@ var Paginator = class {
3524
3626
  case 2 /* DeleteMessage */:
3525
3627
  await this.data.message.delete().catch(Boolean);
3526
3628
  break;
3629
+ case 3 /* DoNothing */:
3630
+ break;
3527
3631
  }
3632
+ this.callEventStack("postTimeout", this.data.message);
3528
3633
  }
3529
3634
  async nav_removeFromMessage() {
3530
3635
  if (!this.data.message?.editable) return;
@@ -3571,35 +3676,43 @@ var Paginator = class {
3571
3676
  await i.reply({ content: "Jump not implemented yet.", flags: "Ephemeral" });
3572
3677
  return;
3573
3678
  }
3574
- await i.deferUpdate().catch(Boolean);
3575
3679
  switch (i.customId) {
3576
3680
  case "ssm_chapterSelect":
3681
+ await i.deferUpdate().catch(Boolean);
3577
3682
  const chapterIndex = this.chapters.findIndex(
3578
3683
  (c) => c.id === i.values[0]
3579
3684
  );
3580
3685
  await this.setPage(chapterIndex, 0);
3686
+ await this.refresh();
3581
3687
  break;
3582
3688
  case "btn_first":
3689
+ await i.deferUpdate().catch(Boolean);
3583
3690
  this.callEventStack("first", this.data.page.current, this.data.page.index);
3584
3691
  await this.setPage(this.data.page.index.chapter, 0);
3692
+ await this.refresh();
3585
3693
  break;
3586
3694
  case "btn_back":
3695
+ await i.deferUpdate().catch(Boolean);
3587
3696
  this.callEventStack("back", this.data.page.current, this.data.page.index);
3588
3697
  await this.setPage(this.data.page.index.chapter, this.data.page.index.nested - 1);
3698
+ await this.refresh();
3589
3699
  break;
3590
3700
  case "btn_next":
3701
+ await i.deferUpdate().catch(Boolean);
3591
3702
  this.callEventStack("next", this.data.page.current, this.data.page.index);
3592
3703
  await this.setPage(this.data.page.index.chapter, this.data.page.index.nested + 1);
3704
+ await this.refresh();
3593
3705
  break;
3594
3706
  case "btn_last":
3707
+ await i.deferUpdate().catch(Boolean);
3595
3708
  this.callEventStack("last", this.data.page.current, this.data.page.index);
3596
3709
  await this.setPage(
3597
3710
  this.data.page.index.chapter,
3598
3711
  this.chapters[this.data.page.index.chapter].pages.length - 1
3599
3712
  );
3713
+ await this.refresh();
3600
3714
  break;
3601
3715
  }
3602
- await this.refresh();
3603
3716
  } catch (err) {
3604
3717
  console.error("[Paginator] Component navigation error", err);
3605
3718
  }
@@ -3639,7 +3752,7 @@ var Paginator = class {
3639
3752
  data.value = `ssm_c:${this.chapters.length}`;
3640
3753
  }
3641
3754
  const normalizedPages = resolvePages(pages);
3642
- this.chapters.push({ id: data.value, pages: normalizedPages });
3755
+ this.chapters.push({ id: data.value, pages: normalizedPages, files: data.files });
3643
3756
  this.data.components.chapterSelect.addOptions(data);
3644
3757
  return this;
3645
3758
  }
@@ -3747,6 +3860,7 @@ var Prompt = class {
3747
3860
  content;
3748
3861
  embed;
3749
3862
  container;
3863
+ textOnly;
3750
3864
  buttons;
3751
3865
  customButtons;
3752
3866
  onResolve;
@@ -3760,6 +3874,7 @@ var Prompt = class {
3760
3874
  this.content = options.content;
3761
3875
  this.embed = options.embed ?? this.createDefaultForm();
3762
3876
  this.container = options?.container;
3877
+ this.textOnly = options.textOnly;
3763
3878
  this.buttons = this.createButtons(options.buttons);
3764
3879
  this.customButtons = this.createCustomButtons(options.customButtons);
3765
3880
  this.onResolve = options.onResolve ?? [3 /* DeleteOnConfirm */, 4 /* DeleteOnReject */];
@@ -3839,7 +3954,7 @@ var Prompt = class {
3839
3954
  }
3840
3955
  buildSendOptions(options) {
3841
3956
  const sendData = { ...options };
3842
- if (this.container) {
3957
+ if (!this.textOnly && this.container) {
3843
3958
  sendData.components = Array.isArray(sendData.components) ? [...sendData.components, this.container] : [this.container];
3844
3959
  const existingFlags = sendData.flags ? Array.isArray(sendData.flags) ? sendData.flags : [sendData.flags] : [];
3845
3960
  if (!existingFlags.includes("IsComponentsV2")) {
@@ -3848,7 +3963,9 @@ var Prompt = class {
3848
3963
  sendData.flags = existingFlags;
3849
3964
  }
3850
3965
  } else {
3851
- sendData.embeds = Array.isArray(sendData.embeds) ? [this.embed, ...sendData.embeds] : [this.embed];
3966
+ if (!this.textOnly) {
3967
+ sendData.embeds = Array.isArray(sendData.embeds) ? [this.embed, ...sendData.embeds] : [this.embed];
3968
+ }
3852
3969
  }
3853
3970
  if (this.content) {
3854
3971
  sendData.content = this.content;
@@ -3934,7 +4051,7 @@ async function prompt(handler, options, sendOptions) {
3934
4051
 
3935
4052
  // src/utils/VimcordCLI.ts
3936
4053
  var import_node_readline = require("readline");
3937
- var import_jstools2 = __toESM(require("jstools"));
4054
+ var import_qznt3 = require("qznt");
3938
4055
  var VimcordCLI = class {
3939
4056
  rl;
3940
4057
  options;
@@ -3973,7 +4090,7 @@ var VimcordCLI = class {
3973
4090
  return { isCommand: true, commandName, content: args.join(" "), args };
3974
4091
  }
3975
4092
  getClientInstance(line) {
3976
- const clientIndex = import_jstools2.default.getFlagSubstring(line, "--client", 1) || import_jstools2.default.getFlagSubstring(line, "-c", 1);
4093
+ const clientIndex = import_qznt3.$.str.getFlag(line, "--client", 1) || import_qznt3.$.str.getFlag(line, "-c", 1);
3977
4094
  if (clientIndex) {
3978
4095
  const idx = Number(clientIndex);
3979
4096
  if (isNaN(idx)) {
@@ -4016,21 +4133,25 @@ CLI.addCommand("help", "View information about a command, or the available CLI o
4016
4133
  }
4017
4134
  CLI.logger.table("(help)", helpList);
4018
4135
  });
4019
- CLI.addCommand("register", "Register app commands globally, or per guild", async (args, content) => {
4136
+ CLI.addCommand("register", "Register app commands (slash & context) globally, or per guild", async (args, content) => {
4020
4137
  const client = CLI.getClientInstance(content);
4021
4138
  if (!client) return;
4022
4139
  const mode = args[0]?.toLowerCase() || "";
4023
4140
  if (!["guild", "global"].includes(mode)) {
4024
4141
  return CLI.logger.error(`'${mode}' is not a valid option. Your options are [guild|global]`);
4025
4142
  }
4026
- let guildIds = (import_jstools2.default.getFlagSubstring(content, "--guilds", 1) || import_jstools2.default.getFlagSubstring(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
4143
+ let guildIds = (import_qznt3.$.str.getFlag(content, "--guilds", 1) || import_qznt3.$.str.getFlag(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
4027
4144
  if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
4028
4145
  switch (mode) {
4029
4146
  case "guild":
4147
+ CLI.logger.info("Registering guild commands...");
4030
4148
  await client.commands.slash.registerGuild({ guilds: guildIds });
4149
+ await client.commands.context.registerGuild({ guilds: guildIds });
4031
4150
  break;
4032
4151
  case "global":
4152
+ CLI.logger.info("Registering global commands...");
4033
4153
  await client.commands.slash.registerGlobal();
4154
+ await client.commands.context.registerGlobal();
4034
4155
  break;
4035
4156
  }
4036
4157
  });
@@ -4041,14 +4162,18 @@ CLI.addCommand("unregister", "Unregister app commands globally, or per guild", a
4041
4162
  if (!["guild", "global"].includes(mode)) {
4042
4163
  return CLI.logger.error(`'${mode}' is not a valid option. Your options are [guild|global]`);
4043
4164
  }
4044
- let guildIds = (import_jstools2.default.getFlagSubstring(content, "--guilds", 1) || import_jstools2.default.getFlagSubstring(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
4165
+ let guildIds = (import_qznt3.$.str.getFlag(content, "--guilds", 1) || import_qznt3.$.str.getFlag(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
4045
4166
  if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
4046
4167
  switch (mode) {
4047
4168
  case "guild":
4169
+ CLI.logger.info("Unregistering guild commands...");
4048
4170
  await client.commands.slash.unregisterGuild({ guilds: guildIds });
4171
+ await client.commands.context.unregisterGuild({ guilds: guildIds });
4049
4172
  break;
4050
4173
  case "global":
4174
+ CLI.logger.info("Unregistering global commands...");
4051
4175
  await client.commands.slash.unregisterGlobal();
4176
+ await client.commands.context.unregisterGlobal();
4052
4177
  break;
4053
4178
  }
4054
4179
  });
@@ -4056,53 +4181,55 @@ CLI.addCommand("stats", "View statistics about a client instance", (args, conten
4056
4181
  const client = CLI.getClientInstance(content);
4057
4182
  if (!client) return;
4058
4183
  CLI.logger.table(`(stats) ~ ${client.config.app.name}`, {
4059
- "Guilds:": import_jstools2.default.formatThousands(client.guilds.cache.size),
4184
+ "Guilds:": import_qznt3.$.format.number(client.guilds.cache.size),
4060
4185
  "Ping:": `${client.ws.ping || 0}ms`,
4061
- "Uptime:": `${import_jstools2.default.msToSec(client.uptime || 0)}s`,
4186
+ "Uptime:": `${import_qznt3.$.math.secs(client.uptime || 0)}s`,
4062
4187
  "Process Uptime:": `${Math.floor(process.uptime())}s`,
4063
4188
  "Memory Usage:": `${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)} MB`
4064
4189
  });
4065
4190
  });
4066
- CLI.addCommand("cmds", "List the loaded commands", (args, content) => {
4191
+ CLI.addCommand("cmds", "List the loaded commands", async (args, content) => {
4067
4192
  const client = CLI.getClientInstance(content);
4068
4193
  if (!client) return;
4069
- const mode = args[0] || "slash";
4194
+ const mode = (args[0] || "slash").toLowerCase();
4070
4195
  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"}`;
4196
+ case "slash": {
4197
+ const commands = Array.from(client.commands.slash.commands.values());
4198
+ commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
4199
+ const tableData = {};
4200
+ for (const cmd of commands) {
4201
+ tableData[`/${cmd.builder.name}`] = `~ ${cmd.builder.description || "No description"}`;
4077
4202
  }
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"}`;
4203
+ return CLI.logger.table(`(cmds) ~ slash (${import_qznt3.$.format.number(commands.length)})`, tableData);
4204
+ }
4205
+ case "prefix": {
4206
+ const commands = Array.from(client.commands.prefix.commands.values());
4207
+ commands.sort((a, b) => {
4208
+ const nameA = a.toConfig().name;
4209
+ const nameB = b.toConfig().name;
4210
+ return nameA.localeCompare(nameB);
4211
+ });
4212
+ const tableData = {};
4213
+ const defaultPrefix = client.config.prefixCommands.defaultPrefix;
4214
+ for (const cmd of commands) {
4215
+ const config = cmd.toConfig();
4216
+ const aliasIndicator = config.aliases?.length ? ` [${config.aliases.join(", ")}]` : "";
4217
+ tableData[`${defaultPrefix}${config.name}${aliasIndicator}`] = `~ ${config.description || "No description"}`;
4088
4218
  }
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}`] = "";
4219
+ return CLI.logger.table(`(cmds) ~ prefix (${import_qznt3.$.format.number(commands.length)})`, tableData);
4220
+ }
4221
+ case "ctx": {
4222
+ const commands = Array.from(client.commands.context.commands.values());
4223
+ commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
4224
+ const tableData = {};
4225
+ for (const cmd of commands) {
4226
+ const type = cmd.builder.type === 2 ? "User" : "Msg";
4227
+ tableData[`[${type}] ${cmd.builder.name}`] = "";
4099
4228
  }
4100
- return CLI.logger.table(
4101
- `(cmds) ~ ctx (${import_jstools2.default.formatThousands(client.commands.context.commands.size)})`,
4102
- contextCommands_f
4103
- );
4229
+ return CLI.logger.table(`(cmds) ~ ctx (${import_qznt3.$.format.number(commands.length)})`, tableData);
4230
+ }
4104
4231
  default:
4105
- return CLI.logger.error(`'${mode}' is not a valid option. Your options are [slash|prefix|ctx]`);
4232
+ return CLI.logger.error(`'${mode}' is not a valid option. Valid options: [slash|prefix|ctx]`);
4106
4233
  }
4107
4234
  });
4108
4235
  function initCLI() {
@@ -4126,17 +4253,39 @@ function createClient(options, features = {}, config = {}) {
4126
4253
  function getClientInstances() {
4127
4254
  return clientInstances;
4128
4255
  }
4256
+
4257
+ // src/utils/random.ts
4258
+ function pickRandom(arr, options) {
4259
+ const _rnd = () => {
4260
+ return arr[Math.floor(Math.random() * arr.length)];
4261
+ };
4262
+ let att = 0;
4263
+ let candidate = _rnd();
4264
+ if (options?.notEqualTo !== void 0 && arr.length > 1) {
4265
+ while (candidate === options.notEqualTo) {
4266
+ if (att < (options?.maxRerollAttempts ?? 100)) {
4267
+ throw new Error(`pickRandom reached max reroll attempts (${options?.maxRerollAttempts ?? 100})`);
4268
+ }
4269
+ candidate = _rnd();
4270
+ att++;
4271
+ }
4272
+ }
4273
+ return options?.clone ? structuredClone(candidate) : candidate;
4274
+ }
4129
4275
  // Annotate the CommonJS export names for ESM import in node:
4130
4276
  0 && (module.exports = {
4131
4277
  BaseCommandBuilder,
4278
+ BetterCollector,
4132
4279
  BetterContainer,
4133
4280
  BetterEmbed,
4134
4281
  BetterModal,
4135
4282
  CLI,
4283
+ CollectorTimeoutType,
4136
4284
  CommandType,
4137
4285
  ContextCommandBuilder,
4138
4286
  DynaSend,
4139
4287
  EventBuilder,
4288
+ LOGGER_COLORS,
4140
4289
  LogLevel,
4141
4290
  Logger,
4142
4291
  MissingPermissionReason,
@@ -4165,6 +4314,7 @@ function getClientInstances() {
4165
4314
  clientInstances,
4166
4315
  createClient,
4167
4316
  createMongoSchema,
4317
+ createMongoSession,
4168
4318
  createToolsConfig,
4169
4319
  createVimcordAppConfig,
4170
4320
  createVimcordContextCommandConfig,
@@ -4198,6 +4348,7 @@ function getClientInstances() {
4198
4348
  useClient,
4199
4349
  useMongoDatabase,
4200
4350
  useReadyClient,
4351
+ useReadyMongoDatabase,
4201
4352
  validateCommandPermissions
4202
4353
  });
4203
4354
  //# sourceMappingURL=index.cjs.map