vimcord 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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);
187
+ }
188
+ const perms = this.checkPermissions(client, ctx.member || ctx.user, args[1]);
189
+ if (!perms.validated) {
190
+ return await config.onMissingPermissions?.(perms, ...args);
179
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;
201
286
  return this;
202
287
  }
288
+ /** Merge new permission requirements into the existing ones */
289
+ setPermissions(perms) {
290
+ this.options.permissions = _.merge(this.options.permissions || {}, perms);
291
+ return this;
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,73 +375,48 @@ 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
 
@@ -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();
673
+ }
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
+ }
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;
730
706
  }
731
- validateConfig() {
707
+ /**
708
+ * Set the command description.
709
+ */
710
+ setDescription(description) {
711
+ this.options.description = description;
712
+ return this;
732
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;
@@ -1462,236 +1419,187 @@ var VimcordStatusManager = class {
1462
1419
 
1463
1420
  // src/modules/command.manager.ts
1464
1421
  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) {
1422
+ var VimcordAppCommandManager = class {
1423
+ constructor(client, typeName) {
1477
1424
  this.client = client;
1478
- client.whenReady().then((c) => this.rest = new REST().setToken(c.token));
1425
+ this.typeName = typeName;
1426
+ this.client.whenReady().then((c) => this.rest = new REST().setToken(c.token));
1479
1427
  }
1480
1428
  commands = /* @__PURE__ */ new Map();
1481
1429
  rest;
1482
1430
  get(name) {
1483
1431
  return this.commands.get(name);
1484
1432
  }
1433
+ /**
1434
+ * Filters and returns commands based on deployment options
1435
+ */
1485
1436
  getAll(options) {
1486
1437
  const matchedCommands = /* @__PURE__ */ new Map();
1438
+ const isDev = this.client.config.app.devMode;
1487
1439
  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;
1440
+ const config = cmd.toConfig();
1441
+ const name = cmd.builder.name;
1442
+ if (options?.names || options?.fuzzyNames) {
1443
+ const nameMatched = options.names?.includes(name) || options.fuzzyNames?.some((fuzzy) => name.includes(fuzzy));
1444
+ if (!nameMatched) continue;
1501
1445
  }
1502
- if (!matched) continue;
1503
1446
  if (options?.ignoreDeploymentOptions) {
1504
- if (matchedCommands.has(matched.builder.name)) continue;
1505
- matchedCommands.set(matched.builder.name, matched);
1447
+ matchedCommands.set(name, cmd);
1506
1448
  continue;
1507
1449
  }
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
- }
1450
+ const deployment = config.deployment || {};
1451
+ const isProperEnv = !deployment.environments || deployment.environments.includes(isDev ? "development" : "production");
1452
+ if (!isProperEnv) continue;
1453
+ if (options?.globalOnly && deployment.global === false) continue;
1454
+ matchedCommands.set(name, cmd);
1514
1455
  }
1515
1456
  return matchedCommands;
1516
1457
  }
1517
- /** Import command modules that end with `.slash` */
1518
- async importFrom(dir, replaceAll) {
1519
- if (replaceAll) {
1520
- this.commands.clear();
1458
+ async registerGlobal(options) {
1459
+ const client = await this.client.whenReady();
1460
+ const commands = Array.from(
1461
+ this.getAll({
1462
+ names: options?.commands,
1463
+ fuzzyNames: options?.fuzzyCommands,
1464
+ globalOnly: true
1465
+ }).values()
1466
+ ).map((cmd) => cmd.builder.toJSON());
1467
+ if (!commands.length) {
1468
+ console.log(`[${this.typeName}] No commands to register`);
1469
+ return;
1521
1470
  }
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
- }
1471
+ console.log(`[${this.typeName}] Registering ${commands.length} commands globally...`);
1472
+ try {
1473
+ await this.rest.put(Routes.applicationCommands(client.user.id), { body: commands });
1474
+ console.log(`[${this.typeName}] \u2714 Registered app commands globally`);
1475
+ } catch (err) {
1476
+ console.log(`[${this.typeName}] \u2716 Failed to register app commands globally`, err);
1535
1477
  }
1536
- this.client.logger.moduleLoaded("Slash Commands", importedCommands);
1537
- return this.commands;
1538
1478
  }
1539
1479
  async registerGuild(options) {
1540
1480
  const client = await this.client.whenReady();
1541
- const commandsToRegister = Array.from(
1481
+ const commands = Array.from(
1542
1482
  this.getAll({
1543
1483
  names: options?.commands,
1544
1484
  fuzzyNames: options?.fuzzyCommands
1545
1485
  }).values()
1546
1486
  ).map((cmd) => cmd.builder.toJSON());
1547
- if (!commandsToRegister.length) {
1548
- console.log("[SlashCommandManager] No commands to register");
1487
+ if (!commands.length) {
1488
+ console.log(`[${this.typeName}] No commands to register`);
1549
1489
  return;
1550
1490
  }
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
- );
1491
+ const guildIds = options?.guilds || client.guilds.cache.map((g) => g.id);
1492
+ console.log(`[${this.typeName}] Registering ${commands.length} commands for ${guildIds.length} guilds...`);
1555
1493
  await Promise.all(
1556
1494
  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
- )
1495
+ (guildId) => this.rest.put(Routes.applicationGuildCommands(client.user.id, guildId), { body: commands }).then(() => {
1496
+ const gName = client.guilds.cache.get(guildId)?.name || "n/a";
1497
+ console.log(`[${this.typeName}] \u2714 Set app commands in guild: ${guildId} (${gName})`);
1498
+ }).catch((err) => {
1499
+ const gName = client.guilds.cache.get(guildId)?.name || "n/a";
1500
+ console.log(`[${this.typeName}] \u2716 Failed to set app commands in guild: ${guildId} (${gName})`, err);
1501
+ })
1569
1502
  )
1570
1503
  );
1571
- console.log(
1572
- `[SlashCommandManager] \u2714 Finished registering app (/) commands for ${guildIds.length} ${guildIds.length === 1 ? "guild" : "guilds"}`
1573
- );
1574
1504
  }
1575
1505
  async unregisterGuild(options) {
1576
1506
  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
- );
1507
+ const guildIds = options?.guilds || client.guilds.cache.map((g) => g.id);
1508
+ console.log(`[${this.typeName}] Unregistering commands from ${guildIds.length} guilds...`);
1581
1509
  await Promise.all(
1582
1510
  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
- )
1511
+ (guildId) => this.rest.put(Routes.applicationGuildCommands(client.user.id, guildId), { body: [] }).then(() => console.log(`[${this.typeName}] \u2714 Removed app commands in guild: ${guildId}`)).catch(
1512
+ (err) => console.log(`[${this.typeName}] \u2716 Failed to remove app commands in guild: ${guildId}`, err)
1594
1513
  )
1595
1514
  )
1596
1515
  );
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
1516
  }
1624
1517
  async unregisterGlobal() {
1625
1518
  const client = await this.client.whenReady();
1626
- console.log("[SlashCommandManager] Unregistering app (/) commands globally...");
1627
1519
  try {
1628
1520
  await this.rest.put(Routes.applicationCommands(client.user.id), { body: [] });
1629
- console.log("[SlashCommandManager] \u2714 Removed app (/) commands globally");
1521
+ console.log(`[${this.typeName}] \u2714 Removed app commands globally`);
1630
1522
  } catch (err) {
1631
- console.log("[SlashCommandManager] \u2716 Failed to remove app (/) commands globally", err);
1523
+ console.log(`[${this.typeName}] \u2716 Failed to remove app commands globally`, err);
1632
1524
  }
1633
1525
  }
1634
1526
  };
1635
- var VimcordPrefixCommandManager = class {
1527
+ var VimcordSlashCommandManager = class extends VimcordAppCommandManager {
1636
1528
  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));
1529
+ super(client, "SlashCommandManager");
1530
+ }
1531
+ async importFrom(dir, replaceAll = false) {
1532
+ if (replaceAll) this.commands.clear();
1533
+ const dirs = Array.isArray(dir) ? dir : [dir];
1534
+ const modules = (await Promise.all(
1535
+ dirs.map((d) => importModulesFromDir(d, "slash"))
1536
+ )).flat();
1537
+ for (const { module } of modules) {
1538
+ this.commands.set(module.default.builder.name, module.default);
1539
+ }
1540
+ this.client.logger.moduleLoaded("Slash Commands", modules.length);
1541
+ return this.commands;
1653
1542
  }
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);
1543
+ };
1544
+ var VimcordContextCommandManager = class extends VimcordAppCommandManager {
1545
+ constructor(client) {
1546
+ super(client, "ContextCommandManager");
1547
+ }
1548
+ async importFrom(dir, replaceAll = false) {
1549
+ if (replaceAll) this.commands.clear();
1550
+ const dirs = Array.isArray(dir) ? dir : [dir];
1551
+ const modules = (await Promise.all(
1552
+ dirs.map((d) => importModulesFromDir(d, "ctx"))
1553
+ )).flat();
1554
+ for (const { module } of modules) {
1555
+ this.commands.set(module.default.builder.name, module.default);
1556
+ }
1557
+ this.client.logger.moduleLoaded("Context Commands", modules.length);
1669
1558
  return this.commands;
1670
1559
  }
1671
1560
  };
1672
- var VimcordContextCommandManager = class {
1561
+ var VimcordPrefixCommandManager = class {
1673
1562
  constructor(client) {
1674
1563
  this.client = client;
1675
1564
  }
1676
1565
  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++;
1566
+ resolve(trigger) {
1567
+ const config = this.client.config.prefixCommands;
1568
+ const search = config.allowCaseInsensitiveCommandNames ? trigger.toLowerCase() : trigger;
1569
+ return Array.from(this.commands.values()).find((cmd) => {
1570
+ const opts = cmd.toConfig();
1571
+ const name = config.allowCaseInsensitiveCommandNames ? opts.name.toLowerCase() : opts.name;
1572
+ if (name === search) return true;
1573
+ return opts.aliases?.some(
1574
+ (a) => config.allowCaseInsensitiveCommandNames ? a.toLowerCase() === search : a === search
1575
+ );
1576
+ });
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(
1583
+ (d) => importModulesFromDir(d, "prefix")
1584
+ )
1585
+ )).flat();
1586
+ for (const { module } of modules) {
1587
+ this.commands.set(module.default.toConfig().name, module.default);
1690
1588
  }
1691
- this.client.logger.moduleLoaded("Context Commands", importedCommands);
1589
+ this.client.logger.moduleLoaded("Prefix Commands", modules.length);
1692
1590
  return this.commands;
1693
1591
  }
1694
1592
  };
1593
+ var VimcordCommandManager = class {
1594
+ slash;
1595
+ prefix;
1596
+ context;
1597
+ constructor(client) {
1598
+ this.slash = new VimcordSlashCommandManager(client);
1599
+ this.prefix = new VimcordPrefixCommandManager(client);
1600
+ this.context = new VimcordContextCommandManager(client);
1601
+ }
1602
+ };
1695
1603
 
1696
1604
  // src/modules/event.manager.ts
1697
1605
  import { Events } from "discord.js";
@@ -2087,7 +1995,7 @@ var BetterEmbed = class _BetterEmbed {
2087
1995
  }
2088
1996
  if (this.data.description) {
2089
1997
  this.data.description = formatString(
2090
- Array.isArray(this.data.description) ? this.data.description.join("\n") : this.data.description
1998
+ Array.isArray(this.data.description) ? this.data.description.filter((s) => s !== null && s !== void 0).join("\n") : this.data.description
2091
1999
  );
2092
2000
  }
2093
2001
  if (this.data.footer && typeof this.data.footer === "object") {
@@ -2099,7 +2007,7 @@ var BetterEmbed = class _BetterEmbed {
2099
2007
  if (this.data.imageUrl) {
2100
2008
  this.data.imageUrl = formatString(this.data.imageUrl);
2101
2009
  }
2102
- this.data.fields = this.data.fields.map((field) => ({
2010
+ this.data.fields = this.data.fields.filter(Boolean).map((field) => ({
2103
2011
  ...field,
2104
2012
  name: formatString(field.name),
2105
2013
  value: formatString(field.value)
@@ -2324,7 +2232,7 @@ async function retryExponentialBackoff(fn, maxRetries = 3, retryDelay = 1e3) {
2324
2232
  }
2325
2233
 
2326
2234
  // package.json
2327
- var version = "1.0.0";
2235
+ var version = "1.0.1";
2328
2236
 
2329
2237
  // src/client.ts
2330
2238
  import { randomUUID as randomUUID3 } from "crypto";
@@ -2590,62 +2498,63 @@ var Vimcord = class _Vimcord extends Client2 {
2590
2498
  return fetchGuild(client, id);
2591
2499
  }
2592
2500
  };
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
2501
  var defaultPrefixCommandHandler = new EventBuilder({
2617
2502
  event: "messageCreate",
2618
2503
  name: "PrefixCommandHandler",
2619
2504
  async execute(client, message) {
2620
- if (message.author.bot) return;
2505
+ if (message.author.bot || !message.guild) return;
2506
+ const config = client.config.prefixCommands;
2507
+ let activePrefix = config.defaultPrefix;
2508
+ if (config.guildPrefixResolver) {
2509
+ try {
2510
+ const customPrefix = await config.guildPrefixResolver(client, message.guild.id);
2511
+ if (customPrefix) activePrefix = customPrefix;
2512
+ } catch (err) {
2513
+ client.logger.error(`Error in guildPrefixResolver for guild ${message.guild.id}:`, err);
2514
+ }
2515
+ }
2621
2516
  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;
2517
+ if (message.content.startsWith(activePrefix)) {
2518
+ prefixUsed = activePrefix;
2519
+ } else if (config.allowMentionAsPrefix) {
2520
+ const mention = userMention(client.user.id);
2521
+ if (message.content.startsWith(mention)) {
2522
+ prefixUsed = message.content.startsWith(`${mention} `) ? `${mention} ` : mention;
2631
2523
  }
2632
- } else {
2633
- prefixUsed = client.config.prefixCommands.defaultPrefix;
2634
2524
  }
2635
- message.content = message.content.slice(prefixUsed.length);
2636
- let commandName = message.content.split(" ")[0];
2637
- if (!commandName) {
2638
- return;
2525
+ if (!prefixUsed) return;
2526
+ const contentWithoutPrefix = message.content.slice(prefixUsed.length).trim();
2527
+ const args = contentWithoutPrefix.split(/\s+/);
2528
+ const trigger = args.shift();
2529
+ if (!trigger) return;
2530
+ const command = client.commands.prefix.resolve(trigger);
2531
+ if (!command) return;
2532
+ message.content = args.join(" ");
2533
+ try {
2534
+ return await command.run(client, client, message);
2535
+ } catch (err) {
2536
+ await sendCommandErrorEmbed(client, err, message.guild, message);
2537
+ throw err;
2639
2538
  }
2640
- const command = client.commands.prefix.get(commandName) || client.commands.prefix.getByAlias(commandName);
2539
+ }
2540
+ });
2541
+ var defaultSlashCommandHandler = new EventBuilder({
2542
+ event: "interactionCreate",
2543
+ name: "SlashCommandHandler",
2544
+ async execute(client, interaction) {
2545
+ if (!interaction.isChatInputCommand()) return;
2546
+ const command = client.commands.slash.get(interaction.commandName);
2641
2547
  if (!command) {
2642
- return;
2548
+ const content = `**/\`${interaction.commandName}\`** is not a registered command.`;
2549
+ if (interaction.replied || interaction.deferred) {
2550
+ return interaction.followUp({ content, flags: "Ephemeral" });
2551
+ }
2552
+ return interaction.reply({ content, flags: "Ephemeral" });
2643
2553
  }
2644
- message.content = message.content.slice(commandName.length + 1);
2645
2554
  try {
2646
- return command.executeCommand(client, message);
2555
+ return await command.run(client, client, interaction);
2647
2556
  } catch (err) {
2648
- sendCommandErrorEmbed(client, err, message.guild, message);
2557
+ await sendCommandErrorEmbed(client, err, interaction.guild, interaction);
2649
2558
  throw err;
2650
2559
  }
2651
2560
  }
@@ -2653,9 +2562,22 @@ var defaultPrefixCommandHandler = new EventBuilder({
2653
2562
  var defaultContextCommandHandler = new EventBuilder({
2654
2563
  event: "interactionCreate",
2655
2564
  name: "ContextCommandHandler",
2656
- execute(client, interaction) {
2565
+ async execute(client, interaction) {
2657
2566
  if (!interaction.isContextMenuCommand()) return;
2658
- interaction.reply({ content: "This handler is not yet implemented" });
2567
+ const command = client.commands.context.get(interaction.commandName);
2568
+ if (!command) {
2569
+ const content = `**${interaction.commandName}** is not a registered context command.`;
2570
+ if (interaction.replied || interaction.deferred) {
2571
+ return interaction.followUp({ content, flags: "Ephemeral" });
2572
+ }
2573
+ return interaction.reply({ content, flags: "Ephemeral" });
2574
+ }
2575
+ try {
2576
+ return await command.run(client, client, interaction);
2577
+ } catch (err) {
2578
+ await sendCommandErrorEmbed(client, err, interaction.guild, interaction);
2579
+ throw err;
2580
+ }
2659
2581
  }
2660
2582
  });
2661
2583
 
@@ -2670,8 +2592,8 @@ try {
2670
2592
  }
2671
2593
  var globalInstanceEmitter = new EventEmitter2();
2672
2594
  var instances = [];
2673
- async function useMongoDatabase(index) {
2674
- const instance = instances.at(index ?? 0);
2595
+ async function useMongoDatabase(instanceIndex) {
2596
+ const instance = instances.at(instanceIndex ?? 0);
2675
2597
  if (!instance) {
2676
2598
  return new Promise((resolve, reject) => {
2677
2599
  const timeout = setTimeout(() => reject("useMongoDatabase timed out"), 45e3);
@@ -2683,6 +2605,15 @@ async function useMongoDatabase(index) {
2683
2605
  }
2684
2606
  return instance;
2685
2607
  }
2608
+ async function useReadyMongoDatabase(instanceIndex) {
2609
+ const instance = await useMongoDatabase(instanceIndex);
2610
+ await instance.waitForReady();
2611
+ return instance;
2612
+ }
2613
+ async function createMongoSession(instanceIndex, options) {
2614
+ const instance = await useReadyMongoDatabase(instanceIndex);
2615
+ return instance.mongoose.startSession(options);
2616
+ }
2686
2617
  var MongoDatabase = class {
2687
2618
  constructor(client, options) {
2688
2619
  this.client = client;
@@ -2839,6 +2770,7 @@ var MongoSchemaBuilder = class {
2839
2770
  this.eventEmitter.off(event, listener);
2840
2771
  return this;
2841
2772
  }
2773
+ /** Execute a function while ensuring the connection is ready. On error it will retry using an exponential backoff. */
2842
2774
  async execute(fn) {
2843
2775
  try {
2844
2776
  if (!this.isReady) {
@@ -2875,9 +2807,9 @@ var MongoSchemaBuilder = class {
2875
2807
  return id;
2876
2808
  });
2877
2809
  }
2878
- async count(filter) {
2810
+ async count(filter, options) {
2879
2811
  return await this.execute(async () => {
2880
- return this.model.countDocuments(filter);
2812
+ return this.model.countDocuments(filter, options);
2881
2813
  });
2882
2814
  }
2883
2815
  async exists(filter) {
@@ -2885,29 +2817,29 @@ var MongoSchemaBuilder = class {
2885
2817
  return await this.model.exists(filter) ? true : false;
2886
2818
  });
2887
2819
  }
2888
- async create(query) {
2820
+ async create(query, options) {
2889
2821
  return await this.execute(async () => {
2890
- return this.model.create(query);
2891
- });
2822
+ return this.model.create(query, options);
2823
+ }) ?? [];
2892
2824
  }
2893
2825
  async upsert(filter, query, options) {
2894
2826
  return await this.execute(async () => {
2895
2827
  return this.model.findOneAndUpdate(filter, query, { ...options, upsert: true, new: true });
2896
2828
  });
2897
2829
  }
2898
- async delete(filter) {
2830
+ async delete(filter, options) {
2899
2831
  return await this.execute(async () => {
2900
- return this.model.deleteOne(filter);
2832
+ return this.model.deleteOne(filter, options);
2901
2833
  });
2902
2834
  }
2903
- async deleteAll(filter) {
2835
+ async deleteAll(filter, options) {
2904
2836
  return await this.execute(async () => {
2905
- return this.model.deleteMany(filter);
2837
+ return this.model.deleteMany(filter, options);
2906
2838
  });
2907
2839
  }
2908
- async distinct(key, filter) {
2840
+ async distinct(key, filter, options) {
2909
2841
  return await this.execute(async () => {
2910
- return this.model.distinct(key, filter);
2842
+ return this.model.distinct(key, filter, options);
2911
2843
  });
2912
2844
  }
2913
2845
  async fetch(filter, projection, options) {
@@ -2938,6 +2870,215 @@ var MongoSchemaBuilder = class {
2938
2870
  }
2939
2871
  };
2940
2872
 
2873
+ // src/tools/BetterCollector.ts
2874
+ var CollectorTimeoutType = /* @__PURE__ */ ((CollectorTimeoutType2) => {
2875
+ CollectorTimeoutType2[CollectorTimeoutType2["DisableComponents"] = 0] = "DisableComponents";
2876
+ CollectorTimeoutType2[CollectorTimeoutType2["DeleteMessage"] = 1] = "DeleteMessage";
2877
+ CollectorTimeoutType2[CollectorTimeoutType2["DoNothing"] = 2] = "DoNothing";
2878
+ return CollectorTimeoutType2;
2879
+ })(CollectorTimeoutType || {});
2880
+ var BetterCollector = class _BetterCollector {
2881
+ message;
2882
+ collector;
2883
+ options;
2884
+ activeUsers = /* @__PURE__ */ new Set();
2885
+ participantWarningCooldowns = /* @__PURE__ */ new Map();
2886
+ config;
2887
+ events = {
2888
+ collectId: /* @__PURE__ */ new Map(),
2889
+ collect: [],
2890
+ end: [],
2891
+ timeout: []
2892
+ };
2893
+ static create(message, options) {
2894
+ return new _BetterCollector(message, options);
2895
+ }
2896
+ async validateParticipant(interaction, participants) {
2897
+ const allowedParticipants = participants || this.options?.participants || [];
2898
+ if (!allowedParticipants.length) return true;
2899
+ const isAllowed = allowedParticipants.some((user) => {
2900
+ if (typeof user === "string") return user === interaction.user.id;
2901
+ if (typeof user === "object" && "id" in user) return user.id === interaction.user.id;
2902
+ return false;
2903
+ });
2904
+ if (!isAllowed) {
2905
+ const now = Date.now();
2906
+ const lastWarned = this.participantWarningCooldowns.get(interaction.user.id) || 0;
2907
+ if (now - lastWarned > this.config.collector.notAParticipantWarningCooldown) {
2908
+ this.participantWarningCooldowns.set(interaction.user.id, now);
2909
+ if (this.config.collector.notAParticipantMessage) {
2910
+ await interaction.reply({
2911
+ content: this.config.collector.notAParticipantMessage,
2912
+ flags: "Ephemeral"
2913
+ }).catch(() => {
2914
+ });
2915
+ } else {
2916
+ await interaction.deferUpdate().catch(() => {
2917
+ });
2918
+ }
2919
+ } else {
2920
+ await interaction.deferUpdate().catch(() => {
2921
+ });
2922
+ }
2923
+ }
2924
+ return isAllowed;
2925
+ }
2926
+ build() {
2927
+ if (!this.message) return;
2928
+ if (this.collector) return;
2929
+ this.collector = this.message.createMessageComponentCollector({
2930
+ idle: this.options?.idle ?? this.config.timeouts.collectorIdle,
2931
+ time: this.options?.timeout ?? this.config.timeouts.collectorTimeout,
2932
+ componentType: this.options?.type,
2933
+ max: this.options?.max,
2934
+ maxComponents: this.options?.maxComponents,
2935
+ maxUsers: this.options?.maxUsers
2936
+ });
2937
+ this.setupListeners();
2938
+ }
2939
+ setupListeners() {
2940
+ if (!this.collector) return;
2941
+ this.collector.on("collect", async (interaction) => {
2942
+ if (this.options?.userLock && this.activeUsers.has(interaction.user.id)) {
2943
+ return interaction.reply({ content: this.config.collector.userLockMessage, flags: "Ephemeral" }).catch(() => {
2944
+ });
2945
+ }
2946
+ if (this.options?.userLock) {
2947
+ this.activeUsers.add(interaction.user.id);
2948
+ }
2949
+ const globalListeners = this.events.collect;
2950
+ const idListeners = this.events.collectId.get(interaction.customId) || [];
2951
+ const allListeners = [...globalListeners, ...idListeners];
2952
+ const shouldBeDeferred = allListeners.find((l) => l.options?.defer)?.options?.defer;
2953
+ const validListeners = [];
2954
+ for (const listener of allListeners) {
2955
+ const isAllowed = await this.validateParticipant(interaction, listener.options?.participants);
2956
+ if (isAllowed) validListeners.push(listener);
2957
+ }
2958
+ if (validListeners.length === 0) return;
2959
+ try {
2960
+ if (shouldBeDeferred) {
2961
+ if (typeof shouldBeDeferred === "object") {
2962
+ if (shouldBeDeferred.update) {
2963
+ await interaction.deferUpdate().catch(Boolean);
2964
+ } else {
2965
+ await interaction.deferReply({ ephemeral: shouldBeDeferred.ephemeral }).catch(Boolean);
2966
+ }
2967
+ } else {
2968
+ await interaction.deferReply().catch(Boolean);
2969
+ }
2970
+ }
2971
+ if (this.options?.sequential) {
2972
+ for (const listener of allListeners) {
2973
+ try {
2974
+ const isAllowed = await this.validateParticipant(interaction, listener.options?.participants);
2975
+ if (!isAllowed) return;
2976
+ await listener.fn(interaction);
2977
+ } catch (err) {
2978
+ this.handleListenerError(err);
2979
+ }
2980
+ }
2981
+ } else {
2982
+ Promise.all(
2983
+ allListeners.map((l) => {
2984
+ const isAllowed = this.validateParticipant(interaction, l.options?.participants);
2985
+ if (!isAllowed) return;
2986
+ return l.fn(interaction).catch(this.handleListenerError);
2987
+ })
2988
+ );
2989
+ }
2990
+ } finally {
2991
+ if (this.options?.userLock) {
2992
+ this.activeUsers.delete(interaction.user.id);
2993
+ }
2994
+ }
2995
+ });
2996
+ this.collector.on("end", async (collected, reason) => {
2997
+ if (this.options?.sequential) {
2998
+ for (const listener of this.events.end) {
2999
+ try {
3000
+ await listener.fn(collected, reason);
3001
+ } catch (err) {
3002
+ this.handleListenerError(err);
3003
+ }
3004
+ }
3005
+ } else {
3006
+ Promise.all(this.events.end.map((l) => l.fn(collected, reason).catch(this.handleListenerError)));
3007
+ }
3008
+ switch (this.options?.onTimeout) {
3009
+ case 0 /* DisableComponents */:
3010
+ if (!this.message?.editable) break;
3011
+ try {
3012
+ const disabledRows = this.message.components.map((row) => {
3013
+ const updatedRow = row.toJSON();
3014
+ if ("components" in updatedRow) {
3015
+ updatedRow.components = updatedRow.components.map((component) => ({
3016
+ ...component,
3017
+ disabled: true
3018
+ }));
3019
+ }
3020
+ return updatedRow;
3021
+ });
3022
+ await this.message.edit({ components: disabledRows });
3023
+ } catch (err) {
3024
+ if (!(err instanceof Error && err.message.includes("Unknown Message"))) {
3025
+ this.handleListenerError(err);
3026
+ }
3027
+ }
3028
+ break;
3029
+ case 1 /* DeleteMessage */:
3030
+ if (!this.message?.deletable) break;
3031
+ await this.message.delete().catch(Boolean);
3032
+ break;
3033
+ case 2 /* DoNothing */:
3034
+ default:
3035
+ break;
3036
+ }
3037
+ });
3038
+ }
3039
+ handleListenerError(err) {
3040
+ console.error("[BetterCollector] Listener Error:", err);
3041
+ }
3042
+ constructor(message, options) {
3043
+ this.config = options?.config || globalVimcordToolsConfig;
3044
+ this.message = message || void 0;
3045
+ this.options = options;
3046
+ this.build();
3047
+ }
3048
+ on(idOrFunc, fnOrOptions, options) {
3049
+ let finalFn;
3050
+ let finalOptions;
3051
+ let customId;
3052
+ if (typeof idOrFunc === "function") {
3053
+ finalFn = idOrFunc;
3054
+ finalOptions = fnOrOptions;
3055
+ } else {
3056
+ if (typeof fnOrOptions !== "function") {
3057
+ throw new Error("[BetterCollector] Second argument must be a function when a customId is provided.");
3058
+ }
3059
+ customId = idOrFunc;
3060
+ finalFn = fnOrOptions;
3061
+ finalOptions = options;
3062
+ }
3063
+ if (customId) {
3064
+ const listeners = this.events.collectId.get(customId) || [];
3065
+ listeners.push({ fn: finalFn, options: finalOptions });
3066
+ this.events.collectId.set(customId, listeners);
3067
+ } else {
3068
+ this.events.collect.push({ fn: finalFn, options: finalOptions });
3069
+ }
3070
+ return this;
3071
+ }
3072
+ onEnd(fn, options) {
3073
+ this.events.end.push({ fn, options });
3074
+ return this;
3075
+ }
3076
+ /** Manually stop the collector and trigger cleanup */
3077
+ stop(reason = "manual") {
3078
+ this.collector?.stop(reason);
3079
+ }
3080
+ };
3081
+
2941
3082
  // src/tools/BetterContainer.ts
2942
3083
  import {
2943
3084
  ButtonBuilder as ButtonBuilder2,
@@ -2982,7 +3123,9 @@ var BetterContainer = class {
2982
3123
  return this;
2983
3124
  }
2984
3125
  addText(text) {
2985
- this.container.addTextDisplayComponents((tdb) => tdb.setContent(Array.isArray(text) ? text.join("\n") : text));
3126
+ this.container.addTextDisplayComponents(
3127
+ (tdb) => tdb.setContent(Array.isArray(text) ? text.filter((t) => t !== null && t !== void 0).join("\n") : text)
3128
+ );
2986
3129
  return this;
2987
3130
  }
2988
3131
  addMedia(...media) {
@@ -3006,7 +3149,9 @@ var BetterContainer = class {
3006
3149
  this.container.addSectionComponents((sb) => {
3007
3150
  if (data.text) {
3008
3151
  sb.addTextDisplayComponents(
3009
- (tdb) => tdb.setContent(Array.isArray(data.text) ? data.text.join("\n") : data.text)
3152
+ (tdb) => tdb.setContent(
3153
+ Array.isArray(data.text) ? data.text.filter((t) => t !== null && t !== void 0).join("\n") : data.text
3154
+ )
3010
3155
  );
3011
3156
  }
3012
3157
  if (data.thumbnail) sb.setThumbnailAccessory(new ThumbnailBuilder(data.thumbnail));
@@ -3237,6 +3382,7 @@ var BetterModal = class {
3237
3382
  // src/tools/Paginator.ts
3238
3383
  import {
3239
3384
  ActionRowBuilder as ActionRowBuilder4,
3385
+ AttachmentBuilder as AttachmentBuilder2,
3240
3386
  ButtonBuilder as ButtonBuilder3,
3241
3387
  ButtonStyle as ButtonStyle2,
3242
3388
  ComponentType as ComponentType2,
@@ -3256,6 +3402,7 @@ var PaginationTimeoutType = /* @__PURE__ */ ((PaginationTimeoutType2) => {
3256
3402
  PaginationTimeoutType2[PaginationTimeoutType2["DisableComponents"] = 0] = "DisableComponents";
3257
3403
  PaginationTimeoutType2[PaginationTimeoutType2["ClearComponents"] = 1] = "ClearComponents";
3258
3404
  PaginationTimeoutType2[PaginationTimeoutType2["DeleteMessage"] = 2] = "DeleteMessage";
3405
+ PaginationTimeoutType2[PaginationTimeoutType2["DoNothing"] = 3] = "DoNothing";
3259
3406
  return PaginationTimeoutType2;
3260
3407
  })(PaginationTimeoutType || {});
3261
3408
  function wrapPositive(num, max) {
@@ -3336,7 +3483,8 @@ var Paginator = class {
3336
3483
  last: [],
3337
3484
  collect: [],
3338
3485
  react: [],
3339
- timeout: []
3486
+ preTimeout: [],
3487
+ postTimeout: []
3340
3488
  };
3341
3489
  if (this.options.pages.length) {
3342
3490
  this.addChapter(this.options.pages, { label: "Default" });
@@ -3403,16 +3551,23 @@ var Paginator = class {
3403
3551
  embeds: [],
3404
3552
  components: [],
3405
3553
  flags: [],
3554
+ files: [],
3555
+ // Explicitly empty to clear previous files on page switch
3406
3556
  ...options,
3407
3557
  withResponse: true
3408
3558
  };
3409
3559
  const page = this.data.page.current;
3560
+ const currentChapter = this.chapters[this.data.page.index.chapter];
3561
+ const chapterFile = currentChapter?.files?.[this.data.page.index.nested];
3562
+ if (chapterFile) sendOptions.files.push(chapterFile);
3410
3563
  if (Array.isArray(page)) {
3411
3564
  sendOptions.embeds.push(...page);
3412
3565
  } else if (typeof page === "string") {
3413
3566
  sendOptions.content = page;
3414
3567
  } else if (isEmbed(page)) {
3415
3568
  sendOptions.embeds.push(page);
3569
+ } else if (page instanceof AttachmentBuilder2) {
3570
+ sendOptions.files.push(page);
3416
3571
  } else if (page instanceof ContainerBuilder3 || page instanceof BetterContainer) {
3417
3572
  sendOptions.components.push(page);
3418
3573
  if (!sendOptions.flags.includes("IsComponentsV2")) {
@@ -3424,7 +3579,7 @@ var Paginator = class {
3424
3579
  }
3425
3580
  async handlePostTimeout() {
3426
3581
  if (!this.data.message) return;
3427
- this.callEventStack("timeout", this.data.message);
3582
+ this.callEventStack("preTimeout", this.data.message);
3428
3583
  this.data.collectors.component?.stop();
3429
3584
  this.data.collectors.reaction?.stop();
3430
3585
  switch (this.options.onTimeout) {
@@ -3468,7 +3623,10 @@ var Paginator = class {
3468
3623
  case 2 /* DeleteMessage */:
3469
3624
  await this.data.message.delete().catch(Boolean);
3470
3625
  break;
3626
+ case 3 /* DoNothing */:
3627
+ break;
3471
3628
  }
3629
+ this.callEventStack("postTimeout", this.data.message);
3472
3630
  }
3473
3631
  async nav_removeFromMessage() {
3474
3632
  if (!this.data.message?.editable) return;
@@ -3515,35 +3673,43 @@ var Paginator = class {
3515
3673
  await i.reply({ content: "Jump not implemented yet.", flags: "Ephemeral" });
3516
3674
  return;
3517
3675
  }
3518
- await i.deferUpdate().catch(Boolean);
3519
3676
  switch (i.customId) {
3520
3677
  case "ssm_chapterSelect":
3678
+ await i.deferUpdate().catch(Boolean);
3521
3679
  const chapterIndex = this.chapters.findIndex(
3522
3680
  (c) => c.id === i.values[0]
3523
3681
  );
3524
3682
  await this.setPage(chapterIndex, 0);
3683
+ await this.refresh();
3525
3684
  break;
3526
3685
  case "btn_first":
3686
+ await i.deferUpdate().catch(Boolean);
3527
3687
  this.callEventStack("first", this.data.page.current, this.data.page.index);
3528
3688
  await this.setPage(this.data.page.index.chapter, 0);
3689
+ await this.refresh();
3529
3690
  break;
3530
3691
  case "btn_back":
3692
+ await i.deferUpdate().catch(Boolean);
3531
3693
  this.callEventStack("back", this.data.page.current, this.data.page.index);
3532
3694
  await this.setPage(this.data.page.index.chapter, this.data.page.index.nested - 1);
3695
+ await this.refresh();
3533
3696
  break;
3534
3697
  case "btn_next":
3698
+ await i.deferUpdate().catch(Boolean);
3535
3699
  this.callEventStack("next", this.data.page.current, this.data.page.index);
3536
3700
  await this.setPage(this.data.page.index.chapter, this.data.page.index.nested + 1);
3701
+ await this.refresh();
3537
3702
  break;
3538
3703
  case "btn_last":
3704
+ await i.deferUpdate().catch(Boolean);
3539
3705
  this.callEventStack("last", this.data.page.current, this.data.page.index);
3540
3706
  await this.setPage(
3541
3707
  this.data.page.index.chapter,
3542
3708
  this.chapters[this.data.page.index.chapter].pages.length - 1
3543
3709
  );
3710
+ await this.refresh();
3544
3711
  break;
3545
3712
  }
3546
- await this.refresh();
3547
3713
  } catch (err) {
3548
3714
  console.error("[Paginator] Component navigation error", err);
3549
3715
  }
@@ -3583,7 +3749,7 @@ var Paginator = class {
3583
3749
  data.value = `ssm_c:${this.chapters.length}`;
3584
3750
  }
3585
3751
  const normalizedPages = resolvePages(pages);
3586
- this.chapters.push({ id: data.value, pages: normalizedPages });
3752
+ this.chapters.push({ id: data.value, pages: normalizedPages, files: data.files });
3587
3753
  this.data.components.chapterSelect.addOptions(data);
3588
3754
  return this;
3589
3755
  }
@@ -3965,7 +4131,7 @@ CLI.addCommand("help", "View information about a command, or the available CLI o
3965
4131
  }
3966
4132
  CLI.logger.table("(help)", helpList);
3967
4133
  });
3968
- CLI.addCommand("register", "Register app commands globally, or per guild", async (args, content) => {
4134
+ CLI.addCommand("register", "Register app commands (slash & context) globally, or per guild", async (args, content) => {
3969
4135
  const client = CLI.getClientInstance(content);
3970
4136
  if (!client) return;
3971
4137
  const mode = args[0]?.toLowerCase() || "";
@@ -3976,10 +4142,14 @@ CLI.addCommand("register", "Register app commands globally, or per guild", async
3976
4142
  if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
3977
4143
  switch (mode) {
3978
4144
  case "guild":
4145
+ CLI.logger.info("Registering guild commands...");
3979
4146
  await client.commands.slash.registerGuild({ guilds: guildIds });
4147
+ await client.commands.context.registerGuild({ guilds: guildIds });
3980
4148
  break;
3981
4149
  case "global":
4150
+ CLI.logger.info("Registering global commands...");
3982
4151
  await client.commands.slash.registerGlobal();
4152
+ await client.commands.context.registerGlobal();
3983
4153
  break;
3984
4154
  }
3985
4155
  });
@@ -3994,10 +4164,14 @@ CLI.addCommand("unregister", "Unregister app commands globally, or per guild", a
3994
4164
  if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
3995
4165
  switch (mode) {
3996
4166
  case "guild":
4167
+ CLI.logger.info("Unregistering guild commands...");
3997
4168
  await client.commands.slash.unregisterGuild({ guilds: guildIds });
4169
+ await client.commands.context.unregisterGuild({ guilds: guildIds });
3998
4170
  break;
3999
4171
  case "global":
4172
+ CLI.logger.info("Unregistering global commands...");
4000
4173
  await client.commands.slash.unregisterGlobal();
4174
+ await client.commands.context.unregisterGlobal();
4001
4175
  break;
4002
4176
  }
4003
4177
  });
@@ -4012,46 +4186,48 @@ CLI.addCommand("stats", "View statistics about a client instance", (args, conten
4012
4186
  "Memory Usage:": `${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)} MB`
4013
4187
  });
4014
4188
  });
4015
- CLI.addCommand("cmds", "List the loaded commands", (args, content) => {
4189
+ CLI.addCommand("cmds", "List the loaded commands", async (args, content) => {
4016
4190
  const client = CLI.getClientInstance(content);
4017
4191
  if (!client) return;
4018
- const mode = args[0] || "slash";
4192
+ const mode = (args[0] || "slash").toLowerCase();
4019
4193
  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"}`;
4194
+ case "slash": {
4195
+ const commands = Array.from(client.commands.slash.commands.values());
4196
+ commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
4197
+ const tableData = {};
4198
+ for (const cmd of commands) {
4199
+ tableData[`/${cmd.builder.name}`] = `~ ${cmd.builder.description || "No description"}`;
4026
4200
  }
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"}`;
4201
+ return CLI.logger.table(`(cmds) ~ slash (${jsTools2.formatThousands(commands.length)})`, tableData);
4202
+ }
4203
+ case "prefix": {
4204
+ const commands = Array.from(client.commands.prefix.commands.values());
4205
+ commands.sort((a, b) => {
4206
+ const nameA = a.toConfig().name;
4207
+ const nameB = b.toConfig().name;
4208
+ return nameA.localeCompare(nameB);
4209
+ });
4210
+ const tableData = {};
4211
+ const defaultPrefix = client.config.prefixCommands.defaultPrefix;
4212
+ for (const cmd of commands) {
4213
+ const config = cmd.toConfig();
4214
+ const aliasIndicator = config.aliases?.length ? ` [${config.aliases.join(", ")}]` : "";
4215
+ tableData[`${defaultPrefix}${config.name}${aliasIndicator}`] = `~ ${config.description || "No description"}`;
4037
4216
  }
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}`] = "";
4217
+ return CLI.logger.table(`(cmds) ~ prefix (${jsTools2.formatThousands(commands.length)})`, tableData);
4218
+ }
4219
+ case "ctx": {
4220
+ const commands = Array.from(client.commands.context.commands.values());
4221
+ commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
4222
+ const tableData = {};
4223
+ for (const cmd of commands) {
4224
+ const type = cmd.builder.type === 2 ? "User" : "Msg";
4225
+ tableData[`[${type}] ${cmd.builder.name}`] = "";
4048
4226
  }
4049
- return CLI.logger.table(
4050
- `(cmds) ~ ctx (${jsTools2.formatThousands(client.commands.context.commands.size)})`,
4051
- contextCommands_f
4052
- );
4227
+ return CLI.logger.table(`(cmds) ~ ctx (${jsTools2.formatThousands(commands.length)})`, tableData);
4228
+ }
4053
4229
  default:
4054
- return CLI.logger.error(`'${mode}' is not a valid option. Your options are [slash|prefix|ctx]`);
4230
+ return CLI.logger.error(`'${mode}' is not a valid option. Valid options: [slash|prefix|ctx]`);
4055
4231
  }
4056
4232
  });
4057
4233
  function initCLI() {
@@ -4077,14 +4253,17 @@ function getClientInstances() {
4077
4253
  }
4078
4254
  export {
4079
4255
  BaseCommandBuilder,
4256
+ BetterCollector,
4080
4257
  BetterContainer,
4081
4258
  BetterEmbed,
4082
4259
  BetterModal,
4083
4260
  CLI,
4261
+ CollectorTimeoutType,
4084
4262
  CommandType,
4085
4263
  ContextCommandBuilder,
4086
4264
  DynaSend,
4087
4265
  EventBuilder,
4266
+ LOGGER_COLORS,
4088
4267
  LogLevel,
4089
4268
  Logger,
4090
4269
  MissingPermissionReason,
@@ -4113,6 +4292,7 @@ export {
4113
4292
  clientInstances,
4114
4293
  createClient,
4115
4294
  createMongoSchema,
4295
+ createMongoSession,
4116
4296
  createToolsConfig,
4117
4297
  createVimcordAppConfig,
4118
4298
  createVimcordContextCommandConfig,
@@ -4146,6 +4326,7 @@ export {
4146
4326
  useClient,
4147
4327
  useMongoDatabase,
4148
4328
  useReadyClient,
4329
+ useReadyMongoDatabase,
4149
4330
  validateCommandPermissions
4150
4331
  };
4151
4332
  //# sourceMappingURL=index.js.map