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.js CHANGED
@@ -6,11 +6,11 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  });
7
7
 
8
8
  // src/types/command.base.ts
9
- var CommandType = /* @__PURE__ */ ((CommandType3) => {
10
- CommandType3[CommandType3["Slash"] = 0] = "Slash";
11
- CommandType3[CommandType3["Prefix"] = 1] = "Prefix";
12
- CommandType3[CommandType3["Context"] = 2] = "Context";
13
- return CommandType3;
9
+ var CommandType = /* @__PURE__ */ ((CommandType2) => {
10
+ CommandType2[CommandType2["Slash"] = 0] = "Slash";
11
+ CommandType2[CommandType2["Prefix"] = 1] = "Prefix";
12
+ CommandType2[CommandType2["Context"] = 2] = "Context";
13
+ return CommandType2;
14
14
  })(CommandType || {});
15
15
  var MissingPermissionReason = /* @__PURE__ */ ((MissingPermissionReason2) => {
16
16
  MissingPermissionReason2[MissingPermissionReason2["User"] = 0] = "User";
@@ -134,270 +134,239 @@ import _ from "lodash";
134
134
  var BaseCommandBuilder = class {
135
135
  uuid = randomUUID();
136
136
  commandType;
137
- client;
138
- enabled;
139
- conditions;
140
- permissions;
141
- metadata;
142
- rateLimit;
143
- beforeExecute;
144
- execute;
145
- afterExecute;
146
- onRateLimit;
147
- onMissingPermissions;
148
- onConditionsNotMet;
149
- onUsedWhenDisabled;
150
- onError;
151
- globalRateLimitData = { executions: 0, timestamp: 0 };
152
- userRateLimitData = /* @__PURE__ */ new Map();
153
- guildRateLimitData = /* @__PURE__ */ new Map();
154
- channelRateLimitData = /* @__PURE__ */ new Map();
155
- constructor(type, config) {
137
+ /** Local command configuration and hooks */
138
+ options;
139
+ /** Internal state for rate limiting across different scopes */
140
+ rlStores = {
141
+ [3 /* Global */]: { executions: 0, timestamp: 0 },
142
+ [0 /* User */]: /* @__PURE__ */ new Map(),
143
+ [1 /* Guild */]: /* @__PURE__ */ new Map(),
144
+ [2 /* Channel */]: /* @__PURE__ */ new Map()
145
+ };
146
+ /** * Mapping of CommandTypes to their respective config keys in the Vimcord client
147
+ */
148
+ typeConfigMapping = {
149
+ [0 /* Slash */]: "slashCommands",
150
+ [1 /* Prefix */]: "prefixCommands",
151
+ [2 /* Context */]: "contextCommands"
152
+ };
153
+ constructor(type, options = {}) {
156
154
  this.commandType = type;
157
- this.enabled = config?.enabled ?? true;
158
- this.conditions = config?.conditions;
159
- this.permissions = config?.permissions;
160
- this.metadata = config?.metadata;
161
- this.rateLimit = config?.rateLimit;
162
- this.beforeExecute = config?.beforeExecute;
163
- this.execute = config?.execute;
164
- this.afterExecute = config?.afterExecute;
165
- this.onRateLimit = config?.onRateLimit;
166
- this.onMissingPermissions = config?.onMissingPermissions;
167
- this.onConditionsNotMet = config?.onConditionsNotMet;
168
- this.onUsedWhenDisabled = config?.onUsedWhenDisabled;
169
- this.onError = config?.onError;
170
- this.validateBaseConfig();
155
+ this.options = { enabled: true, ...options };
171
156
  }
172
157
  validateBaseConfig() {
173
- if (this.rateLimit) {
174
- if (this.rateLimit.max <= 0) {
175
- throw new Error("DJSSlashCommandBuilder: Rate limit max must be greater than 0");
158
+ if (this.options.rateLimit) {
159
+ const { max, interval } = this.options.rateLimit;
160
+ if (max <= 0 || interval <= 0) {
161
+ throw new Error(`[Vimcord:${this.constructor.name}] Rate limit values must be positive.`);
176
162
  }
177
- if (this.rateLimit.interval <= 0) {
178
- throw new Error("DJSSlashCommandBuilder: Rate limit interval must be greater than 0");
163
+ }
164
+ }
165
+ /**
166
+ * Resolves the final configuration by merging layers:
167
+ * Client Defaults < Client Type-Specific < Local Command Options
168
+ */
169
+ resolveConfig(client) {
170
+ const typeKey = this.typeConfigMapping[this.commandType];
171
+ const typeSpecificGlobals = client.config?.[typeKey] || {};
172
+ return _.merge({}, typeSpecificGlobals, this.options);
173
+ }
174
+ /**
175
+ * Executes the command lifecycle.
176
+ * Merges global client config with local command options at runtime.
177
+ */
178
+ async run(client, ...args) {
179
+ const config = this.resolveConfig(client);
180
+ const ctx = this.extractContext(args);
181
+ try {
182
+ if (!config.enabled) {
183
+ return await config.onUsedWhenDisabled?.(...args);
184
+ }
185
+ if (this.isRateLimited(config, ctx)) {
186
+ return await config.onRateLimit?.(...args);
179
187
  }
188
+ const perms = this.checkPermissions(client, ctx.member || ctx.user, args[1]);
189
+ if (!perms.validated) {
190
+ return await config.onMissingPermissions?.(perms, ...args);
191
+ }
192
+ if (!await this.checkConditions(config, ...args)) {
193
+ return await config.onConditionsNotMet?.(...args);
194
+ }
195
+ await config.beforeExecute?.(...args);
196
+ if (config.logExecution !== false) {
197
+ const cmdName = this.options.name || this.builder?.name || "Unknown";
198
+ const location = ctx.guild ? `${ctx.guild.name} (${ctx.guild.id})` : "Direct Messages";
199
+ client.logger.commandExecuted(cmdName, ctx.user.username, location);
200
+ }
201
+ const result = await config.execute?.(...args);
202
+ await config.afterExecute?.(result, ...args);
203
+ } catch (error) {
204
+ await this.handleError(error, config, ...args);
180
205
  }
181
206
  }
182
- toConfig() {
207
+ /**
208
+ * Internal logic to determine if a command execution should be throttled.
209
+ * @param config The merged configuration to use for limits.
210
+ * @param ctx Extracted Discord context (User, Guild, Channel).
211
+ */
212
+ isRateLimited(config, ctx) {
213
+ if (!config.rateLimit) return false;
214
+ const { scope, interval, max } = config.rateLimit;
215
+ const now = Date.now();
216
+ const key = this.getScopeKey(scope, ctx);
217
+ if (scope !== 3 /* Global */ && !key) return false;
218
+ let data;
219
+ if (scope === 3 /* Global */) {
220
+ data = this.rlStores[3 /* Global */];
221
+ } else {
222
+ const store = this.rlStores[scope];
223
+ data = store.get(key) ?? { executions: 0, timestamp: now };
224
+ store.set(key, data);
225
+ }
226
+ if (now - data.timestamp > interval) {
227
+ data.executions = 0;
228
+ data.timestamp = now;
229
+ }
230
+ if (data.executions >= max) return true;
231
+ data.executions++;
232
+ return false;
233
+ }
234
+ /**
235
+ * Validates if the user has required permissions.
236
+ */
237
+ checkPermissions(client, user, target) {
238
+ if (!this.options.permissions) return { validated: true };
239
+ return validateCommandPermissions(this.options.permissions, client, user, target);
240
+ }
241
+ /**
242
+ * Evaluates all custom conditions defined for the command.
243
+ */
244
+ async checkConditions(config, ...args) {
245
+ if (!config.conditions?.length) return true;
246
+ const results = await Promise.all(config.conditions.map((c) => c(...args)));
247
+ return results.every(Boolean);
248
+ }
249
+ /**
250
+ * Normalizes the trigger arguments into a standard context object.
251
+ */
252
+ extractContext(args) {
253
+ const event = args[1];
183
254
  return {
184
- enabled: this.enabled,
185
- conditions: this.conditions,
186
- permissions: this.permissions,
187
- metadata: this.metadata,
188
- rateLimit: this.rateLimit,
189
- beforeExecute: this.beforeExecute,
190
- execute: this.execute,
191
- afterExecute: this.afterExecute,
192
- onMissingPermissions: this.onMissingPermissions,
193
- onConditionsNotMet: this.onConditionsNotMet,
194
- onUsedWhenDisabled: this.onUsedWhenDisabled,
195
- onRateLimit: this.onRateLimit,
196
- onError: this.onError
255
+ user: event.user || event.author,
256
+ member: event.member,
257
+ guild: event.guild,
258
+ channel: event.channel
197
259
  };
198
260
  }
261
+ /**
262
+ * Resolves the storage key based on the RateLimit scope.
263
+ */
264
+ getScopeKey(scope, ctx) {
265
+ switch (scope) {
266
+ case 0 /* User */:
267
+ return ctx.user.id;
268
+ case 1 /* Guild */:
269
+ return ctx.guild?.id ?? null;
270
+ case 2 /* Channel */:
271
+ return ctx.channel?.id ?? null;
272
+ default:
273
+ return null;
274
+ }
275
+ }
276
+ /**
277
+ * Handles command errors by checking local handlers before falling back to global handlers.
278
+ */
279
+ async handleError(err, config, ...args) {
280
+ if (config.onError) return config.onError(err, ...args);
281
+ throw err;
282
+ }
283
+ /** Toggle command availability */
199
284
  setEnabled(enabled) {
200
- this.enabled = enabled;
285
+ this.options.enabled = enabled;
286
+ return this;
287
+ }
288
+ /** Merge new permission requirements into the existing ones */
289
+ setPermissions(perms) {
290
+ this.options.permissions = _.merge(this.options.permissions || {}, perms);
201
291
  return this;
202
292
  }
293
+ /** Add custom logic checks that run before execution */
203
294
  addConditions(...conditions) {
204
- if (!this.conditions) this.conditions = [];
205
- this.conditions.push(...conditions);
295
+ this.options.conditions = [...this.options.conditions || [], ...conditions];
206
296
  return this;
207
297
  }
208
- setConditions(conditions) {
209
- this.conditions = conditions;
298
+ /** Set the primary command execution logic */
299
+ setExecute(fn) {
300
+ this.options.execute = fn;
210
301
  return this;
211
302
  }
212
- setPermissions(permissions) {
213
- this.permissions = _.merge(this.permissions, permissions);
303
+ /** * Set the custom conditions that must be met for this command to execute
304
+ */
305
+ setConditions(conditions) {
306
+ this.options.conditions = conditions;
214
307
  return this;
215
308
  }
309
+ /** * Set the command metadata configuration
310
+ */
216
311
  setMetadata(metadata) {
217
- this.metadata = _.merge(this.metadata, metadata);
312
+ this.options.metadata = _.merge(this.options.metadata || {}, metadata);
218
313
  return this;
219
314
  }
315
+ /** * Set the rate limiting options for this command
316
+ */
220
317
  setRateLimit(options) {
221
- this.rateLimit = options;
318
+ this.options.rateLimit = options;
319
+ this.validateBaseConfig();
222
320
  return this;
223
321
  }
224
- setBeforeExecute(callback) {
225
- this.beforeExecute = callback;
322
+ /** * Set whether to log whenever this command is executed
323
+ * @default true
324
+ */
325
+ setLogExecution(log) {
326
+ this.options.logExecution = log;
226
327
  return this;
227
328
  }
228
- setExecute(callback) {
229
- this.execute = callback;
329
+ /** * Set the function to execute before the main command logic
330
+ */
331
+ setBeforeExecute(callback) {
332
+ this.options.beforeExecute = callback;
230
333
  return this;
231
334
  }
335
+ /** * Set the function to execute after successful command execution
336
+ */
232
337
  setAfterExecute(callback) {
233
- this.afterExecute = callback;
234
- return this;
235
- }
236
- setOnRateLimit(callback) {
237
- this.onRateLimit = callback;
338
+ this.options.afterExecute = callback;
238
339
  return this;
239
340
  }
341
+ /** * Set the function to execute when the required permissions are not met
342
+ */
240
343
  setOnMissingPermissions(callback) {
241
- this.onMissingPermissions = callback;
344
+ this.options.onMissingPermissions = callback;
242
345
  return this;
243
346
  }
347
+ /** * Set the function to execute when the required conditions are not met
348
+ */
244
349
  setOnConditionsNotMet(callback) {
245
- this.onConditionsNotMet = callback;
350
+ this.options.onConditionsNotMet = callback;
246
351
  return this;
247
352
  }
353
+ /** * Set the function to execute when this command is used when its disabled
354
+ */
248
355
  setOnUsedWhenDisabled(callback) {
249
- this.onUsedWhenDisabled = callback;
356
+ this.options.onUsedWhenDisabled = callback;
250
357
  return this;
251
358
  }
252
- setOnError(callback) {
253
- this.onError = callback;
359
+ /** * Set the function to execute when the rate limit is hit
360
+ */
361
+ setOnRateLimit(callback) {
362
+ this.options.onRateLimit = callback;
254
363
  return this;
255
364
  }
256
- getRateLimitInfo(user, guild, channel) {
257
- if (!this.rateLimit) return null;
258
- let rateLimitData;
259
- switch (this.rateLimit.scope) {
260
- case 0 /* User */:
261
- if (!user) return null;
262
- rateLimitData = this.userRateLimitData.get(user.id) ?? { executions: 0, timestamp: 0 };
263
- break;
264
- case 1 /* Guild */:
265
- if (!guild) return null;
266
- rateLimitData = this.guildRateLimitData.get(guild.id) ?? { executions: 0, timestamp: 0 };
267
- break;
268
- case 2 /* Channel */:
269
- if (!channel) return null;
270
- rateLimitData = this.channelRateLimitData.get(channel.id) ?? { executions: 0, timestamp: 0 };
271
- break;
272
- case 3 /* Global */:
273
- rateLimitData = this.globalRateLimitData;
274
- break;
275
- default:
276
- return null;
277
- }
278
- return { ...rateLimitData, isLimited: this.isRateLimited(user, guild, channel, false) };
279
- }
280
- isRateLimited(user, guild, channel, updateExecutions = true) {
281
- if (!this.rateLimit) return false;
282
- const now = Date.now();
283
- let rateLimitData;
284
- switch (this.rateLimit.scope) {
285
- case 0 /* User */:
286
- if (!user) return false;
287
- rateLimitData = this.userRateLimitData.get(user.id) ?? { executions: 0, timestamp: 0 };
288
- if (now - rateLimitData.timestamp >= this.rateLimit.interval) {
289
- rateLimitData.executions = 0;
290
- rateLimitData.timestamp = now;
291
- }
292
- if (updateExecutions) rateLimitData.executions++;
293
- this.userRateLimitData.set(user.id, rateLimitData);
294
- break;
295
- case 1 /* Guild */:
296
- if (!guild) return false;
297
- rateLimitData = this.guildRateLimitData.get(guild.id) ?? { executions: 0, timestamp: 0 };
298
- if (now - rateLimitData.timestamp >= this.rateLimit.interval) {
299
- rateLimitData.executions = 0;
300
- rateLimitData.timestamp = now;
301
- }
302
- if (updateExecutions) rateLimitData.executions++;
303
- this.guildRateLimitData.set(guild.id, rateLimitData);
304
- break;
305
- case 2 /* Channel */:
306
- if (!channel) return false;
307
- rateLimitData = this.guildRateLimitData.get(channel.id) ?? { executions: 0, timestamp: 0 };
308
- if (now - rateLimitData.timestamp >= this.rateLimit.interval) {
309
- rateLimitData.executions = 0;
310
- rateLimitData.timestamp = now;
311
- }
312
- if (updateExecutions) rateLimitData.executions++;
313
- this.channelRateLimitData.set(channel.id, rateLimitData);
314
- break;
315
- case 3 /* Global */:
316
- rateLimitData = this.globalRateLimitData;
317
- if (now - rateLimitData.timestamp >= this.rateLimit.interval) {
318
- rateLimitData.executions = 0;
319
- rateLimitData.timestamp = now;
320
- }
321
- if (updateExecutions) rateLimitData.executions++;
322
- this.globalRateLimitData = rateLimitData;
323
- break;
324
- }
325
- return rateLimitData.executions >= this.rateLimit.max;
326
- }
327
- checkPermissions(client, user, command) {
328
- if (!this.permissions) return { validated: true };
329
- return validateCommandPermissions(this.permissions, client, user, command);
330
- }
331
- async checkConditions(...args) {
332
- if (!this.conditions?.length) return true;
333
- const results = await Promise.all(this.conditions.map((condition) => condition(...args)));
334
- return results.every(Boolean);
335
- }
336
- async _runPreflight(clientConfig, command, ...args) {
337
- try {
338
- if (!this.enabled) {
339
- await clientConfig.onUsedWhenDisabled?.(...args);
340
- await this.onUsedWhenDisabled?.(...args);
341
- return;
342
- }
343
- if (this.isRateLimited(args[1]?.member || args[1]?.author, args[1].guild, args[1].channel)) {
344
- await clientConfig.onRateLimit?.(...args);
345
- await this.rateLimit?.onRateLimit?.(...args);
346
- return;
347
- }
348
- const permissionResults = this.checkPermissions(
349
- args[0],
350
- args[1]?.member || args[1]?.author || args[1]?.user,
351
- command
352
- );
353
- if (!permissionResults.validated) {
354
- await clientConfig.onMissingPermissions?.(permissionResults, ...args);
355
- await this.onMissingPermissions?.(permissionResults, ...args);
356
- return;
357
- }
358
- if (!await this.checkConditions(...args)) {
359
- await clientConfig.onConditionsNotMet?.(...args);
360
- await this.onConditionsNotMet?.(...args);
361
- return;
362
- }
363
- } catch (err) {
364
- if (this.onError) {
365
- return this.onError(err, ...args);
366
- } else if (clientConfig.onError) {
367
- return clientConfig.onError(err, ...args);
368
- } else {
369
- throw err;
370
- }
371
- }
372
- }
373
- async _runCabinCheck(clientConfig, ...args) {
374
- try {
375
- await clientConfig.beforeExecute?.(...args);
376
- await this.beforeExecute?.(...args);
377
- } catch (err) {
378
- if (this.onError) {
379
- return this.onError(err, ...args);
380
- } else if (clientConfig.onError) {
381
- return clientConfig.onError(err, ...args);
382
- } else {
383
- throw err;
384
- }
385
- }
386
- }
387
- async _runFlyAndLand(clientConfig, ...args) {
388
- try {
389
- const result = await this.execute?.(...args);
390
- await clientConfig?.afterExecute?.(result, ...args);
391
- await this.afterExecute?.(result, ...args);
392
- } catch (err) {
393
- if (this.onError) {
394
- return this.onError(err, ...args);
395
- } else if (clientConfig.onError) {
396
- return clientConfig.onError(err, ...args);
397
- } else {
398
- throw err;
399
- }
400
- }
365
+ /** * Set a custom error handler for this command
366
+ */
367
+ setOnError(callback) {
368
+ this.options.onError = callback;
369
+ return this;
401
370
  }
402
371
  };
403
372
 
@@ -406,79 +375,54 @@ import { ContextMenuCommandBuilder } from "discord.js";
406
375
  import _2 from "lodash";
407
376
  var ContextCommandBuilder = class extends BaseCommandBuilder {
408
377
  builder;
409
- deferReply;
410
- deployment;
411
378
  constructor(config) {
412
379
  super(2 /* Context */, config);
413
380
  this.setBuilder(config.builder);
414
- this.deferReply = config?.deferReply;
415
- this.deployment = config?.deployment;
416
- this.validateConfig();
381
+ const originalExecute = this.options.execute;
382
+ this.options.execute = async (client, interaction) => {
383
+ return await this.handleExecution(client, interaction, originalExecute);
384
+ };
417
385
  }
418
- validateBuilder() {
419
- if (!this.builder.name) {
420
- throw new Error("VContextCommandBuilder: Command name is required");
421
- }
422
- try {
423
- this.builder.toJSON();
424
- } catch (err) {
425
- throw new Error("VContextCommandBuilder: Invalid command builder", { cause: err });
386
+ async handleExecution(client, interaction, originalExecute) {
387
+ const config = this.resolveConfig(client);
388
+ if (config.deferReply && !interaction.replied && !interaction.deferred) {
389
+ await interaction.deferReply(typeof config.deferReply === "object" ? config.deferReply : void 0);
426
390
  }
391
+ return await originalExecute?.(client, interaction);
427
392
  }
428
- validateConfig() {
429
- }
430
- toConfig() {
431
- return {
432
- ...super.toConfig(),
433
- builder: this.builder,
434
- deferReply: this.deferReply,
435
- deployment: this.deployment
436
- };
393
+ validateBuilder() {
394
+ if (!this.builder.name) throw new Error(`[Vimcord] ContextCommandBuilder: Command name is required.`);
395
+ this.builder.toJSON();
437
396
  }
397
+ // --- Specialized Fluent API ---
438
398
  setBuilder(builder) {
439
- if (typeof builder === "function") {
440
- this.builder = builder(new ContextMenuCommandBuilder());
441
- } else {
442
- this.builder = builder;
443
- }
399
+ this.builder = typeof builder === "function" ? builder(new ContextMenuCommandBuilder()) : builder;
444
400
  this.validateBuilder();
445
401
  return this;
446
402
  }
447
403
  setDeferReply(defer) {
448
- this.deferReply = defer;
404
+ this.options.deferReply = defer;
449
405
  return this;
450
406
  }
451
407
  setDeployment(deployment) {
452
- this.deployment = _2.merge(this.deployment, deployment);
408
+ this.options.deployment = _2.merge(this.options.deployment || {}, deployment);
453
409
  return this;
454
410
  }
455
- async executeCommand(client, interaction) {
456
- try {
457
- await this._runPreflight(client.config.contextCommands, interaction, client, interaction);
458
- await this._runCabinCheck(client.config.contextCommands, client, interaction);
459
- if (this.deferReply && !interaction.replied && !interaction.deferred) {
460
- await interaction.deferReply(typeof this.deferReply === "object" ? this.deferReply : void 0);
461
- }
462
- return this._runFlyAndLand(client.config.contextCommands, client, interaction);
463
- } catch (err) {
464
- if (this.onError) {
465
- return this.onError(err, client, interaction);
466
- } else if (client.config.contextCommands.onError) {
467
- return client.config.contextCommands.onError(err, client, interaction);
468
- } else {
469
- throw err;
470
- }
471
- } finally {
472
- if (client.config.contextCommands.logExecution) {
473
- client.logger.commandExecuted(this.builder.name, interaction.user.username, interaction.guild?.name);
474
- }
475
- }
411
+ setExecute(fn) {
412
+ const originalExecute = fn;
413
+ this.options.execute = async (client, interaction) => {
414
+ return await this.handleExecution(client, interaction, originalExecute);
415
+ };
416
+ return this;
417
+ }
418
+ toConfig() {
419
+ return { ...this.options, builder: this.builder };
476
420
  }
477
421
  };
478
422
 
479
423
  // src/utils/dir.ts
480
- import jsTools from "jstools";
481
424
  import path from "path";
425
+ import { $ } from "qznt";
482
426
  function getProcessDir() {
483
427
  const mainPath = process.argv[1];
484
428
  if (!mainPath) return "";
@@ -488,7 +432,7 @@ async function importModulesFromDir(dir, fnPrefix) {
488
432
  const cwd = getProcessDir();
489
433
  const MODULE_RELATIVE_PATH = path.join(cwd, dir);
490
434
  const MODULE_LOG_PATH = dir;
491
- const files = jsTools.readDir(MODULE_RELATIVE_PATH, { recursive: true }).filter(
435
+ const files = $.fs.readDir(MODULE_RELATIVE_PATH).filter(
492
436
  (fn) => fn.endsWith(`${fnPrefix ? `.${fnPrefix}` : ""}.js`) || fn.endsWith(`${fnPrefix ? `.${fnPrefix}` : ""}.ts`)
493
437
  );
494
438
  if (!files.length) {
@@ -719,39 +663,74 @@ var EventBuilder = class _EventBuilder {
719
663
 
720
664
  // src/builders/prefixCommand.builder.ts
721
665
  var PrefixCommandBuilder = class extends BaseCommandBuilder {
722
- name;
723
- aliases;
724
- description;
725
666
  constructor(config) {
726
667
  super(1 /* Prefix */, config);
727
- this.name = config.name;
728
- this.description = config.description;
729
- this.validateConfig();
668
+ const originalExecute = this.options.execute;
669
+ this.options.execute = async (client, message) => {
670
+ return await this.handleExecution(client, message, originalExecute);
671
+ };
672
+ this.validatePrefixConfig();
730
673
  }
731
- validateConfig() {
674
+ /**
675
+ * Specialized execution logic for Prefix Commands.
676
+ */
677
+ async handleExecution(client, message, originalExecute) {
678
+ return await originalExecute?.(client, message);
679
+ }
680
+ validatePrefixConfig() {
681
+ if (!this.options.name) {
682
+ throw new Error(`[Vimcord] PrefixCommandBuilder: Command name is required.`);
683
+ }
732
684
  }
685
+ // --- Fluent API (Prefix Specific Only) ---
686
+ /**
687
+ * Set the primary name of the command.
688
+ */
689
+ setName(name) {
690
+ this.options.name = name;
691
+ return this;
692
+ }
693
+ /**
694
+ * Set or replace the command aliases.
695
+ */
696
+ setAliases(aliases) {
697
+ this.options.aliases = aliases;
698
+ return this;
699
+ }
700
+ /**
701
+ * Add additional aliases without clearing existing ones.
702
+ */
703
+ addAliases(...aliases) {
704
+ this.options.aliases = [...this.options.aliases || [], ...aliases];
705
+ return this;
706
+ }
707
+ /**
708
+ * Set the command description.
709
+ */
710
+ setDescription(description) {
711
+ this.options.description = description;
712
+ return this;
713
+ }
714
+ // --- Overrides ---
715
+ /**
716
+ * Override setExecute to ensure handleExecution remains the entry point.
717
+ * This is the only base method we need to redefine.
718
+ */
719
+ setExecute(fn) {
720
+ const originalExecute = fn;
721
+ this.options.execute = async (client, message) => {
722
+ return await this.handleExecution(client, message, originalExecute);
723
+ };
724
+ return this;
725
+ }
726
+ /**
727
+ * Converts the current builder state back into a config object.
728
+ */
733
729
  toConfig() {
734
730
  return {
735
- ...super.toConfig(),
736
- name: this.name,
737
- description: this.description
731
+ ...this.options
738
732
  };
739
733
  }
740
- async executeCommand(client, message) {
741
- try {
742
- await this._runPreflight(client.config.prefixCommands, this.name, client, message);
743
- await this._runCabinCheck(client.config.prefixCommands, client, message);
744
- return this._runFlyAndLand(client.config.prefixCommands, client, message);
745
- } catch (err) {
746
- this.onError?.(err, client, message);
747
- client.config.prefixCommands.onError?.(err, client, message);
748
- throw err;
749
- } finally {
750
- if (client.config.prefixCommands.logExecution) {
751
- client.logger.commandExecuted(this.name, message.author.username, message.guild?.name);
752
- }
753
- }
754
- }
755
734
  };
756
735
 
757
736
  // src/builders/slashCommand.builder.ts
@@ -759,110 +738,81 @@ import { SlashCommandBuilder as DJSSlashCommandBuilder } from "discord.js";
759
738
  import _4 from "lodash";
760
739
  var SlashCommandBuilder = class extends BaseCommandBuilder {
761
740
  builder;
762
- deferReply;
763
- deployment;
764
741
  routes = /* @__PURE__ */ new Map();
765
- onUnknownRouteHandler;
766
742
  constructor(config) {
767
743
  super(0 /* Slash */, config);
768
744
  this.setBuilder(config.builder);
769
- this.setRoutes(...config.routes ?? []);
770
- this.deferReply = config?.deferReply;
771
- this.deployment = config?.deployment;
772
- this.validateConfig();
745
+ if (config.routes) this.addRoutes(...config.routes);
746
+ const originalExecute = this.options.execute;
747
+ this.options.execute = async (client, interaction) => {
748
+ return await this.handleExecution(client, interaction, originalExecute);
749
+ };
773
750
  }
774
- validateBuilder() {
775
- if (!this.builder.name) {
776
- throw new Error("VSlashCommandBuilder: Command name is required");
751
+ async handleExecution(client, interaction, originalExecute) {
752
+ const config = this.resolveConfig(client);
753
+ if (config.deferReply && !interaction.replied && !interaction.deferred) {
754
+ await interaction.deferReply(typeof config.deferReply === "object" ? config.deferReply : void 0);
777
755
  }
778
- if (!this.builder.description) {
779
- throw new Error("VSlashCommandBuilder: Command description is required");
756
+ const subCommand = interaction.options.getSubcommand(false);
757
+ if (subCommand) {
758
+ const handler = this.routes.get(subCommand.toLowerCase());
759
+ if (handler) return await handler(client, interaction);
760
+ if (config.onUnknownRouteHandler) return await config.onUnknownRouteHandler(client, interaction);
780
761
  }
781
- try {
782
- this.builder.toJSON();
783
- } catch (err) {
784
- throw new Error("VSlashCommandBuilder: Invalid command builder", { cause: err });
785
- }
786
- }
787
- validateConfig() {
762
+ return await originalExecute?.(client, interaction);
788
763
  }
789
- toConfig() {
790
- return {
791
- ...super.toConfig(),
792
- builder: this.builder,
793
- deferReply: this.deferReply,
794
- deployment: this.deployment,
795
- routes: this.routes.size ? Array.from(this.routes.entries()).map(([k, v]) => ({ name: k, handler: v })) : void 0,
796
- onUnknownRouteHandler: this.onUnknownRouteHandler
797
- };
764
+ validateBuilder() {
765
+ if (!this.builder.name) throw new Error(`[Vimcord] SlashCommandBuilder: Command name is required.`);
766
+ if (!this.builder.description) throw new Error(`[Vimcord] SlashCommandBuilder: Command description is required.`);
767
+ this.builder.toJSON();
798
768
  }
769
+ // --- Specialized Fluent API ---
799
770
  setBuilder(builder) {
800
- if (typeof builder === "function") {
801
- this.builder = builder(new DJSSlashCommandBuilder());
802
- } else {
803
- this.builder = builder;
804
- }
771
+ this.builder = typeof builder === "function" ? builder(new DJSSlashCommandBuilder()) : builder;
805
772
  this.validateBuilder();
806
773
  return this;
807
774
  }
808
775
  setDeferReply(defer) {
809
- this.deferReply = defer;
776
+ this.options.deferReply = defer;
810
777
  return this;
811
778
  }
812
779
  setDeployment(deployment) {
813
- this.deployment = _4.merge(this.deployment, deployment);
780
+ this.options.deployment = _4.merge(this.options.deployment || {}, deployment);
814
781
  return this;
815
782
  }
816
783
  setRoutes(...routes) {
817
- if (routes.length) {
818
- for (const route of routes) {
819
- this.routes.set(route.name.toLowerCase(), route.handler);
820
- }
821
- } else {
822
- this.routes = /* @__PURE__ */ new Map();
823
- }
784
+ this.routes.clear();
785
+ this.addRoutes(...routes);
824
786
  return this;
825
787
  }
826
788
  addRoutes(...routes) {
789
+ if (!this.options.routes) this.options.routes = [];
827
790
  for (const route of routes) {
828
- this.routes.set(route.name.toLowerCase(), route.handler);
791
+ const name = route.name.toLowerCase();
792
+ this.routes.set(name, route.handler);
793
+ const existingIndex = this.options.routes.findIndex((r) => r.name.toLowerCase() === name);
794
+ if (existingIndex > -1) this.options.routes[existingIndex] = route;
795
+ else this.options.routes.push(route);
829
796
  }
830
797
  return this;
831
798
  }
832
799
  setUnknownRouteHandler(handler) {
833
- this.onUnknownRouteHandler = handler;
800
+ this.options.onUnknownRouteHandler = handler;
834
801
  return this;
835
802
  }
836
- async executeCommand(client, interaction) {
837
- try {
838
- await this._runPreflight(client.config.slashCommands, interaction, client, interaction);
839
- await this._runCabinCheck(client.config.slashCommands, client, interaction);
840
- if (this.deferReply && !interaction.replied && !interaction.deferred) {
841
- await interaction.deferReply(typeof this.deferReply === "object" ? this.deferReply : void 0);
842
- }
843
- if (this.routes.size) {
844
- const subCommand = interaction.options.getSubcommand();
845
- const route = this.routes.get(subCommand);
846
- if (route) {
847
- const result = await route(client, interaction);
848
- await this.afterExecute?.(result, client, interaction);
849
- return result;
850
- } else if (this.onUnknownRouteHandler) {
851
- const result = await this.onUnknownRouteHandler?.(client, interaction);
852
- await this.afterExecute?.(result, client, interaction);
853
- return result;
854
- }
855
- }
856
- return this._runFlyAndLand(client.config.slashCommands, client, interaction);
857
- } catch (err) {
858
- this.onError?.(err, client, interaction);
859
- client.config.slashCommands.onError?.(err, client, interaction);
860
- throw err;
861
- } finally {
862
- if (client.config.slashCommands.logExecution) {
863
- client.logger.commandExecuted(this.builder.name, interaction.user.username, interaction.guild?.name);
864
- }
865
- }
803
+ setExecute(fn) {
804
+ const originalExecute = fn;
805
+ this.options.execute = async (client, interaction) => {
806
+ return await this.handleExecution(client, interaction, originalExecute);
807
+ };
808
+ return this;
809
+ }
810
+ toConfig() {
811
+ return {
812
+ ...this.options,
813
+ builder: this.builder,
814
+ routes: Array.from(this.routes.entries()).map(([name, handler]) => ({ name, handler }))
815
+ };
866
816
  }
867
817
  };
868
818
 
@@ -877,12 +827,19 @@ var globalVimcordToolsConfig = {
877
827
  embedColor: [],
878
828
  embedColorDev: [],
879
829
  timeouts: {
830
+ collectorTimeout: 6e4,
831
+ collectorIdle: 6e4,
880
832
  pagination: 6e4,
881
833
  prompt: 3e4,
882
834
  modalSubmit: 6e4
883
835
  },
836
+ collector: {
837
+ notAParticipantMessage: "You are not allowed to use this.",
838
+ userLockMessage: "Please wait until your current action is finished.",
839
+ notAParticipantWarningCooldown: 5e3
840
+ },
884
841
  paginator: {
885
- notAParticipantMessage: "You are not allowed to use this!",
842
+ notAParticipantMessage: "You are not allowed to use this.",
886
843
  jumpableThreshold: 5,
887
844
  longThreshold: 4,
888
845
  buttons: {
@@ -895,7 +852,7 @@ var globalVimcordToolsConfig = {
895
852
  },
896
853
  prompt: {
897
854
  defaultTitle: "Are you sure?",
898
- defaultDescription: "Make sure you know what you're doing!",
855
+ defaultDescription: "Make sure you know what you're doing.",
899
856
  confirmLabel: "Confirm",
900
857
  rejectLabel: "Cancel"
901
858
  }
@@ -985,7 +942,7 @@ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
985
942
  LogLevel2[LogLevel2["ERROR"] = 4] = "ERROR";
986
943
  return LogLevel2;
987
944
  })(LogLevel || {});
988
- var DEFAULT_COLORS = {
945
+ var LOGGER_COLORS = {
989
946
  primary: "#5865F2",
990
947
  success: "#57F287",
991
948
  warn: "#FEE75C",
@@ -1006,7 +963,7 @@ var Logger = class {
1006
963
  this.minLevel = minLevel;
1007
964
  this.showTimestamp = showTimestamp;
1008
965
  this.colorScheme = {
1009
- ...DEFAULT_COLORS,
966
+ ...LOGGER_COLORS,
1010
967
  ...options?.colors
1011
968
  };
1012
969
  }
@@ -1067,7 +1024,7 @@ var Logger = class {
1067
1024
  }
1068
1025
  setColors(colors) {
1069
1026
  this.colorScheme = {
1070
- ...DEFAULT_COLORS,
1027
+ ...LOGGER_COLORS,
1071
1028
  ...colors
1072
1029
  };
1073
1030
  return this;
@@ -1280,28 +1237,9 @@ function formatThousands(num, sep = ",") {
1280
1237
  return `${num}`.replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, sep);
1281
1238
  }
1282
1239
 
1283
- // src/utils/random.ts
1284
- function pickRandom(arr, options) {
1285
- const _rnd = () => {
1286
- return arr[Math.floor(Math.random() * arr.length)];
1287
- };
1288
- let att = 0;
1289
- let candidate = _rnd();
1290
- if (options?.notEqualTo !== void 0 && arr.length > 1) {
1291
- while (candidate === options.notEqualTo) {
1292
- if (att < (options?.maxRerollAttempts ?? 100)) {
1293
- throw new Error(`pickRandom reached max reroll attempts (${options?.maxRerollAttempts ?? 100})`);
1294
- }
1295
- candidate = _rnd();
1296
- att++;
1297
- }
1298
- }
1299
- return options?.clone ? structuredClone(candidate) : candidate;
1300
- }
1301
-
1302
1240
  // src/modules/status.manager.ts
1303
- import cron from "node-cron";
1304
1241
  import EventEmitter from "events";
1242
+ import { $ as $2 } from "qznt";
1305
1243
  var VimcordStatusManager = class {
1306
1244
  client;
1307
1245
  logger;
@@ -1322,24 +1260,9 @@ var VimcordStatusManager = class {
1322
1260
  this.logger.debug("Status cleared");
1323
1261
  }
1324
1262
  });
1325
- this.emitter.on("rotation", () => {
1326
- if (this.client.config.app.verbose) {
1327
- this.logger.debug("Status rotated");
1328
- }
1329
- });
1330
- this.emitter.on("rotationStarted", () => {
1331
- if (this.client.config.app.verbose) {
1332
- this.logger.debug("Status rotation resumed \u25B6\uFE0F");
1333
- }
1334
- });
1335
- this.emitter.on("rotationPaused", () => {
1336
- if (this.client.config.app.verbose) {
1337
- this.logger.debug("Status rotation paused \u23F8\uFE0F");
1338
- }
1339
- });
1340
1263
  }
1341
1264
  clearData() {
1342
- this.task?.destroy();
1265
+ this.task?.stop();
1343
1266
  this.task = null;
1344
1267
  this.lastActivity = null;
1345
1268
  this.lastActivityIndex = 0;
@@ -1372,11 +1295,8 @@ var VimcordStatusManager = class {
1372
1295
  }
1373
1296
  async statusRotationTask(clientStatus) {
1374
1297
  let activity;
1375
- if (clientStatus.randomize) {
1376
- activity = pickRandom(clientStatus.activity, {
1377
- notEqualTo: this.lastActivity,
1378
- clone: true
1379
- });
1298
+ if (clientStatus.randomize && Array.isArray(clientStatus.activity)) {
1299
+ activity = $2.rnd.choice(clientStatus.activity, { not: this.lastActivity });
1380
1300
  this.lastActivity = activity;
1381
1301
  } else {
1382
1302
  const activityIndex = (this.lastActivityIndex + 1) % clientStatus.activity.length;
@@ -1388,34 +1308,22 @@ var VimcordStatusManager = class {
1388
1308
  }
1389
1309
  async scheduleStatusRotation(clientStatus) {
1390
1310
  if (!clientStatus.interval) throw new Error("Cannot create client activity interval without interval time");
1391
- this.task?.destroy();
1311
+ this.task?.stop();
1392
1312
  this.task = null;
1393
- await this.statusRotationTask(clientStatus);
1394
- this.task = cron.createTask(
1395
- `*/${clientStatus.interval} * * * * *`,
1396
- async () => await this.statusRotationTask(clientStatus),
1397
- { noOverlap: true }
1398
- );
1313
+ this.task = new $2.Loop(() => this.statusRotationTask(clientStatus), $2.math.ms(clientStatus.interval), true);
1399
1314
  this.start();
1400
- this.emitter.emit("rotationStarted", this.task);
1401
1315
  }
1402
1316
  start() {
1403
1317
  if (this.task) {
1404
1318
  this.task.start();
1405
- this.emitter.emit("rotationStarted", this.task);
1406
- if (this.client.config.app.verbose) {
1407
- this.logger.debug("Status rotation started");
1408
- }
1319
+ this.emitter.emit("started", this.task);
1409
1320
  }
1410
1321
  return this;
1411
1322
  }
1412
1323
  pause() {
1413
1324
  if (this.task) {
1414
1325
  this.task.stop();
1415
- this.emitter.emit("rotationPaused", this.task);
1416
- if (this.client.config.app.verbose) {
1417
- this.logger.debug("Status rotation paused");
1418
- }
1326
+ this.emitter.emit("paused", this.task);
1419
1327
  }
1420
1328
  return this;
1421
1329
  }
@@ -1429,21 +1337,16 @@ var VimcordStatusManager = class {
1429
1337
  }
1430
1338
  if (!clientStatus.interval) {
1431
1339
  await this.setActivity(Array.isArray(clientStatus.activity) ? clientStatus.activity[0] : clientStatus.activity);
1432
- return this;
1433
- }
1434
- if (clientStatus.interval) {
1340
+ } else {
1435
1341
  await this.scheduleStatusRotation(clientStatus);
1436
1342
  }
1437
1343
  return this;
1438
1344
  }
1439
1345
  async destroy() {
1440
1346
  if (this.task) {
1441
- this.task.destroy();
1347
+ this.task.stop();
1442
1348
  this.task = null;
1443
- this.emitter.emit("rotationDestroyed");
1444
- if (this.client.config.app.verbose) {
1445
- this.logger.debug("Status rotation destroyed");
1446
- }
1349
+ this.emitter.emit("destroyed");
1447
1350
  await this.clear();
1448
1351
  }
1449
1352
  return this;
@@ -1453,245 +1356,193 @@ var VimcordStatusManager = class {
1453
1356
  this.clearData();
1454
1357
  client.user.setActivity({ name: "" });
1455
1358
  this.emitter.emit("cleared");
1456
- if (this.client.config.app.verbose) {
1457
- this.logger.debug("Status cleared");
1458
- }
1459
1359
  return this;
1460
1360
  }
1461
1361
  };
1462
1362
 
1463
1363
  // src/modules/command.manager.ts
1464
1364
  import { REST, Routes } from "discord.js";
1465
- var VimcordCommandManager = class {
1466
- slash;
1467
- prefix;
1468
- context;
1469
- constructor(client) {
1470
- this.slash = new VimcordSlashCommandManager(client);
1471
- this.prefix = new VimcordPrefixCommandManager(client);
1472
- this.context = new VimcordContextCommandManager(client);
1473
- }
1474
- };
1475
- var VimcordSlashCommandManager = class {
1476
- constructor(client) {
1365
+ var VimcordAppCommandManager = class {
1366
+ constructor(client, typeName) {
1477
1367
  this.client = client;
1478
- client.whenReady().then((c) => this.rest = new REST().setToken(c.token));
1368
+ this.typeName = typeName;
1369
+ this.client.whenReady().then((c) => this.rest = new REST().setToken(c.token));
1479
1370
  }
1480
1371
  commands = /* @__PURE__ */ new Map();
1481
1372
  rest;
1482
1373
  get(name) {
1483
1374
  return this.commands.get(name);
1484
1375
  }
1376
+ /**
1377
+ * Filters and returns commands based on deployment options
1378
+ */
1485
1379
  getAll(options) {
1486
1380
  const matchedCommands = /* @__PURE__ */ new Map();
1381
+ const isDev = this.client.config.app.devMode;
1487
1382
  for (const cmd of this.commands.values()) {
1488
- let matched = null;
1489
- if (options?.names || options?.fuzzyNames || options?.guilds) {
1490
- const nameMatched = (
1491
- // Exact name match
1492
- options.names?.includes(cmd.builder.name) || // Fuzzy match
1493
- options.fuzzyNames?.some((fuzzyCommand) => cmd.builder.name.includes(fuzzyCommand))
1494
- );
1495
- if ((options.names || options.fuzzyNames) && !nameMatched) continue;
1496
- const guildMatched = cmd.deployment?.guilds?.some((guildId) => options.guilds?.includes(guildId));
1497
- if (options.guilds && !guildMatched) continue;
1498
- matched = cmd;
1499
- } else {
1500
- matched = cmd;
1383
+ const config = cmd.toConfig();
1384
+ const name = cmd.builder.name;
1385
+ if (options?.names || options?.fuzzyNames) {
1386
+ const nameMatched = options.names?.includes(name) || options.fuzzyNames?.some((fuzzy) => name.includes(fuzzy));
1387
+ if (!nameMatched) continue;
1501
1388
  }
1502
- if (!matched) continue;
1503
1389
  if (options?.ignoreDeploymentOptions) {
1504
- if (matchedCommands.has(matched.builder.name)) continue;
1505
- matchedCommands.set(matched.builder.name, matched);
1390
+ matchedCommands.set(name, cmd);
1506
1391
  continue;
1507
1392
  }
1508
- const isProperEnvironment = matched?.deployment?.environments?.includes(
1509
- this.client.config.app.devMode ? "development" : "production"
1510
- );
1511
- if ((isProperEnvironment ?? true) && ((options?.globalOnly && (matched?.deployment?.global ?? true)) ?? true)) {
1512
- matchedCommands.set(matched.builder.name, matched);
1513
- }
1393
+ const deployment = config.deployment || {};
1394
+ const isProperEnv = !deployment.environments || deployment.environments.includes(isDev ? "development" : "production");
1395
+ if (!isProperEnv) continue;
1396
+ if (options?.globalOnly && deployment.global === false) continue;
1397
+ matchedCommands.set(name, cmd);
1514
1398
  }
1515
1399
  return matchedCommands;
1516
1400
  }
1517
- /** Import command modules that end with `.slash` */
1518
- async importFrom(dir, replaceAll) {
1519
- if (replaceAll) {
1520
- this.commands.clear();
1401
+ async registerGlobal(options) {
1402
+ const client = await this.client.whenReady();
1403
+ const commands = Array.from(
1404
+ this.getAll({
1405
+ names: options?.commands,
1406
+ fuzzyNames: options?.fuzzyCommands,
1407
+ globalOnly: true
1408
+ }).values()
1409
+ ).map((cmd) => cmd.builder.toJSON());
1410
+ if (!commands.length) {
1411
+ console.log(`[${this.typeName}] No commands to register`);
1412
+ return;
1521
1413
  }
1522
- dir = Array.isArray(dir) ? dir : [dir];
1523
- const commandModules = await Promise.all(
1524
- dir.map((dir2) => importModulesFromDir(dir2, "slash"))
1525
- );
1526
- let importedCommands = 0;
1527
- for (const command of commandModules.flat()) {
1528
- const isProperEnvironment = command.module.default.deployment?.environments?.includes(
1529
- this.client.config.app.devMode ? "development" : "production"
1530
- );
1531
- if (isProperEnvironment ?? true) {
1532
- this.commands.set(command.module.default.builder.name, command.module.default);
1533
- importedCommands++;
1534
- }
1414
+ console.log(`[${this.typeName}] Registering ${commands.length} commands globally...`);
1415
+ try {
1416
+ await this.rest.put(Routes.applicationCommands(client.user.id), { body: commands });
1417
+ console.log(`[${this.typeName}] \u2714 Registered app commands globally`);
1418
+ } catch (err) {
1419
+ console.log(`[${this.typeName}] \u2716 Failed to register app commands globally`, err);
1535
1420
  }
1536
- this.client.logger.moduleLoaded("Slash Commands", importedCommands);
1537
- return this.commands;
1538
1421
  }
1539
1422
  async registerGuild(options) {
1540
1423
  const client = await this.client.whenReady();
1541
- const commandsToRegister = Array.from(
1424
+ const commands = Array.from(
1542
1425
  this.getAll({
1543
1426
  names: options?.commands,
1544
1427
  fuzzyNames: options?.fuzzyCommands
1545
1428
  }).values()
1546
1429
  ).map((cmd) => cmd.builder.toJSON());
1547
- if (!commandsToRegister.length) {
1548
- console.log("[SlashCommandManager] No commands to register");
1430
+ if (!commands.length) {
1431
+ console.log(`[${this.typeName}] No commands to register`);
1549
1432
  return;
1550
1433
  }
1551
- const guildIds = options?.guilds || client.guilds.cache.map((guild) => guild.id);
1552
- console.log(
1553
- `[SlashCommandManager] Registering ${commandsToRegister.length} app (/) ${commandsToRegister.length === 1 ? "command" : "commands"} for ${guildIds.length} ${guildIds.length === 1 ? "guild" : "guilds"}...`
1554
- );
1434
+ const guildIds = options?.guilds || client.guilds.cache.map((g) => g.id);
1435
+ console.log(`[${this.typeName}] Registering ${commands.length} commands for ${guildIds.length} guilds...`);
1555
1436
  await Promise.all(
1556
1437
  guildIds.map(
1557
- (guildId) => this.rest.put(Routes.applicationGuildCommands(client.user.id, guildId), {
1558
- body: commandsToRegister
1559
- }).then(
1560
- () => console.log(
1561
- `[SlashCommandManager] \u2714 Set app (/) commands in guild: ${guildId} (${client.guilds.cache.find((g) => g.id === guildId)?.name || "n/a"})`
1562
- )
1563
- ).catch(
1564
- (err) => console.log(
1565
- `[SlashCommandManager] \u2716 Failed to set app (/) commands in guild: ${guildId} (${client.guilds.cache.find((g) => g.id === guildId)?.name || "n/a"})`,
1566
- err
1567
- )
1568
- )
1438
+ (guildId) => this.rest.put(Routes.applicationGuildCommands(client.user.id, guildId), { body: commands }).then(() => {
1439
+ const gName = client.guilds.cache.get(guildId)?.name || "n/a";
1440
+ console.log(`[${this.typeName}] \u2714 Set app commands in guild: ${guildId} (${gName})`);
1441
+ }).catch((err) => {
1442
+ const gName = client.guilds.cache.get(guildId)?.name || "n/a";
1443
+ console.log(`[${this.typeName}] \u2716 Failed to set app commands in guild: ${guildId} (${gName})`, err);
1444
+ })
1569
1445
  )
1570
1446
  );
1571
- console.log(
1572
- `[SlashCommandManager] \u2714 Finished registering app (/) commands for ${guildIds.length} ${guildIds.length === 1 ? "guild" : "guilds"}`
1573
- );
1574
1447
  }
1575
1448
  async unregisterGuild(options) {
1576
1449
  const client = await this.client.whenReady();
1577
- const guildIds = options?.guilds || client.guilds.cache.map((guild) => guild.id);
1578
- console.log(
1579
- `[SlashCommandManager] Unregistering app (/) commands from ${guildIds.length} ${guildIds.length === 1 ? "guild" : "guilds"}...`
1580
- );
1450
+ const guildIds = options?.guilds || client.guilds.cache.map((g) => g.id);
1451
+ console.log(`[${this.typeName}] Unregistering commands from ${guildIds.length} guilds...`);
1581
1452
  await Promise.all(
1582
1453
  guildIds.map(
1583
- (guildId) => this.rest.put(Routes.applicationGuildCommands(client.user.id, guildId), {
1584
- body: []
1585
- }).then(
1586
- () => console.log(
1587
- `[SlashCommandManager] \u2714 Removed app (/) commands in guild: ${guildId} (${client.guilds.cache.find((g) => g.id === guildId)?.name || "n/a"})`
1588
- )
1589
- ).catch(
1590
- (err) => console.log(
1591
- `[SlashCommandManager] \u2716 Failed to remove app (/) commands in guild: ${guildId} (${client.guilds.cache.find((g) => g.id === guildId)?.name || "n/a"})`,
1592
- err
1593
- )
1454
+ (guildId) => this.rest.put(Routes.applicationGuildCommands(client.user.id, guildId), { body: [] }).then(() => console.log(`[${this.typeName}] \u2714 Removed app commands in guild: ${guildId}`)).catch(
1455
+ (err) => console.log(`[${this.typeName}] \u2716 Failed to remove app commands in guild: ${guildId}`, err)
1594
1456
  )
1595
1457
  )
1596
1458
  );
1597
- console.log(
1598
- `[SlashCommandManager] \u2714 Finished unregistering app (/) commands for ${guildIds.length} ${guildIds.length === 1 ? "guild" : "guilds"}`
1599
- );
1600
- }
1601
- async registerGlobal(options) {
1602
- const client = await this.client.whenReady();
1603
- const commandsToRegister = Array.from(
1604
- this.getAll({
1605
- names: options?.commands,
1606
- fuzzyNames: options?.fuzzyCommands,
1607
- globalOnly: true
1608
- }).values()
1609
- ).map((cmd) => cmd.builder.toJSON());
1610
- if (!commandsToRegister.length) {
1611
- console.log("[SlashCommandManager] No commands to register");
1612
- return;
1613
- }
1614
- console.log(
1615
- `[SlashCommandManager] Registering ${commandsToRegister.length} app (/) ${commandsToRegister.length === 1 ? "command" : "commands"} globally...`
1616
- );
1617
- try {
1618
- await this.rest.put(Routes.applicationCommands(client.user.id), { body: commandsToRegister });
1619
- console.log("[SlashCommandManager] \u2714 Registered app (/) commands globally");
1620
- } catch (err) {
1621
- console.log("[SlashCommandManager] \u2716 Failed to register app (/) commands globally", err);
1622
- }
1623
1459
  }
1624
1460
  async unregisterGlobal() {
1625
1461
  const client = await this.client.whenReady();
1626
- console.log("[SlashCommandManager] Unregistering app (/) commands globally...");
1627
1462
  try {
1628
1463
  await this.rest.put(Routes.applicationCommands(client.user.id), { body: [] });
1629
- console.log("[SlashCommandManager] \u2714 Removed app (/) commands globally");
1464
+ console.log(`[${this.typeName}] \u2714 Removed app commands globally`);
1630
1465
  } catch (err) {
1631
- console.log("[SlashCommandManager] \u2716 Failed to remove app (/) commands globally", err);
1466
+ console.log(`[${this.typeName}] \u2716 Failed to remove app commands globally`, err);
1632
1467
  }
1633
1468
  }
1634
1469
  };
1635
- var VimcordPrefixCommandManager = class {
1470
+ var VimcordSlashCommandManager = class extends VimcordAppCommandManager {
1636
1471
  constructor(client) {
1637
- this.client = client;
1638
- }
1639
- commands = /* @__PURE__ */ new Map();
1640
- get(name) {
1641
- if (this.client.config.prefixCommands.allowCaseInsensitiveCommandNames) {
1642
- name = name.toLowerCase();
1643
- return Array.from(this.commands.values()).find((cmd) => cmd.name.toLowerCase() === name);
1644
- }
1645
- return this.commands.get(name);
1646
- }
1647
- getByAlias(alias) {
1648
- if (this.client.config.prefixCommands.allowCaseInsensitiveCommandNames) {
1649
- alias = alias.toLowerCase();
1650
- return Array.from(this.commands.values()).find((cmd) => cmd.aliases?.includes(alias));
1651
- }
1652
- return Array.from(this.commands.values()).find((cmd) => cmd.aliases?.includes(alias));
1472
+ super(client, "SlashCommandManager");
1473
+ }
1474
+ async importFrom(dir, replaceAll = false) {
1475
+ if (replaceAll) this.commands.clear();
1476
+ const dirs = Array.isArray(dir) ? dir : [dir];
1477
+ const modules = (await Promise.all(
1478
+ dirs.map((d) => importModulesFromDir(d, "slash"))
1479
+ )).flat();
1480
+ for (const { module } of modules) {
1481
+ this.commands.set(module.default.builder.name, module.default);
1482
+ }
1483
+ this.client.logger.moduleLoaded("Slash Commands", modules.length);
1484
+ return this.commands;
1653
1485
  }
1654
- /** Import command modules that end with `.prefix` */
1655
- async importFrom(dir, replaceAll) {
1656
- if (replaceAll) {
1657
- this.commands.clear();
1658
- }
1659
- dir = Array.isArray(dir) ? dir : [dir];
1660
- const commandModules = await Promise.all(
1661
- dir.map((dir2) => importModulesFromDir(dir2, "prefix"))
1662
- );
1663
- let importedCommands = 0;
1664
- for (const command of commandModules.flat()) {
1665
- this.commands.set(command.module.default.name, command.module.default);
1666
- importedCommands++;
1667
- }
1668
- this.client.logger.moduleLoaded("Prefix Commands", importedCommands);
1486
+ };
1487
+ var VimcordContextCommandManager = class extends VimcordAppCommandManager {
1488
+ constructor(client) {
1489
+ super(client, "ContextCommandManager");
1490
+ }
1491
+ async importFrom(dir, replaceAll = false) {
1492
+ if (replaceAll) this.commands.clear();
1493
+ const dirs = Array.isArray(dir) ? dir : [dir];
1494
+ const modules = (await Promise.all(
1495
+ dirs.map((d) => importModulesFromDir(d, "ctx"))
1496
+ )).flat();
1497
+ for (const { module } of modules) {
1498
+ this.commands.set(module.default.builder.name, module.default);
1499
+ }
1500
+ this.client.logger.moduleLoaded("Context Commands", modules.length);
1669
1501
  return this.commands;
1670
1502
  }
1671
1503
  };
1672
- var VimcordContextCommandManager = class {
1504
+ var VimcordPrefixCommandManager = class {
1673
1505
  constructor(client) {
1674
1506
  this.client = client;
1675
1507
  }
1676
1508
  commands = /* @__PURE__ */ new Map();
1677
- /** Import command modules that end with `.ctx` */
1678
- async importFrom(dir, replaceAll) {
1679
- if (replaceAll) {
1680
- this.commands.clear();
1681
- }
1682
- dir = Array.isArray(dir) ? dir : [dir];
1683
- const commandModules = await Promise.all(
1684
- dir.map((dir2) => importModulesFromDir(dir2, "ctx"))
1685
- );
1686
- let importedCommands = 0;
1687
- for (const command of commandModules.flat()) {
1688
- this.commands.set(command.module.default.builder.name, command.module.default);
1689
- importedCommands++;
1509
+ resolve(trigger) {
1510
+ const config = this.client.config.prefixCommands;
1511
+ const search = config.allowCaseInsensitiveCommandNames ? trigger.toLowerCase() : trigger;
1512
+ return Array.from(this.commands.values()).find((cmd) => {
1513
+ const opts = cmd.toConfig();
1514
+ const name = config.allowCaseInsensitiveCommandNames ? opts.name.toLowerCase() : opts.name;
1515
+ if (name === search) return true;
1516
+ return opts.aliases?.some(
1517
+ (a) => config.allowCaseInsensitiveCommandNames ? a.toLowerCase() === search : a === search
1518
+ );
1519
+ });
1520
+ }
1521
+ async importFrom(dir, replaceAll = false) {
1522
+ if (replaceAll) this.commands.clear();
1523
+ const dirs = Array.isArray(dir) ? dir : [dir];
1524
+ const modules = (await Promise.all(
1525
+ dirs.map(
1526
+ (d) => importModulesFromDir(d, "prefix")
1527
+ )
1528
+ )).flat();
1529
+ for (const { module } of modules) {
1530
+ this.commands.set(module.default.toConfig().name, module.default);
1690
1531
  }
1691
- this.client.logger.moduleLoaded("Context Commands", importedCommands);
1532
+ this.client.logger.moduleLoaded("Prefix Commands", modules.length);
1692
1533
  return this.commands;
1693
1534
  }
1694
1535
  };
1536
+ var VimcordCommandManager = class {
1537
+ slash;
1538
+ prefix;
1539
+ context;
1540
+ constructor(client) {
1541
+ this.slash = new VimcordSlashCommandManager(client);
1542
+ this.prefix = new VimcordPrefixCommandManager(client);
1543
+ this.context = new VimcordContextCommandManager(client);
1544
+ }
1545
+ };
1695
1546
 
1696
1547
  // src/modules/event.manager.ts
1697
1548
  import { Events } from "discord.js";
@@ -2087,7 +1938,7 @@ var BetterEmbed = class _BetterEmbed {
2087
1938
  }
2088
1939
  if (this.data.description) {
2089
1940
  this.data.description = formatString(
2090
- Array.isArray(this.data.description) ? this.data.description.join("\n") : this.data.description
1941
+ Array.isArray(this.data.description) ? this.data.description.filter((s) => s !== null && s !== void 0).join("\n") : this.data.description
2091
1942
  );
2092
1943
  }
2093
1944
  if (this.data.footer && typeof this.data.footer === "object") {
@@ -2099,7 +1950,7 @@ var BetterEmbed = class _BetterEmbed {
2099
1950
  if (this.data.imageUrl) {
2100
1951
  this.data.imageUrl = formatString(this.data.imageUrl);
2101
1952
  }
2102
- this.data.fields = this.data.fields.map((field) => ({
1953
+ this.data.fields = this.data.fields.filter(Boolean).map((field) => ({
2103
1954
  ...field,
2104
1955
  name: formatString(field.name),
2105
1956
  value: formatString(field.value)
@@ -2324,7 +2175,7 @@ async function retryExponentialBackoff(fn, maxRetries = 3, retryDelay = 1e3) {
2324
2175
  }
2325
2176
 
2326
2177
  // package.json
2327
- var version = "1.0.0";
2178
+ var version = "1.0.1";
2328
2179
 
2329
2180
  // src/client.ts
2330
2181
  import { randomUUID as randomUUID3 } from "crypto";
@@ -2590,62 +2441,63 @@ var Vimcord = class _Vimcord extends Client2 {
2590
2441
  return fetchGuild(client, id);
2591
2442
  }
2592
2443
  };
2593
- var defaultSlashCommandHandler = new EventBuilder({
2594
- event: "interactionCreate",
2595
- name: "SlashCommandHandler",
2596
- async execute(client, interaction) {
2597
- if (!interaction.isChatInputCommand()) return;
2598
- const command = client.commands.slash.get(interaction.commandName);
2599
- if (!command) {
2600
- return interaction.reply({
2601
- content: `**/\`${interaction.commandName}\`** is not a command`,
2602
- flags: "Ephemeral"
2603
- });
2604
- }
2605
- if (command.deferReply && !interaction.replied && !interaction.deferred) {
2606
- await interaction.deferReply(typeof command.deferReply === "object" ? command.deferReply : void 0);
2607
- }
2608
- try {
2609
- return command.executeCommand(client, interaction);
2610
- } catch (err) {
2611
- sendCommandErrorEmbed(client, err, interaction.guild, interaction);
2612
- throw err;
2613
- }
2614
- }
2615
- });
2616
2444
  var defaultPrefixCommandHandler = new EventBuilder({
2617
2445
  event: "messageCreate",
2618
2446
  name: "PrefixCommandHandler",
2619
2447
  async execute(client, message) {
2620
- if (message.author.bot) return;
2448
+ if (message.author.bot || !message.guild) return;
2449
+ const config = client.config.prefixCommands;
2450
+ let activePrefix = config.defaultPrefix;
2451
+ if (config.guildPrefixResolver) {
2452
+ try {
2453
+ const customPrefix = await config.guildPrefixResolver(client, message.guild.id);
2454
+ if (customPrefix) activePrefix = customPrefix;
2455
+ } catch (err) {
2456
+ client.logger.error(`Error in guildPrefixResolver for guild ${message.guild.id}:`, err);
2457
+ }
2458
+ }
2621
2459
  let prefixUsed;
2622
- if (!message.content.startsWith(client.config.prefixCommands.defaultPrefix)) {
2623
- if (client.config.prefixCommands.allowMentionAsPrefix) {
2624
- if (!message.content.startsWith(`${userMention(client.user.id)} `)) {
2625
- return;
2626
- } else {
2627
- prefixUsed = `${userMention(client.user.id)} `;
2628
- }
2629
- } else {
2630
- return;
2460
+ if (message.content.startsWith(activePrefix)) {
2461
+ prefixUsed = activePrefix;
2462
+ } else if (config.allowMentionAsPrefix) {
2463
+ const mention = userMention(client.user.id);
2464
+ if (message.content.startsWith(mention)) {
2465
+ prefixUsed = message.content.startsWith(`${mention} `) ? `${mention} ` : mention;
2631
2466
  }
2632
- } else {
2633
- prefixUsed = client.config.prefixCommands.defaultPrefix;
2634
2467
  }
2635
- message.content = message.content.slice(prefixUsed.length);
2636
- let commandName = message.content.split(" ")[0];
2637
- if (!commandName) {
2638
- return;
2468
+ if (!prefixUsed) return;
2469
+ const contentWithoutPrefix = message.content.slice(prefixUsed.length).trim();
2470
+ const args = contentWithoutPrefix.split(/\s+/);
2471
+ const trigger = args.shift();
2472
+ if (!trigger) return;
2473
+ const command = client.commands.prefix.resolve(trigger);
2474
+ if (!command) return;
2475
+ message.content = args.join(" ");
2476
+ try {
2477
+ return await command.run(client, client, message);
2478
+ } catch (err) {
2479
+ await sendCommandErrorEmbed(client, err, message.guild, message);
2480
+ throw err;
2639
2481
  }
2640
- const command = client.commands.prefix.get(commandName) || client.commands.prefix.getByAlias(commandName);
2482
+ }
2483
+ });
2484
+ var defaultSlashCommandHandler = new EventBuilder({
2485
+ event: "interactionCreate",
2486
+ name: "SlashCommandHandler",
2487
+ async execute(client, interaction) {
2488
+ if (!interaction.isChatInputCommand()) return;
2489
+ const command = client.commands.slash.get(interaction.commandName);
2641
2490
  if (!command) {
2642
- return;
2491
+ const content = `**/\`${interaction.commandName}\`** is not a registered command.`;
2492
+ if (interaction.replied || interaction.deferred) {
2493
+ return interaction.followUp({ content, flags: "Ephemeral" });
2494
+ }
2495
+ return interaction.reply({ content, flags: "Ephemeral" });
2643
2496
  }
2644
- message.content = message.content.slice(commandName.length + 1);
2645
2497
  try {
2646
- return command.executeCommand(client, message);
2498
+ return await command.run(client, client, interaction);
2647
2499
  } catch (err) {
2648
- sendCommandErrorEmbed(client, err, message.guild, message);
2500
+ await sendCommandErrorEmbed(client, err, interaction.guild, interaction);
2649
2501
  throw err;
2650
2502
  }
2651
2503
  }
@@ -2653,9 +2505,22 @@ var defaultPrefixCommandHandler = new EventBuilder({
2653
2505
  var defaultContextCommandHandler = new EventBuilder({
2654
2506
  event: "interactionCreate",
2655
2507
  name: "ContextCommandHandler",
2656
- execute(client, interaction) {
2508
+ async execute(client, interaction) {
2657
2509
  if (!interaction.isContextMenuCommand()) return;
2658
- interaction.reply({ content: "This handler is not yet implemented" });
2510
+ const command = client.commands.context.get(interaction.commandName);
2511
+ if (!command) {
2512
+ const content = `**${interaction.commandName}** is not a registered context command.`;
2513
+ if (interaction.replied || interaction.deferred) {
2514
+ return interaction.followUp({ content, flags: "Ephemeral" });
2515
+ }
2516
+ return interaction.reply({ content, flags: "Ephemeral" });
2517
+ }
2518
+ try {
2519
+ return await command.run(client, client, interaction);
2520
+ } catch (err) {
2521
+ await sendCommandErrorEmbed(client, err, interaction.guild, interaction);
2522
+ throw err;
2523
+ }
2659
2524
  }
2660
2525
  });
2661
2526
 
@@ -2670,8 +2535,8 @@ try {
2670
2535
  }
2671
2536
  var globalInstanceEmitter = new EventEmitter2();
2672
2537
  var instances = [];
2673
- async function useMongoDatabase(index) {
2674
- const instance = instances.at(index ?? 0);
2538
+ async function useMongoDatabase(instanceIndex) {
2539
+ const instance = instances.at(instanceIndex ?? 0);
2675
2540
  if (!instance) {
2676
2541
  return new Promise((resolve, reject) => {
2677
2542
  const timeout = setTimeout(() => reject("useMongoDatabase timed out"), 45e3);
@@ -2683,6 +2548,15 @@ async function useMongoDatabase(index) {
2683
2548
  }
2684
2549
  return instance;
2685
2550
  }
2551
+ async function useReadyMongoDatabase(instanceIndex) {
2552
+ const instance = await useMongoDatabase(instanceIndex);
2553
+ await instance.waitForReady();
2554
+ return instance;
2555
+ }
2556
+ async function createMongoSession(instanceIndex, options) {
2557
+ const instance = await useReadyMongoDatabase(instanceIndex);
2558
+ return instance.mongoose.startSession(options);
2559
+ }
2686
2560
  var MongoDatabase = class {
2687
2561
  constructor(client, options) {
2688
2562
  this.client = client;
@@ -2839,6 +2713,7 @@ var MongoSchemaBuilder = class {
2839
2713
  this.eventEmitter.off(event, listener);
2840
2714
  return this;
2841
2715
  }
2716
+ /** Execute a function while ensuring the connection is ready. On error it will retry using an exponential backoff. */
2842
2717
  async execute(fn) {
2843
2718
  try {
2844
2719
  if (!this.isReady) {
@@ -2875,9 +2750,9 @@ var MongoSchemaBuilder = class {
2875
2750
  return id;
2876
2751
  });
2877
2752
  }
2878
- async count(filter) {
2753
+ async count(filter, options) {
2879
2754
  return await this.execute(async () => {
2880
- return this.model.countDocuments(filter);
2755
+ return this.model.countDocuments(filter, options);
2881
2756
  });
2882
2757
  }
2883
2758
  async exists(filter) {
@@ -2885,29 +2760,29 @@ var MongoSchemaBuilder = class {
2885
2760
  return await this.model.exists(filter) ? true : false;
2886
2761
  });
2887
2762
  }
2888
- async create(query) {
2763
+ async create(query, options) {
2889
2764
  return await this.execute(async () => {
2890
- return this.model.create(query);
2891
- });
2765
+ return this.model.create(query, options);
2766
+ }) ?? [];
2892
2767
  }
2893
2768
  async upsert(filter, query, options) {
2894
2769
  return await this.execute(async () => {
2895
2770
  return this.model.findOneAndUpdate(filter, query, { ...options, upsert: true, new: true });
2896
2771
  });
2897
2772
  }
2898
- async delete(filter) {
2773
+ async delete(filter, options) {
2899
2774
  return await this.execute(async () => {
2900
- return this.model.deleteOne(filter);
2775
+ return this.model.deleteOne(filter, options);
2901
2776
  });
2902
2777
  }
2903
- async deleteAll(filter) {
2778
+ async deleteAll(filter, options) {
2904
2779
  return await this.execute(async () => {
2905
- return this.model.deleteMany(filter);
2780
+ return this.model.deleteMany(filter, options);
2906
2781
  });
2907
2782
  }
2908
- async distinct(key, filter) {
2783
+ async distinct(key, filter, options) {
2909
2784
  return await this.execute(async () => {
2910
- return this.model.distinct(key, filter);
2785
+ return this.model.distinct(key, filter, options);
2911
2786
  });
2912
2787
  }
2913
2788
  async fetch(filter, projection, options) {
@@ -2938,6 +2813,215 @@ var MongoSchemaBuilder = class {
2938
2813
  }
2939
2814
  };
2940
2815
 
2816
+ // src/tools/BetterCollector.ts
2817
+ var CollectorTimeoutType = /* @__PURE__ */ ((CollectorTimeoutType2) => {
2818
+ CollectorTimeoutType2[CollectorTimeoutType2["DisableComponents"] = 0] = "DisableComponents";
2819
+ CollectorTimeoutType2[CollectorTimeoutType2["DeleteMessage"] = 1] = "DeleteMessage";
2820
+ CollectorTimeoutType2[CollectorTimeoutType2["DoNothing"] = 2] = "DoNothing";
2821
+ return CollectorTimeoutType2;
2822
+ })(CollectorTimeoutType || {});
2823
+ var BetterCollector = class _BetterCollector {
2824
+ message;
2825
+ collector;
2826
+ options;
2827
+ activeUsers = /* @__PURE__ */ new Set();
2828
+ participantWarningCooldowns = /* @__PURE__ */ new Map();
2829
+ config;
2830
+ events = {
2831
+ collectId: /* @__PURE__ */ new Map(),
2832
+ collect: [],
2833
+ end: [],
2834
+ timeout: []
2835
+ };
2836
+ static create(message, options) {
2837
+ return new _BetterCollector(message, options);
2838
+ }
2839
+ async validateParticipant(interaction, participants) {
2840
+ const allowedParticipants = participants || this.options?.participants || [];
2841
+ if (!allowedParticipants.length) return true;
2842
+ const isAllowed = allowedParticipants.some((user) => {
2843
+ if (typeof user === "string") return user === interaction.user.id;
2844
+ if (typeof user === "object" && "id" in user) return user.id === interaction.user.id;
2845
+ return false;
2846
+ });
2847
+ if (!isAllowed) {
2848
+ const now = Date.now();
2849
+ const lastWarned = this.participantWarningCooldowns.get(interaction.user.id) || 0;
2850
+ if (now - lastWarned > this.config.collector.notAParticipantWarningCooldown) {
2851
+ this.participantWarningCooldowns.set(interaction.user.id, now);
2852
+ if (this.config.collector.notAParticipantMessage) {
2853
+ await interaction.reply({
2854
+ content: this.config.collector.notAParticipantMessage,
2855
+ flags: "Ephemeral"
2856
+ }).catch(() => {
2857
+ });
2858
+ } else {
2859
+ await interaction.deferUpdate().catch(() => {
2860
+ });
2861
+ }
2862
+ } else {
2863
+ await interaction.deferUpdate().catch(() => {
2864
+ });
2865
+ }
2866
+ }
2867
+ return isAllowed;
2868
+ }
2869
+ build() {
2870
+ if (!this.message) return;
2871
+ if (this.collector) return;
2872
+ this.collector = this.message.createMessageComponentCollector({
2873
+ idle: this.options?.idle ?? this.config.timeouts.collectorIdle,
2874
+ time: this.options?.timeout ?? this.config.timeouts.collectorTimeout,
2875
+ componentType: this.options?.type,
2876
+ max: this.options?.max,
2877
+ maxComponents: this.options?.maxComponents,
2878
+ maxUsers: this.options?.maxUsers
2879
+ });
2880
+ this.setupListeners();
2881
+ }
2882
+ setupListeners() {
2883
+ if (!this.collector) return;
2884
+ this.collector.on("collect", async (interaction) => {
2885
+ if (this.options?.userLock && this.activeUsers.has(interaction.user.id)) {
2886
+ return interaction.reply({ content: this.config.collector.userLockMessage, flags: "Ephemeral" }).catch(() => {
2887
+ });
2888
+ }
2889
+ if (this.options?.userLock) {
2890
+ this.activeUsers.add(interaction.user.id);
2891
+ }
2892
+ const globalListeners = this.events.collect;
2893
+ const idListeners = this.events.collectId.get(interaction.customId) || [];
2894
+ const allListeners = [...globalListeners, ...idListeners];
2895
+ const shouldBeDeferred = allListeners.find((l) => l.options?.defer)?.options?.defer;
2896
+ const validListeners = [];
2897
+ for (const listener of allListeners) {
2898
+ const isAllowed = await this.validateParticipant(interaction, listener.options?.participants);
2899
+ if (isAllowed) validListeners.push(listener);
2900
+ }
2901
+ if (validListeners.length === 0) return;
2902
+ try {
2903
+ if (shouldBeDeferred) {
2904
+ if (typeof shouldBeDeferred === "object") {
2905
+ if (shouldBeDeferred.update) {
2906
+ await interaction.deferUpdate().catch(Boolean);
2907
+ } else {
2908
+ await interaction.deferReply({ flags: shouldBeDeferred.ephemeral ? "Ephemeral" : void 0 }).catch(Boolean);
2909
+ }
2910
+ } else {
2911
+ await interaction.deferReply().catch(Boolean);
2912
+ }
2913
+ }
2914
+ if (this.options?.sequential) {
2915
+ for (const listener of allListeners) {
2916
+ try {
2917
+ const isAllowed = await this.validateParticipant(interaction, listener.options?.participants);
2918
+ if (!isAllowed) return;
2919
+ await listener.fn(interaction).finally(() => listener.options?.finally?.(interaction));
2920
+ } catch (err) {
2921
+ this.handleListenerError(err);
2922
+ }
2923
+ }
2924
+ } else {
2925
+ Promise.all(
2926
+ allListeners.map((l) => {
2927
+ const isAllowed = this.validateParticipant(interaction, l.options?.participants);
2928
+ if (!isAllowed) return;
2929
+ return l.fn(interaction).catch(this.handleListenerError).finally(() => l.options?.finally?.(interaction));
2930
+ })
2931
+ );
2932
+ }
2933
+ } finally {
2934
+ if (this.options?.userLock) {
2935
+ this.activeUsers.delete(interaction.user.id);
2936
+ }
2937
+ }
2938
+ });
2939
+ this.collector.on("end", async (collected, reason) => {
2940
+ if (this.options?.sequential) {
2941
+ for (const listener of this.events.end) {
2942
+ try {
2943
+ await listener.fn(collected, reason);
2944
+ } catch (err) {
2945
+ this.handleListenerError(err);
2946
+ }
2947
+ }
2948
+ } else {
2949
+ Promise.all(this.events.end.map((l) => l.fn(collected, reason).catch(this.handleListenerError)));
2950
+ }
2951
+ switch (this.options?.onTimeout) {
2952
+ case 0 /* DisableComponents */:
2953
+ if (!this.message?.editable) break;
2954
+ try {
2955
+ const disabledRows = this.message.components.map((row) => {
2956
+ const updatedRow = row.toJSON();
2957
+ if ("components" in updatedRow) {
2958
+ updatedRow.components = updatedRow.components.map((component) => ({
2959
+ ...component,
2960
+ disabled: true
2961
+ }));
2962
+ }
2963
+ return updatedRow;
2964
+ });
2965
+ await this.message.edit({ components: disabledRows });
2966
+ } catch (err) {
2967
+ if (!(err instanceof Error && err.message.includes("Unknown Message"))) {
2968
+ this.handleListenerError(err);
2969
+ }
2970
+ }
2971
+ break;
2972
+ case 1 /* DeleteMessage */:
2973
+ if (!this.message?.deletable) break;
2974
+ await this.message.delete().catch(Boolean);
2975
+ break;
2976
+ case 2 /* DoNothing */:
2977
+ default:
2978
+ break;
2979
+ }
2980
+ });
2981
+ }
2982
+ handleListenerError(err) {
2983
+ console.error("[BetterCollector] Listener Error:", err);
2984
+ }
2985
+ constructor(message, options) {
2986
+ this.config = options?.config || globalVimcordToolsConfig;
2987
+ this.message = message || void 0;
2988
+ this.options = options;
2989
+ this.build();
2990
+ }
2991
+ on(idOrFunc, fnOrOptions, options) {
2992
+ let finalFn;
2993
+ let finalOptions;
2994
+ let customId;
2995
+ if (typeof idOrFunc === "function") {
2996
+ finalFn = idOrFunc;
2997
+ finalOptions = fnOrOptions;
2998
+ } else {
2999
+ if (typeof fnOrOptions !== "function") {
3000
+ throw new Error("[BetterCollector] Second argument must be a function when a customId is provided.");
3001
+ }
3002
+ customId = idOrFunc;
3003
+ finalFn = fnOrOptions;
3004
+ finalOptions = options;
3005
+ }
3006
+ if (customId) {
3007
+ const listeners = this.events.collectId.get(customId) || [];
3008
+ listeners.push({ fn: finalFn, options: finalOptions });
3009
+ this.events.collectId.set(customId, listeners);
3010
+ } else {
3011
+ this.events.collect.push({ fn: finalFn, options: finalOptions });
3012
+ }
3013
+ return this;
3014
+ }
3015
+ onEnd(fn, options) {
3016
+ this.events.end.push({ fn, options });
3017
+ return this;
3018
+ }
3019
+ /** Manually stop the collector and trigger cleanup */
3020
+ stop(reason = "manual") {
3021
+ this.collector?.stop(reason);
3022
+ }
3023
+ };
3024
+
2941
3025
  // src/tools/BetterContainer.ts
2942
3026
  import {
2943
3027
  ButtonBuilder as ButtonBuilder2,
@@ -2982,7 +3066,9 @@ var BetterContainer = class {
2982
3066
  return this;
2983
3067
  }
2984
3068
  addText(text) {
2985
- this.container.addTextDisplayComponents((tdb) => tdb.setContent(Array.isArray(text) ? text.join("\n") : text));
3069
+ this.container.addTextDisplayComponents(
3070
+ (tdb) => tdb.setContent(Array.isArray(text) ? text.filter((t) => t !== null && t !== void 0).join("\n") : text)
3071
+ );
2986
3072
  return this;
2987
3073
  }
2988
3074
  addMedia(...media) {
@@ -3006,7 +3092,9 @@ var BetterContainer = class {
3006
3092
  this.container.addSectionComponents((sb) => {
3007
3093
  if (data.text) {
3008
3094
  sb.addTextDisplayComponents(
3009
- (tdb) => tdb.setContent(Array.isArray(data.text) ? data.text.join("\n") : data.text)
3095
+ (tdb) => tdb.setContent(
3096
+ Array.isArray(data.text) ? data.text.filter((t) => t !== null && t !== void 0).join("\n") : data.text
3097
+ )
3010
3098
  );
3011
3099
  }
3012
3100
  if (data.thumbnail) sb.setThumbnailAccessory(new ThumbnailBuilder(data.thumbnail));
@@ -3237,6 +3325,7 @@ var BetterModal = class {
3237
3325
  // src/tools/Paginator.ts
3238
3326
  import {
3239
3327
  ActionRowBuilder as ActionRowBuilder4,
3328
+ AttachmentBuilder as AttachmentBuilder2,
3240
3329
  ButtonBuilder as ButtonBuilder3,
3241
3330
  ButtonStyle as ButtonStyle2,
3242
3331
  ComponentType as ComponentType2,
@@ -3256,6 +3345,7 @@ var PaginationTimeoutType = /* @__PURE__ */ ((PaginationTimeoutType2) => {
3256
3345
  PaginationTimeoutType2[PaginationTimeoutType2["DisableComponents"] = 0] = "DisableComponents";
3257
3346
  PaginationTimeoutType2[PaginationTimeoutType2["ClearComponents"] = 1] = "ClearComponents";
3258
3347
  PaginationTimeoutType2[PaginationTimeoutType2["DeleteMessage"] = 2] = "DeleteMessage";
3348
+ PaginationTimeoutType2[PaginationTimeoutType2["DoNothing"] = 3] = "DoNothing";
3259
3349
  return PaginationTimeoutType2;
3260
3350
  })(PaginationTimeoutType || {});
3261
3351
  function wrapPositive(num, max) {
@@ -3336,7 +3426,8 @@ var Paginator = class {
3336
3426
  last: [],
3337
3427
  collect: [],
3338
3428
  react: [],
3339
- timeout: []
3429
+ preTimeout: [],
3430
+ postTimeout: []
3340
3431
  };
3341
3432
  if (this.options.pages.length) {
3342
3433
  this.addChapter(this.options.pages, { label: "Default" });
@@ -3403,16 +3494,23 @@ var Paginator = class {
3403
3494
  embeds: [],
3404
3495
  components: [],
3405
3496
  flags: [],
3497
+ files: [],
3498
+ // Explicitly empty to clear previous files on page switch
3406
3499
  ...options,
3407
3500
  withResponse: true
3408
3501
  };
3409
3502
  const page = this.data.page.current;
3503
+ const currentChapter = this.chapters[this.data.page.index.chapter];
3504
+ const chapterFile = currentChapter?.files?.[this.data.page.index.nested];
3505
+ if (chapterFile) sendOptions.files.push(chapterFile);
3410
3506
  if (Array.isArray(page)) {
3411
3507
  sendOptions.embeds.push(...page);
3412
3508
  } else if (typeof page === "string") {
3413
3509
  sendOptions.content = page;
3414
3510
  } else if (isEmbed(page)) {
3415
3511
  sendOptions.embeds.push(page);
3512
+ } else if (page instanceof AttachmentBuilder2) {
3513
+ sendOptions.files.push(page);
3416
3514
  } else if (page instanceof ContainerBuilder3 || page instanceof BetterContainer) {
3417
3515
  sendOptions.components.push(page);
3418
3516
  if (!sendOptions.flags.includes("IsComponentsV2")) {
@@ -3424,7 +3522,7 @@ var Paginator = class {
3424
3522
  }
3425
3523
  async handlePostTimeout() {
3426
3524
  if (!this.data.message) return;
3427
- this.callEventStack("timeout", this.data.message);
3525
+ this.callEventStack("preTimeout", this.data.message);
3428
3526
  this.data.collectors.component?.stop();
3429
3527
  this.data.collectors.reaction?.stop();
3430
3528
  switch (this.options.onTimeout) {
@@ -3468,7 +3566,10 @@ var Paginator = class {
3468
3566
  case 2 /* DeleteMessage */:
3469
3567
  await this.data.message.delete().catch(Boolean);
3470
3568
  break;
3569
+ case 3 /* DoNothing */:
3570
+ break;
3471
3571
  }
3572
+ this.callEventStack("postTimeout", this.data.message);
3472
3573
  }
3473
3574
  async nav_removeFromMessage() {
3474
3575
  if (!this.data.message?.editable) return;
@@ -3515,35 +3616,43 @@ var Paginator = class {
3515
3616
  await i.reply({ content: "Jump not implemented yet.", flags: "Ephemeral" });
3516
3617
  return;
3517
3618
  }
3518
- await i.deferUpdate().catch(Boolean);
3519
3619
  switch (i.customId) {
3520
3620
  case "ssm_chapterSelect":
3621
+ await i.deferUpdate().catch(Boolean);
3521
3622
  const chapterIndex = this.chapters.findIndex(
3522
3623
  (c) => c.id === i.values[0]
3523
3624
  );
3524
3625
  await this.setPage(chapterIndex, 0);
3626
+ await this.refresh();
3525
3627
  break;
3526
3628
  case "btn_first":
3629
+ await i.deferUpdate().catch(Boolean);
3527
3630
  this.callEventStack("first", this.data.page.current, this.data.page.index);
3528
3631
  await this.setPage(this.data.page.index.chapter, 0);
3632
+ await this.refresh();
3529
3633
  break;
3530
3634
  case "btn_back":
3635
+ await i.deferUpdate().catch(Boolean);
3531
3636
  this.callEventStack("back", this.data.page.current, this.data.page.index);
3532
3637
  await this.setPage(this.data.page.index.chapter, this.data.page.index.nested - 1);
3638
+ await this.refresh();
3533
3639
  break;
3534
3640
  case "btn_next":
3641
+ await i.deferUpdate().catch(Boolean);
3535
3642
  this.callEventStack("next", this.data.page.current, this.data.page.index);
3536
3643
  await this.setPage(this.data.page.index.chapter, this.data.page.index.nested + 1);
3644
+ await this.refresh();
3537
3645
  break;
3538
3646
  case "btn_last":
3647
+ await i.deferUpdate().catch(Boolean);
3539
3648
  this.callEventStack("last", this.data.page.current, this.data.page.index);
3540
3649
  await this.setPage(
3541
3650
  this.data.page.index.chapter,
3542
3651
  this.chapters[this.data.page.index.chapter].pages.length - 1
3543
3652
  );
3653
+ await this.refresh();
3544
3654
  break;
3545
3655
  }
3546
- await this.refresh();
3547
3656
  } catch (err) {
3548
3657
  console.error("[Paginator] Component navigation error", err);
3549
3658
  }
@@ -3583,7 +3692,7 @@ var Paginator = class {
3583
3692
  data.value = `ssm_c:${this.chapters.length}`;
3584
3693
  }
3585
3694
  const normalizedPages = resolvePages(pages);
3586
- this.chapters.push({ id: data.value, pages: normalizedPages });
3695
+ this.chapters.push({ id: data.value, pages: normalizedPages, files: data.files });
3587
3696
  this.data.components.chapterSelect.addOptions(data);
3588
3697
  return this;
3589
3698
  }
@@ -3696,6 +3805,7 @@ var Prompt = class {
3696
3805
  content;
3697
3806
  embed;
3698
3807
  container;
3808
+ textOnly;
3699
3809
  buttons;
3700
3810
  customButtons;
3701
3811
  onResolve;
@@ -3709,6 +3819,7 @@ var Prompt = class {
3709
3819
  this.content = options.content;
3710
3820
  this.embed = options.embed ?? this.createDefaultForm();
3711
3821
  this.container = options?.container;
3822
+ this.textOnly = options.textOnly;
3712
3823
  this.buttons = this.createButtons(options.buttons);
3713
3824
  this.customButtons = this.createCustomButtons(options.customButtons);
3714
3825
  this.onResolve = options.onResolve ?? [3 /* DeleteOnConfirm */, 4 /* DeleteOnReject */];
@@ -3788,7 +3899,7 @@ var Prompt = class {
3788
3899
  }
3789
3900
  buildSendOptions(options) {
3790
3901
  const sendData = { ...options };
3791
- if (this.container) {
3902
+ if (!this.textOnly && this.container) {
3792
3903
  sendData.components = Array.isArray(sendData.components) ? [...sendData.components, this.container] : [this.container];
3793
3904
  const existingFlags = sendData.flags ? Array.isArray(sendData.flags) ? sendData.flags : [sendData.flags] : [];
3794
3905
  if (!existingFlags.includes("IsComponentsV2")) {
@@ -3797,7 +3908,9 @@ var Prompt = class {
3797
3908
  sendData.flags = existingFlags;
3798
3909
  }
3799
3910
  } else {
3800
- sendData.embeds = Array.isArray(sendData.embeds) ? [this.embed, ...sendData.embeds] : [this.embed];
3911
+ if (!this.textOnly) {
3912
+ sendData.embeds = Array.isArray(sendData.embeds) ? [this.embed, ...sendData.embeds] : [this.embed];
3913
+ }
3801
3914
  }
3802
3915
  if (this.content) {
3803
3916
  sendData.content = this.content;
@@ -3883,7 +3996,7 @@ async function prompt(handler, options, sendOptions) {
3883
3996
 
3884
3997
  // src/utils/VimcordCLI.ts
3885
3998
  import { createInterface } from "readline";
3886
- import jsTools2 from "jstools";
3999
+ import { $ as $3 } from "qznt";
3887
4000
  var VimcordCLI = class {
3888
4001
  rl;
3889
4002
  options;
@@ -3922,7 +4035,7 @@ var VimcordCLI = class {
3922
4035
  return { isCommand: true, commandName, content: args.join(" "), args };
3923
4036
  }
3924
4037
  getClientInstance(line) {
3925
- const clientIndex = jsTools2.getFlagSubstring(line, "--client", 1) || jsTools2.getFlagSubstring(line, "-c", 1);
4038
+ const clientIndex = $3.str.getFlag(line, "--client", 1) || $3.str.getFlag(line, "-c", 1);
3926
4039
  if (clientIndex) {
3927
4040
  const idx = Number(clientIndex);
3928
4041
  if (isNaN(idx)) {
@@ -3965,21 +4078,25 @@ CLI.addCommand("help", "View information about a command, or the available CLI o
3965
4078
  }
3966
4079
  CLI.logger.table("(help)", helpList);
3967
4080
  });
3968
- CLI.addCommand("register", "Register app commands globally, or per guild", async (args, content) => {
4081
+ CLI.addCommand("register", "Register app commands (slash & context) globally, or per guild", async (args, content) => {
3969
4082
  const client = CLI.getClientInstance(content);
3970
4083
  if (!client) return;
3971
4084
  const mode = args[0]?.toLowerCase() || "";
3972
4085
  if (!["guild", "global"].includes(mode)) {
3973
4086
  return CLI.logger.error(`'${mode}' is not a valid option. Your options are [guild|global]`);
3974
4087
  }
3975
- let guildIds = (jsTools2.getFlagSubstring(content, "--guilds", 1) || jsTools2.getFlagSubstring(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
4088
+ let guildIds = ($3.str.getFlag(content, "--guilds", 1) || $3.str.getFlag(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
3976
4089
  if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
3977
4090
  switch (mode) {
3978
4091
  case "guild":
4092
+ CLI.logger.info("Registering guild commands...");
3979
4093
  await client.commands.slash.registerGuild({ guilds: guildIds });
4094
+ await client.commands.context.registerGuild({ guilds: guildIds });
3980
4095
  break;
3981
4096
  case "global":
4097
+ CLI.logger.info("Registering global commands...");
3982
4098
  await client.commands.slash.registerGlobal();
4099
+ await client.commands.context.registerGlobal();
3983
4100
  break;
3984
4101
  }
3985
4102
  });
@@ -3990,14 +4107,18 @@ CLI.addCommand("unregister", "Unregister app commands globally, or per guild", a
3990
4107
  if (!["guild", "global"].includes(mode)) {
3991
4108
  return CLI.logger.error(`'${mode}' is not a valid option. Your options are [guild|global]`);
3992
4109
  }
3993
- let guildIds = (jsTools2.getFlagSubstring(content, "--guilds", 1) || jsTools2.getFlagSubstring(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
4110
+ let guildIds = ($3.str.getFlag(content, "--guilds", 1) || $3.str.getFlag(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
3994
4111
  if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
3995
4112
  switch (mode) {
3996
4113
  case "guild":
4114
+ CLI.logger.info("Unregistering guild commands...");
3997
4115
  await client.commands.slash.unregisterGuild({ guilds: guildIds });
4116
+ await client.commands.context.unregisterGuild({ guilds: guildIds });
3998
4117
  break;
3999
4118
  case "global":
4119
+ CLI.logger.info("Unregistering global commands...");
4000
4120
  await client.commands.slash.unregisterGlobal();
4121
+ await client.commands.context.unregisterGlobal();
4001
4122
  break;
4002
4123
  }
4003
4124
  });
@@ -4005,53 +4126,55 @@ CLI.addCommand("stats", "View statistics about a client instance", (args, conten
4005
4126
  const client = CLI.getClientInstance(content);
4006
4127
  if (!client) return;
4007
4128
  CLI.logger.table(`(stats) ~ ${client.config.app.name}`, {
4008
- "Guilds:": jsTools2.formatThousands(client.guilds.cache.size),
4129
+ "Guilds:": $3.format.number(client.guilds.cache.size),
4009
4130
  "Ping:": `${client.ws.ping || 0}ms`,
4010
- "Uptime:": `${jsTools2.msToSec(client.uptime || 0)}s`,
4131
+ "Uptime:": `${$3.math.secs(client.uptime || 0)}s`,
4011
4132
  "Process Uptime:": `${Math.floor(process.uptime())}s`,
4012
4133
  "Memory Usage:": `${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)} MB`
4013
4134
  });
4014
4135
  });
4015
- CLI.addCommand("cmds", "List the loaded commands", (args, content) => {
4136
+ CLI.addCommand("cmds", "List the loaded commands", async (args, content) => {
4016
4137
  const client = CLI.getClientInstance(content);
4017
4138
  if (!client) return;
4018
- const mode = args[0] || "slash";
4139
+ const mode = (args[0] || "slash").toLowerCase();
4019
4140
  switch (mode) {
4020
- case "slash":
4021
- const slashCommands = Array.from(client.commands.slash.commands.values());
4022
- slashCommands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
4023
- const slashCommands_f = {};
4024
- for (const cmd of slashCommands) {
4025
- slashCommands_f[`/${cmd.builder.name}`] = `~ ${cmd.builder.description || "No description"}`;
4141
+ case "slash": {
4142
+ const commands = Array.from(client.commands.slash.commands.values());
4143
+ commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
4144
+ const tableData = {};
4145
+ for (const cmd of commands) {
4146
+ tableData[`/${cmd.builder.name}`] = `~ ${cmd.builder.description || "No description"}`;
4026
4147
  }
4027
- return CLI.logger.table(
4028
- `(cmds) ~ slash (${jsTools2.formatThousands(client.commands.slash.commands.size)})`,
4029
- slashCommands_f
4030
- );
4031
- case "prefix":
4032
- const prefixCommands = Array.from(client.commands.prefix.commands.values());
4033
- prefixCommands.sort((a, b) => a.name.localeCompare(b.name));
4034
- const prefixCommands_f = {};
4035
- for (const cmd of prefixCommands) {
4036
- prefixCommands_f[`${client.config.prefixCommands.defaultPrefix}${cmd.name}`] = `~ ${cmd.description || "No description"}`;
4148
+ return CLI.logger.table(`(cmds) ~ slash (${$3.format.number(commands.length)})`, tableData);
4149
+ }
4150
+ case "prefix": {
4151
+ const commands = Array.from(client.commands.prefix.commands.values());
4152
+ commands.sort((a, b) => {
4153
+ const nameA = a.toConfig().name;
4154
+ const nameB = b.toConfig().name;
4155
+ return nameA.localeCompare(nameB);
4156
+ });
4157
+ const tableData = {};
4158
+ const defaultPrefix = client.config.prefixCommands.defaultPrefix;
4159
+ for (const cmd of commands) {
4160
+ const config = cmd.toConfig();
4161
+ const aliasIndicator = config.aliases?.length ? ` [${config.aliases.join(", ")}]` : "";
4162
+ tableData[`${defaultPrefix}${config.name}${aliasIndicator}`] = `~ ${config.description || "No description"}`;
4037
4163
  }
4038
- return CLI.logger.table(
4039
- `(cmds) ~ prefix (${jsTools2.formatThousands(client.commands.prefix.commands.size)})`,
4040
- prefixCommands_f
4041
- );
4042
- case "ctx":
4043
- const contextCommands = Array.from(client.commands.context.commands.values());
4044
- contextCommands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
4045
- const contextCommands_f = {};
4046
- for (const cmd of contextCommands) {
4047
- contextCommands_f[`${cmd.builder.name}`] = "";
4164
+ return CLI.logger.table(`(cmds) ~ prefix (${$3.format.number(commands.length)})`, tableData);
4165
+ }
4166
+ case "ctx": {
4167
+ const commands = Array.from(client.commands.context.commands.values());
4168
+ commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
4169
+ const tableData = {};
4170
+ for (const cmd of commands) {
4171
+ const type = cmd.builder.type === 2 ? "User" : "Msg";
4172
+ tableData[`[${type}] ${cmd.builder.name}`] = "";
4048
4173
  }
4049
- return CLI.logger.table(
4050
- `(cmds) ~ ctx (${jsTools2.formatThousands(client.commands.context.commands.size)})`,
4051
- contextCommands_f
4052
- );
4174
+ return CLI.logger.table(`(cmds) ~ ctx (${$3.format.number(commands.length)})`, tableData);
4175
+ }
4053
4176
  default:
4054
- return CLI.logger.error(`'${mode}' is not a valid option. Your options are [slash|prefix|ctx]`);
4177
+ return CLI.logger.error(`'${mode}' is not a valid option. Valid options: [slash|prefix|ctx]`);
4055
4178
  }
4056
4179
  });
4057
4180
  function initCLI() {
@@ -4075,16 +4198,38 @@ function createClient(options, features = {}, config = {}) {
4075
4198
  function getClientInstances() {
4076
4199
  return clientInstances;
4077
4200
  }
4201
+
4202
+ // src/utils/random.ts
4203
+ function pickRandom(arr, options) {
4204
+ const _rnd = () => {
4205
+ return arr[Math.floor(Math.random() * arr.length)];
4206
+ };
4207
+ let att = 0;
4208
+ let candidate = _rnd();
4209
+ if (options?.notEqualTo !== void 0 && arr.length > 1) {
4210
+ while (candidate === options.notEqualTo) {
4211
+ if (att < (options?.maxRerollAttempts ?? 100)) {
4212
+ throw new Error(`pickRandom reached max reroll attempts (${options?.maxRerollAttempts ?? 100})`);
4213
+ }
4214
+ candidate = _rnd();
4215
+ att++;
4216
+ }
4217
+ }
4218
+ return options?.clone ? structuredClone(candidate) : candidate;
4219
+ }
4078
4220
  export {
4079
4221
  BaseCommandBuilder,
4222
+ BetterCollector,
4080
4223
  BetterContainer,
4081
4224
  BetterEmbed,
4082
4225
  BetterModal,
4083
4226
  CLI,
4227
+ CollectorTimeoutType,
4084
4228
  CommandType,
4085
4229
  ContextCommandBuilder,
4086
4230
  DynaSend,
4087
4231
  EventBuilder,
4232
+ LOGGER_COLORS,
4088
4233
  LogLevel,
4089
4234
  Logger,
4090
4235
  MissingPermissionReason,
@@ -4113,6 +4258,7 @@ export {
4113
4258
  clientInstances,
4114
4259
  createClient,
4115
4260
  createMongoSchema,
4261
+ createMongoSession,
4116
4262
  createToolsConfig,
4117
4263
  createVimcordAppConfig,
4118
4264
  createVimcordContextCommandConfig,
@@ -4146,6 +4292,7 @@ export {
4146
4292
  useClient,
4147
4293
  useMongoDatabase,
4148
4294
  useReadyClient,
4295
+ useReadyMongoDatabase,
4149
4296
  validateCommandPermissions
4150
4297
  };
4151
4298
  //# sourceMappingURL=index.js.map