vimcord 1.0.58 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,14 +1,8 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
6
  var __copyProps = (to, from, except, desc) => {
13
7
  if (from && typeof from === "object" || typeof from === "function") {
14
8
  for (let key of __getOwnPropNames(from))
@@ -17,4559 +11,14 @@ var __copyProps = (to, from, except, desc) => {
17
11
  }
18
12
  return to;
19
13
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
14
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
28
15
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
16
 
30
17
  // src/index.ts
31
18
  var index_exports = {};
32
- __export(index_exports, {
33
- BaseCommandBuilder: () => BaseCommandBuilder,
34
- BaseCommandManager: () => BaseCommandManager,
35
- BetterCollector: () => BetterCollector,
36
- BetterContainer: () => BetterContainer,
37
- BetterEmbed: () => BetterEmbed,
38
- BetterModal: () => BetterModal,
39
- CLI: () => CLI,
40
- CollectorTimeoutType: () => CollectorTimeoutType,
41
- CommandManager: () => CommandManager,
42
- CommandType: () => CommandType,
43
- ContextCommandBuilder: () => ContextCommandBuilder,
44
- ContextCommandManager: () => ContextCommandManager,
45
- DEFAULT_MODULE_SUFFIXES: () => DEFAULT_MODULE_SUFFIXES,
46
- DynaSend: () => DynaSend,
47
- ErrorHandler: () => ErrorHandler,
48
- EventBuilder: () => EventBuilder,
49
- EventManager: () => EventManager,
50
- LOGGER_COLORS: () => LOGGER_COLORS,
51
- LogLevel: () => LogLevel,
52
- Logger: () => Logger,
53
- MissingPermissionReason: () => MissingPermissionReason,
54
- ModuleImporter: () => ModuleImporter,
55
- MongoDatabase: () => MongoDatabase,
56
- MongoSchemaBuilder: () => MongoSchemaBuilder,
57
- PaginationTimeoutType: () => PaginationTimeoutType,
58
- PaginationType: () => PaginationType,
59
- Paginator: () => Paginator,
60
- PrefixCommandBuilder: () => PrefixCommandBuilder,
61
- PrefixCommandManager: () => PrefixCommandManager,
62
- Prompt: () => Prompt,
63
- PromptResolveType: () => PromptResolveType,
64
- RateLimitScope: () => RateLimitScope,
65
- SendMethod: () => SendMethod,
66
- SlashCommandBuilder: () => SlashCommandBuilder,
67
- SlashCommandManager: () => SlashCommandManager,
68
- StatusManager: () => StatusManager,
69
- StatusType: () => StatusType,
70
- Vimcord: () => Vimcord,
71
- VimcordCLI: () => VimcordCLI,
72
- VimcordErrorHandler: () => VimcordErrorHandler,
73
- __zero: () => __zero,
74
- cleanMention: () => cleanMention,
75
- clientLoggerFactory: () => clientLoggerFactory,
76
- configSetters: () => configSetters,
77
- contextCommandHandler: () => contextCommandHandler,
78
- createAppConfig: () => createAppConfig,
79
- createClient: () => createClient,
80
- createConfigFactory: () => createConfigFactory,
81
- createContextCommandConfig: () => createContextCommandConfig,
82
- createMongoPlugin: () => createMongoPlugin,
83
- createMongoSchema: () => createMongoSchema,
84
- createPrefixCommandConfig: () => createPrefixCommandConfig,
85
- createSlashCommandConfig: () => createSlashCommandConfig,
86
- createStaffConfig: () => createStaffConfig,
87
- createToolsConfig: () => createToolsConfig,
88
- createVimcordStatusConfig: () => createVimcordStatusConfig,
89
- deepMerge: () => deepMerge,
90
- defineClientOptions: () => defineClientOptions,
91
- defineGlobalToolsConfig: () => defineGlobalToolsConfig,
92
- defineVimcordConfig: () => defineVimcordConfig,
93
- defineVimcordFeatures: () => defineVimcordFeatures,
94
- dynaSend: () => dynaSend,
95
- fetchChannel: () => fetchChannel,
96
- fetchGuild: () => fetchGuild,
97
- fetchMember: () => fetchMember,
98
- fetchMessage: () => fetchMessage,
99
- fetchRole: () => fetchRole,
100
- fetchUser: () => fetchUser,
101
- getDevMode: () => getDevMode,
102
- getFirstMentionId: () => getFirstMentionId,
103
- getMessageMention: () => getMessageMention,
104
- getPackageJson: () => getPackageJson,
105
- globalToolsConfig: () => globalToolsConfig,
106
- importModulesFromDir: () => importModulesFromDir,
107
- isMentionOrSnowflake: () => isMentionOrSnowflake,
108
- logger: () => logger,
109
- moduleImporters: () => moduleImporters,
110
- prefixCommandHandler: () => prefixCommandHandler,
111
- prompt: () => prompt,
112
- sendCommandErrorEmbed: () => sendCommandErrorEmbed,
113
- slashCommandHandler: () => slashCommandHandler,
114
- useClient: () => useClient,
115
- useReadyClient: () => useReadyClient,
116
- validateCommandPermissions: () => validateCommandPermissions
117
- });
118
19
  module.exports = __toCommonJS(index_exports);
119
-
120
- // src/modules/validators/permissions.validator.ts
121
- var import_discord = require("discord.js");
122
-
123
- // src/types/command.base.ts
124
- var CommandType = /* @__PURE__ */ ((CommandType2) => {
125
- CommandType2[CommandType2["Slash"] = 0] = "Slash";
126
- CommandType2[CommandType2["Prefix"] = 1] = "Prefix";
127
- CommandType2[CommandType2["Context"] = 2] = "Context";
128
- return CommandType2;
129
- })(CommandType || {});
130
- var MissingPermissionReason = /* @__PURE__ */ ((MissingPermissionReason2) => {
131
- MissingPermissionReason2[MissingPermissionReason2["User"] = 0] = "User";
132
- MissingPermissionReason2[MissingPermissionReason2["Bot"] = 1] = "Bot";
133
- MissingPermissionReason2[MissingPermissionReason2["Role"] = 2] = "Role";
134
- MissingPermissionReason2[MissingPermissionReason2["UserBlacklisted"] = 3] = "UserBlacklisted";
135
- MissingPermissionReason2[MissingPermissionReason2["RoleBlacklisted"] = 4] = "RoleBlacklisted";
136
- MissingPermissionReason2[MissingPermissionReason2["NotInGuild"] = 5] = "NotInGuild";
137
- MissingPermissionReason2[MissingPermissionReason2["NotGuildOwner"] = 6] = "NotGuildOwner";
138
- MissingPermissionReason2[MissingPermissionReason2["NotBotOwner"] = 7] = "NotBotOwner";
139
- MissingPermissionReason2[MissingPermissionReason2["NotBotStaff"] = 8] = "NotBotStaff";
140
- return MissingPermissionReason2;
141
- })(MissingPermissionReason || {});
142
- var RateLimitScope = /* @__PURE__ */ ((RateLimitScope2) => {
143
- RateLimitScope2[RateLimitScope2["User"] = 0] = "User";
144
- RateLimitScope2[RateLimitScope2["Guild"] = 1] = "Guild";
145
- RateLimitScope2[RateLimitScope2["Channel"] = 2] = "Channel";
146
- RateLimitScope2[RateLimitScope2["Global"] = 3] = "Global";
147
- return RateLimitScope2;
148
- })(RateLimitScope || {});
149
-
150
- // src/modules/validators/permissions.validator.ts
151
- function __existsAndTrue(value) {
152
- return value !== void 0 && value;
153
- }
154
- function validateCommandPermissions(permissions, client, user, command) {
155
- const inGuild = "guild" in user;
156
- const missingUserPermissions = [];
157
- const missingBotPermissions = [];
158
- const missingRoles = [];
159
- if (permissions.user?.length && inGuild) {
160
- for (const permission of permissions.user) {
161
- if (!user.permissions.has(permission)) {
162
- missingUserPermissions.push(permission);
163
- }
164
- }
165
- if (missingUserPermissions.length) {
166
- return { validated: false, failReason: 0 /* User */, missingUserPermissions };
167
- }
168
- }
169
- if (permissions.bot?.length && inGuild && user.guild.members.me) {
170
- for (const permission of permissions.bot) {
171
- if (!user.guild.members.me.permissions.has(permission)) {
172
- missingBotPermissions.push(permission);
173
- }
174
- }
175
- if (missingBotPermissions.length) {
176
- return { validated: false, failReason: 1 /* Bot */, missingBotPermissions };
177
- }
178
- }
179
- if (permissions.roles?.length && inGuild) {
180
- for (const role of permissions.roles) {
181
- if (!user.roles.cache.has(role)) {
182
- missingRoles.push(role);
183
- }
184
- }
185
- if (missingRoles.length) {
186
- return { validated: false, failReason: 2 /* Role */, missingRoles };
187
- }
188
- }
189
- if (permissions.userBlacklist?.length) {
190
- if (permissions.userBlacklist.includes(user.id)) {
191
- return { validated: false, failReason: 3 /* UserBlacklisted */, blacklistedUser: user.id };
192
- }
193
- }
194
- if (permissions.roleBlacklist?.length && inGuild) {
195
- if (user.roles.cache.some((role) => permissions.roleBlacklist.includes(role.id))) {
196
- return { validated: false, failReason: 4 /* RoleBlacklisted */, blacklistedRole: user.id };
197
- }
198
- }
199
- if (__existsAndTrue(permissions.guildOnly) && !inGuild) {
200
- return { validated: false, failReason: 5 /* NotInGuild */ };
201
- }
202
- if (__existsAndTrue(permissions.guildOwnerOnly) && inGuild && user.id !== user.guild.ownerId) {
203
- return { validated: false, failReason: 6 /* NotGuildOwner */ };
204
- }
205
- if (__existsAndTrue(permissions.botOwnerOnly) && user.id !== client.config.staff.ownerId) {
206
- return { validated: false, failReason: 7 /* NotBotOwner */ };
207
- }
208
- if (__existsAndTrue(permissions.botStaffOnly)) {
209
- if (client.config.staff.ownerId === user.id || client.config.staff.superUsers.includes(user.id)) {
210
- return { validated: true };
211
- }
212
- if (command instanceof import_discord.BaseInteraction && command.isCommand()) {
213
- let commandName = null;
214
- if (command.isChatInputCommand()) {
215
- const subcommand = command.options.getSubcommand();
216
- commandName = `${command.commandName}${subcommand ? ` ${subcommand}` : ""}`;
217
- } else {
218
- commandName = command.commandName;
219
- }
220
- if (client.config.staff.bypassers.some(
221
- (bypass) => bypass.commandName.toLowerCase() === commandName.toLowerCase() && bypass.userIds.includes(user.id)
222
- )) {
223
- return { validated: true };
224
- }
225
- }
226
- if (typeof command === "string" && client.config.staff.bypassers.some(
227
- (bypass) => bypass.commandName.toLowerCase() === command.toLowerCase() && bypass.userIds.includes(user.id)
228
- )) {
229
- return { validated: true };
230
- }
231
- if (inGuild) {
232
- for (const role of user.roles.cache.values()) {
233
- if (!user.roles.cache.has(role.id)) {
234
- missingRoles.push(role.id);
235
- }
236
- }
237
- if (missingRoles.length) {
238
- return { validated: false, failReason: 8 /* NotBotStaff */, missingRoles };
239
- } else {
240
- return { validated: true };
241
- }
242
- }
243
- return { validated: false, failReason: 8 /* NotBotStaff */ };
244
- }
245
- return { validated: true };
246
- }
247
-
248
- // src/utils/mergeUtils.ts
249
- function isPlainObject(value) {
250
- if (typeof value !== "object" || value === null) return false;
251
- if (Array.isArray(value)) return false;
252
- return Object.prototype.toString.call(value) === "[object Object]";
253
- }
254
- function deepMerge(target, ...sources) {
255
- for (const source of sources) {
256
- if (!source) continue;
257
- for (const key in source) {
258
- if (!Object.prototype.hasOwnProperty.call(source, key)) continue;
259
- const sourceValue = source[key];
260
- const targetValue = target[key];
261
- if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
262
- deepMerge(targetValue, sourceValue);
263
- } else if (sourceValue !== void 0) {
264
- target[key] = sourceValue;
265
- }
266
- }
267
- }
268
- return target;
269
- }
270
-
271
- // src/builders/baseCommand.builder.ts
272
- var import_node_crypto = require("crypto");
273
- var BaseCommandBuilder = class {
274
- uuid = (0, import_node_crypto.randomUUID)();
275
- commandType;
276
- name = "";
277
- /** Local command configuration and hooks */
278
- options;
279
- /** Internal state for rate limiting across different scopes */
280
- rlStores = {
281
- [3 /* Global */]: { executions: 0, timestamp: 0 },
282
- [0 /* User */]: /* @__PURE__ */ new Map(),
283
- [1 /* Guild */]: /* @__PURE__ */ new Map(),
284
- [2 /* Channel */]: /* @__PURE__ */ new Map()
285
- };
286
- /** Mapping of CommandTypes to their respective config keys in the Vimcord client */
287
- typeConfigMapping = {
288
- [0 /* Slash */]: "slashCommands",
289
- [1 /* Prefix */]: "prefixCommands",
290
- [2 /* Context */]: "contextCommands"
291
- };
292
- constructor(type, options = {}) {
293
- this.commandType = type;
294
- this.options = { enabled: true, ...options };
295
- }
296
- setCommandName(name) {
297
- this.name = name;
298
- }
299
- validateBaseConfig() {
300
- if (this.options.rateLimit) {
301
- const { max, interval } = this.options.rateLimit;
302
- if (max <= 0 || interval <= 0) {
303
- throw new Error(`[Vimcord:${this.constructor.name}] Rate limit values must be positive.`);
304
- }
305
- }
306
- }
307
- /**
308
- * Resolves the final configuration by merging layers:
309
- * Client Defaults < Client Type-Specific < Local Command Options
310
- */
311
- resolveConfig(client) {
312
- const typeKey = this.typeConfigMapping[this.commandType];
313
- const typeSpecificGlobals = client.config?.[typeKey] || {};
314
- return deepMerge({}, typeSpecificGlobals, this.options);
315
- }
316
- /**
317
- * Executes the command lifecycle.
318
- * Merges global client config with local command options at runtime.
319
- */
320
- async run(client, ...args) {
321
- const config = this.resolveConfig(client);
322
- const ctx = this.extractContext(args);
323
- let canceled = false;
324
- const cancel = () => canceled = true;
325
- try {
326
- if (!config.enabled) {
327
- return await config.onUsedWhenDisabled?.(...args);
328
- }
329
- if (this.isRateLimited(config, ctx)) {
330
- return await config.onRateLimit?.(...args);
331
- }
332
- const perms = this.checkPermissions(client, ctx.member || ctx.user, args[1]);
333
- if (!perms.validated) {
334
- return await config.onMissingPermissions?.(perms, ...args);
335
- }
336
- if (!await this.checkConditions(config, ...args)) {
337
- return await config.onConditionsNotMet?.(...args);
338
- }
339
- if (config.beforeExecute) {
340
- await config.beforeExecute?.({ cancel, name: this.name }, ...args);
341
- if (canceled) return;
342
- }
343
- if (config.logExecution !== false) {
344
- const location = ctx.guild ? `${ctx.guild.name} (${ctx.guild.id})` : "Direct Messages";
345
- client.logger.commandExecuted(this.name || "Unknown", ctx.user.username, location);
346
- }
347
- const result = await config.execute?.(...args);
348
- await config.afterExecute?.(result, ...args);
349
- } catch (error) {
350
- await this.handleError(error, config, ...args);
351
- }
352
- }
353
- /**
354
- * Internal logic to determine if a command execution should be throttled.
355
- * @param config The merged configuration to use for limits.
356
- * @param ctx Extracted Discord context (User, Guild, Channel).
357
- */
358
- isRateLimited(config, ctx) {
359
- if (!config.rateLimit) return false;
360
- const { scope, interval, max } = config.rateLimit;
361
- const now = Date.now();
362
- const key = this.getScopeKey(scope, ctx);
363
- if (scope !== 3 /* Global */ && !key) return false;
364
- let data;
365
- if (scope === 3 /* Global */) {
366
- data = this.rlStores[3 /* Global */];
367
- } else {
368
- const store = this.rlStores[scope];
369
- data = store.get(key) ?? { executions: 0, timestamp: now };
370
- store.set(key, data);
371
- }
372
- if (now - data.timestamp > interval) {
373
- data.executions = 0;
374
- data.timestamp = now;
375
- }
376
- if (data.executions >= max) return true;
377
- data.executions++;
378
- return false;
379
- }
380
- /**
381
- * Validates if the user has required permissions.
382
- */
383
- checkPermissions(client, user, target) {
384
- if (!this.options.permissions) return { validated: true };
385
- return validateCommandPermissions(this.options.permissions, client, user, target);
386
- }
387
- /**
388
- * Evaluates all custom conditions defined for the command.
389
- */
390
- async checkConditions(config, ...args) {
391
- if (!config.conditions?.length) return true;
392
- const results = await Promise.all(config.conditions.map((c) => c(...args)));
393
- return results.every(Boolean);
394
- }
395
- /**
396
- * Normalizes the trigger arguments into a standard context object.
397
- */
398
- extractContext(args) {
399
- const event = args[1];
400
- return {
401
- user: event.user || event.author,
402
- member: event.member,
403
- guild: event.guild,
404
- channel: event.channel
405
- };
406
- }
407
- /**
408
- * Resolves the storage key based on the RateLimit scope.
409
- */
410
- getScopeKey(scope, ctx) {
411
- switch (scope) {
412
- case 0 /* User */:
413
- return ctx.user.id;
414
- case 1 /* Guild */:
415
- return ctx.guild?.id ?? null;
416
- case 2 /* Channel */:
417
- return ctx.channel?.id ?? null;
418
- default:
419
- return null;
420
- }
421
- }
422
- /**
423
- * Handles command errors by checking local handlers before falling back to global handlers.
424
- */
425
- async handleError(err, config, ...args) {
426
- if (config.onError) return config.onError(err, ...args);
427
- throw err;
428
- }
429
- /** Toggle command availability */
430
- setEnabled(enabled) {
431
- this.options.enabled = enabled;
432
- return this;
433
- }
434
- /** Merge new permission requirements into the existing ones */
435
- setPermissions(perms) {
436
- this.options.permissions = deepMerge(this.options.permissions || {}, perms);
437
- return this;
438
- }
439
- /** Set the custom conditions that must be met for this command to execute */
440
- setConditions(conditions) {
441
- this.options.conditions = conditions;
442
- return this;
443
- }
444
- /** Set the primary command execution logic */
445
- setExecute(fn) {
446
- this.options.execute = fn;
447
- return this;
448
- }
449
- /** Set the command metadata configuration */
450
- setMetadata(metadata) {
451
- this.options.metadata = deepMerge(this.options.metadata || {}, metadata);
452
- return this;
453
- }
454
- /** Set the rate limiting options for this command */
455
- setRateLimit(options) {
456
- this.options.rateLimit = options;
457
- this.validateBaseConfig();
458
- return this;
459
- }
460
- };
461
-
462
- // src/builders/contextCommand.builder.ts
463
- var import_discord2 = require("discord.js");
464
- var ContextCommandBuilder = class extends BaseCommandBuilder {
465
- builder;
466
- constructor(config) {
467
- super(2 /* Context */, config);
468
- this.setBuilder(config.builder);
469
- const originalExecute = this.options.execute;
470
- this.options.execute = async (client, interaction) => {
471
- return await this.handleExecution(client, interaction, originalExecute);
472
- };
473
- }
474
- async handleExecution(client, interaction, originalExecute) {
475
- const config = this.resolveConfig(client);
476
- if (config.deferReply && !interaction.replied && !interaction.deferred) {
477
- await interaction.deferReply(typeof config.deferReply === "object" ? config.deferReply : void 0);
478
- }
479
- return await originalExecute?.(client, interaction);
480
- }
481
- validateBuilder() {
482
- if (!this.builder.name) throw new Error(`[Vimcord] ContextCommandBuilder: Command name is required.`);
483
- this.builder.toJSON();
484
- }
485
- // --- Specialized Fluent API ---
486
- setBuilder(builder) {
487
- this.builder = typeof builder === "function" ? builder(new import_discord2.ContextMenuCommandBuilder()) : builder;
488
- this.validateBuilder();
489
- this.setCommandName(this.builder.name);
490
- return this;
491
- }
492
- setExecute(fn) {
493
- const originalExecute = fn;
494
- this.options.execute = async (client, interaction) => {
495
- return await this.handleExecution(client, interaction, originalExecute);
496
- };
497
- return this;
498
- }
499
- toConfig() {
500
- return { ...this.options, builder: this.builder };
501
- }
502
- };
503
-
504
- // src/builders/event.builder.ts
505
- var import_node_crypto2 = require("crypto");
506
-
507
- // src/tools/Logger.ts
508
- var import_chalk = __toESM(require("chalk"));
509
- var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
510
- LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
511
- LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
512
- LogLevel2[LogLevel2["SUCCESS"] = 2] = "SUCCESS";
513
- LogLevel2[LogLevel2["WARN"] = 3] = "WARN";
514
- LogLevel2[LogLevel2["ERROR"] = 4] = "ERROR";
515
- return LogLevel2;
516
- })(LogLevel || {});
517
- var LOGGER_COLORS = {
518
- primary: "#5865F2",
519
- success: "#57F287",
520
- warn: "#FEE75C",
521
- danger: "#ED4245",
522
- muted: "#747F8D",
523
- text: "#FFFFFF"
524
- };
525
- var Logger = class {
526
- logPrefixEmoji;
527
- logPrefix;
528
- minLevel;
529
- showTimestamp;
530
- colorScheme;
531
- constructor(options) {
532
- const { prefixEmoji = null, prefix = null, minLevel = 0 /* DEBUG */, showTimestamp = true } = options || {};
533
- this.logPrefixEmoji = prefixEmoji;
534
- this.logPrefix = prefix;
535
- this.minLevel = minLevel;
536
- this.showTimestamp = showTimestamp;
537
- this.colorScheme = {
538
- ...LOGGER_COLORS,
539
- ...options?.colors
540
- };
541
- }
542
- formatTimestamp() {
543
- if (!this.showTimestamp) return "";
544
- const now = /* @__PURE__ */ new Date();
545
- const time = now.toLocaleTimeString("en-US", {
546
- hour12: false,
547
- hour: "2-digit",
548
- minute: "2-digit",
549
- second: "2-digit"
550
- });
551
- return import_chalk.default.hex(this.colorScheme.muted)(`[${time}]`);
552
- }
553
- formatPrefix() {
554
- if (!this.logPrefix) return "";
555
- return import_chalk.default.bold.hex(this.colorScheme.primary)(
556
- `${this.logPrefixEmoji ? `${this.logPrefixEmoji} ` : ""}${this.logPrefix}`
557
- );
558
- }
559
- shouldLog(level) {
560
- return level >= this.minLevel;
561
- }
562
- get prefixEmoji() {
563
- return this.logPrefixEmoji;
564
- }
565
- get prefix() {
566
- return this.logPrefix;
567
- }
568
- get colors() {
569
- return this.colorScheme;
570
- }
571
- extend(extras) {
572
- for (const [key, fn] of Object.entries(extras)) {
573
- if (typeof fn === "function") {
574
- this[key] = function(...args) {
575
- return fn.call(this, ...args);
576
- };
577
- }
578
- }
579
- return this;
580
- }
581
- setPrefix(prefix) {
582
- this.logPrefix = prefix;
583
- return this;
584
- }
585
- setPrefixEmoji(prefixEmoji) {
586
- this.logPrefixEmoji = prefixEmoji;
587
- return this;
588
- }
589
- setMinLevel(minLevel) {
590
- this.minLevel = minLevel;
591
- return this;
592
- }
593
- setShowTimestamp(show) {
594
- this.showTimestamp = show;
595
- return this;
596
- }
597
- setColors(colors) {
598
- this.colorScheme = {
599
- ...LOGGER_COLORS,
600
- ...colors
601
- };
602
- return this;
603
- }
604
- log(message, ...args) {
605
- console.log(this.formatTimestamp(), this.formatPrefix(), message, ...args);
606
- }
607
- debug(message, ...args) {
608
- if (!this.shouldLog(0 /* DEBUG */)) return;
609
- console.log(
610
- this.formatTimestamp(),
611
- this.formatPrefix(),
612
- import_chalk.default.hex(this.colorScheme.muted)("DEBUG"),
613
- import_chalk.default.dim(message),
614
- ...args
615
- );
616
- }
617
- info(message, ...args) {
618
- if (!this.shouldLog(1 /* INFO */)) return;
619
- console.log(this.formatTimestamp(), this.formatPrefix(), import_chalk.default.hex("#87CEEB")("INFO"), message, ...args);
620
- }
621
- success(message, ...args) {
622
- if (!this.shouldLog(2 /* SUCCESS */)) return;
623
- console.log(
624
- this.formatTimestamp(),
625
- this.formatPrefix(),
626
- import_chalk.default.bold.hex(this.colorScheme.success)("\u2713 SUCCESS"),
627
- import_chalk.default.hex(this.colorScheme.success)(message),
628
- ...args
629
- );
630
- }
631
- warn(message, ...args) {
632
- if (!this.shouldLog(3 /* WARN */)) return;
633
- console.warn(
634
- this.formatTimestamp(),
635
- this.formatPrefix(),
636
- import_chalk.default.bold.hex(this.colorScheme.warn)("\u26A0 WARN"),
637
- import_chalk.default.hex(this.colorScheme.warn)(message),
638
- ...args
639
- );
640
- }
641
- error(message, error, ...args) {
642
- if (!this.shouldLog(4 /* ERROR */)) return;
643
- console.error(
644
- this.formatTimestamp(),
645
- this.formatPrefix(),
646
- import_chalk.default.bold.hex(this.colorScheme.danger)("\u2715 ERROR"),
647
- import_chalk.default.hex(this.colorScheme.danger)(message),
648
- ...args
649
- );
650
- if (error && error.stack) {
651
- console.error(import_chalk.default.dim(error.stack));
652
- }
653
- }
654
- loader(message) {
655
- const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
656
- let i = 0;
657
- const interval = setInterval(() => {
658
- process.stdout.write(
659
- `\r${this.formatTimestamp()} ${this.formatPrefix()} ${import_chalk.default.hex(this.colorScheme.warn)(frames[i])} ${message}`
660
- );
661
- i = (i + 1) % frames.length;
662
- }, 100);
663
- return (newMessage) => {
664
- clearInterval(interval);
665
- process.stdout.write(
666
- `\r${this.formatTimestamp()} ${this.formatPrefix()} ${import_chalk.default.hex(this.colorScheme.success)("\u2713")} ${newMessage || message}
667
- `
668
- );
669
- };
670
- }
671
- table(title, data) {
672
- console.log(this.formatTimestamp(), this.formatPrefix(), import_chalk.default.bold(title));
673
- Object.entries(data).forEach(([key, value]) => {
674
- const formattedKey = import_chalk.default.hex(this.colorScheme.warn)(` ${key}`);
675
- const formattedValue = import_chalk.default.hex(this.colorScheme.muted)(value);
676
- console.log(`${formattedKey.padEnd(25)} ${formattedValue}`);
677
- });
678
- }
679
- section(title) {
680
- const line = "\u2500".repeat(Math.max(30, title.length + 4));
681
- console.log(import_chalk.default.hex(this.colorScheme.muted)(`
682
- \u250C\u2500${line}\u2500\u2510`));
683
- console.log(
684
- import_chalk.default.hex(this.colorScheme.muted)("\u2502 ") + import_chalk.default.bold.hex(this.colorScheme.text)(title.padEnd(line.length)) + import_chalk.default.hex(this.colorScheme.muted)(" \u2502")
685
- );
686
- console.log(import_chalk.default.hex(this.colorScheme.muted)(`\u2514\u2500${line}\u2500\u2518`));
687
- }
688
- };
689
- var logger = new Logger();
690
-
691
- // src/builders/event.builder.ts
692
- var EventBuilder = class _EventBuilder {
693
- uuid = (0, import_node_crypto2.randomUUID)();
694
- event;
695
- name = this.uuid;
696
- enabled;
697
- once;
698
- priority;
699
- conditions;
700
- metadata;
701
- deployment;
702
- rateLimit;
703
- beforeExecute;
704
- execute;
705
- afterExecute;
706
- onError;
707
- rateLimitData = { executions: 0, timestamp: 0 };
708
- static create(event, name) {
709
- return new _EventBuilder({ event, name });
710
- }
711
- constructor(config) {
712
- this.event = config.event;
713
- this.name = config.name || this.name;
714
- this.enabled = config.enabled ?? true;
715
- this.once = config.once ?? false;
716
- this.priority = config.priority ?? 0;
717
- this.conditions = config.conditions;
718
- this.metadata = config.metadata;
719
- this.deployment = config.deployment;
720
- this.rateLimit = config.rateLimit;
721
- this.beforeExecute = config.beforeExecute;
722
- this.execute = config.execute;
723
- this.afterExecute = config.afterExecute;
724
- this.onError = config.onError;
725
- this.validate();
726
- }
727
- validate() {
728
- if (!this.event) {
729
- throw new Error("Event name is required");
730
- }
731
- if (this.priority !== void 0 && this.priority < 0) {
732
- throw new Error("Priority must be non-negative");
733
- }
734
- if (this.rateLimit) {
735
- if (this.rateLimit.max <= 0) {
736
- throw new Error("Rate limit max must be greater than 0");
737
- }
738
- if (this.rateLimit.interval <= 0) {
739
- throw new Error("Rate limit interval must be greater than 0");
740
- }
741
- }
742
- if (!this.execute) {
743
- throw new Error("Execute function is required");
744
- }
745
- }
746
- clone() {
747
- return new _EventBuilder(this.toConfig());
748
- }
749
- toConfig() {
750
- return {
751
- event: this.event,
752
- name: this.name,
753
- enabled: this.enabled,
754
- once: this.once,
755
- priority: this.priority,
756
- conditions: this.conditions,
757
- metadata: this.metadata,
758
- deployment: this.deployment,
759
- rateLimit: this.rateLimit,
760
- beforeExecute: this.beforeExecute,
761
- execute: this.execute,
762
- afterExecute: this.afterExecute,
763
- onError: this.onError
764
- };
765
- }
766
- setEnabled(enabled) {
767
- this.enabled = enabled;
768
- return this;
769
- }
770
- setExecute(execute) {
771
- this.execute = execute;
772
- return this;
773
- }
774
- isRateLimited(updateExecutions = true) {
775
- if (!this.rateLimit) return false;
776
- const now = Date.now();
777
- if (now - this.rateLimitData.timestamp >= this.rateLimit.interval) {
778
- this.rateLimitData.executions = 0;
779
- this.rateLimitData.timestamp = now;
780
- }
781
- if (updateExecutions) {
782
- this.rateLimitData.executions++;
783
- }
784
- return this.rateLimitData.executions >= this.rateLimit.max;
785
- }
786
- async checkConditions(...args) {
787
- if (!this.conditions?.length) return true;
788
- const results = await Promise.all(this.conditions.map((condition) => condition(...args)));
789
- return results.every(Boolean);
790
- }
791
- async executeEvent(...args) {
792
- let canceled = false;
793
- const cancel = () => canceled = true;
794
- try {
795
- if (!this.enabled) {
796
- return;
797
- }
798
- if (this.isRateLimited()) {
799
- logger.warn(`Event '${this.name}' (${this.event}) is rate limited`);
800
- if (this.rateLimit?.onRateLimit) {
801
- return await this.rateLimit.onRateLimit(...args);
802
- }
803
- return;
804
- }
805
- if (!await this.checkConditions(...args)) {
806
- return;
807
- }
808
- if (this.beforeExecute) {
809
- await this.beforeExecute({ cancel }, ...args);
810
- if (canceled) return;
811
- }
812
- const result = await this.execute?.(...args);
813
- if (this.afterExecute) {
814
- await this.afterExecute(result, ...args);
815
- }
816
- return result;
817
- } catch (err) {
818
- if (this.onError) {
819
- return await this.onError(err, ...args);
820
- }
821
- logger.error(`Event execution error '${this.name}' (${this.event}):`, err);
822
- throw err;
823
- }
824
- }
825
- };
826
-
827
- // src/builders/prefixCommand.builder.ts
828
- var PrefixCommandBuilder = class extends BaseCommandBuilder {
829
- constructor(options) {
830
- super(1 /* Prefix */, options);
831
- this.options = options;
832
- this.setCommandName(options.name);
833
- const originalExecute = this.options.execute;
834
- this.options.execute = async (client, message) => {
835
- return await this.handleExecution(client, message, originalExecute);
836
- };
837
- this.validatePrefixConfig();
838
- }
839
- /**
840
- * Specialized execution logic for Prefix Commands.
841
- */
842
- async handleExecution(client, message, originalExecute) {
843
- return await originalExecute?.(client, message);
844
- }
845
- validatePrefixConfig() {
846
- if (!this.options.name) {
847
- throw new Error(`[Vimcord] PrefixCommandBuilder: Command name is required.`);
848
- }
849
- }
850
- // --- Overrides ---
851
- /**
852
- * Override setExecute to ensure handleExecution remains the entry point.
853
- */
854
- setExecute(fn) {
855
- const originalExecute = fn;
856
- this.options.execute = async (client, message) => {
857
- return await this.handleExecution(client, message, originalExecute);
858
- };
859
- return this;
860
- }
861
- /**
862
- * Converts the current builder state back into a config object.
863
- */
864
- toConfig() {
865
- return {
866
- ...this.options
867
- };
868
- }
869
- };
870
-
871
- // src/builders/slashCommand.builder.ts
872
- var import_discord3 = require("discord.js");
873
- var SlashCommandBuilder = class extends BaseCommandBuilder {
874
- builder;
875
- routes = /* @__PURE__ */ new Map();
876
- constructor(config) {
877
- super(0 /* Slash */, config);
878
- this.setBuilder(config.builder);
879
- if (config.routes) this.addRoutes(...config.routes);
880
- const originalExecute = this.options.execute;
881
- this.options.execute = async (client, interaction) => {
882
- return await this.handleExecution(client, interaction, originalExecute);
883
- };
884
- }
885
- async handleExecution(client, interaction, originalExecute) {
886
- const config = this.resolveConfig(client);
887
- if (config.deferReply && !interaction.replied && !interaction.deferred) {
888
- await interaction.deferReply(typeof config.deferReply === "object" ? config.deferReply : void 0);
889
- }
890
- const subCommand = interaction.options.getSubcommand(false);
891
- if (subCommand) {
892
- const handler = this.routes.get(subCommand.toLowerCase());
893
- if (handler) return await handler(client, interaction);
894
- if (config.onUnknownRouteHandler) {
895
- return await config.onUnknownRouteHandler(client, interaction);
896
- } else {
897
- return await interaction.reply({ content: `Unknown subcommand: ${subCommand}`, flags: "Ephemeral" });
898
- }
899
- }
900
- return await originalExecute?.(client, interaction);
901
- }
902
- validateBuilder() {
903
- if (!this.builder.name) throw new Error(`[Vimcord] SlashCommandBuilder: Command name is required.`);
904
- if (!this.builder.description) throw new Error(`[Vimcord] SlashCommandBuilder: Command description is required.`);
905
- this.builder.toJSON();
906
- }
907
- // --- Specialized Fluent API ---
908
- setBuilder(builder) {
909
- this.builder = typeof builder === "function" ? builder(new import_discord3.SlashCommandBuilder()) : builder;
910
- this.validateBuilder();
911
- this.setCommandName(this.builder.name);
912
- return this;
913
- }
914
- addRoutes(...routes) {
915
- if (!this.options.routes) this.options.routes = [];
916
- for (const route of routes) {
917
- const name = route.name.toLowerCase();
918
- this.routes.set(name, route.handler);
919
- const existingIndex = this.options.routes.findIndex((r) => r.name.toLowerCase() === name);
920
- if (existingIndex > -1) this.options.routes[existingIndex] = route;
921
- else this.options.routes.push(route);
922
- }
923
- return this;
924
- }
925
- setExecute(fn) {
926
- const originalExecute = fn;
927
- this.options.execute = async (client, interaction) => {
928
- return await this.handleExecution(client, interaction, originalExecute);
929
- };
930
- return this;
931
- }
932
- toConfig() {
933
- return {
934
- ...this.options,
935
- builder: this.builder,
936
- routes: Array.from(this.routes.entries()).map(([name, handler]) => ({ name, handler }))
937
- };
938
- }
939
- };
940
-
941
- // src/modules/builtins/contextCommand.builtin.ts
942
- var contextCommandHandler = new EventBuilder({
943
- event: "interactionCreate",
944
- name: "ContextCommandHandler",
945
- async execute(client, interaction) {
946
- if (!interaction.isContextMenuCommand()) return;
947
- const command = client.commands.context.get(interaction.commandName);
948
- if (!command) {
949
- const content = `**${interaction.commandName}** is not a registered context command.`;
950
- if (interaction.replied || interaction.deferred) {
951
- return interaction.followUp({ content, flags: "Ephemeral" });
952
- }
953
- return interaction.reply({ content, flags: "Ephemeral" });
954
- }
955
- try {
956
- return await command.run(client, client, interaction);
957
- } catch (err) {
958
- await client.error.handleCommandError(err, interaction.guild, interaction);
959
- }
960
- }
961
- });
962
-
963
- // src/modules/builtins/prefixCommand.builtin.ts
964
- var import_discord4 = require("discord.js");
965
- var prefixCommandHandler = new EventBuilder({
966
- event: "messageCreate",
967
- name: "PrefixCommandHandler",
968
- async execute(client, message) {
969
- if (message.author.bot || !message.guild) return;
970
- const config = client.config.prefixCommands;
971
- let activePrefix = config.defaultPrefix;
972
- if (config.guildPrefixResolver) {
973
- try {
974
- const customPrefix = await config.guildPrefixResolver(client, message.guild.id);
975
- if (customPrefix) activePrefix = customPrefix;
976
- } catch (err) {
977
- client.logger.error(`Error in guildPrefixResolver for guild ${message.guild.id}:`, err);
978
- }
979
- }
980
- let prefixUsed;
981
- if (message.content.startsWith(activePrefix)) {
982
- prefixUsed = activePrefix;
983
- } else if (config.allowMentionAsPrefix) {
984
- const mention = (0, import_discord4.userMention)(client.user.id);
985
- if (message.content.startsWith(mention)) {
986
- prefixUsed = message.content.startsWith(`${mention} `) ? `${mention} ` : mention;
987
- }
988
- }
989
- if (!prefixUsed) return;
990
- const contentWithoutPrefix = message.content.slice(prefixUsed.length).trim();
991
- const args = contentWithoutPrefix.split(/\s+/);
992
- const trigger = args.shift();
993
- if (!trigger) return;
994
- const command = client.commands.prefix.get(trigger);
995
- if (!command) return;
996
- message.content = args.join(" ");
997
- try {
998
- return await command.run(client, client, message);
999
- } catch (err) {
1000
- await client.error.handleCommandError(err, message.guild, message);
1001
- }
1002
- }
1003
- });
1004
-
1005
- // src/modules/builtins/slashCommand.builtin.ts
1006
- var slashCommandHandler = new EventBuilder({
1007
- event: "interactionCreate",
1008
- name: "SlashCommandHandler",
1009
- async execute(client, interaction) {
1010
- if (!interaction.isChatInputCommand()) return;
1011
- const command = client.commands.slash.get(interaction.commandName);
1012
- if (!command) {
1013
- const content = `**/\`${interaction.commandName}\`** is not a registered command.`;
1014
- if (interaction.replied || interaction.deferred) {
1015
- return interaction.followUp({ content, flags: "Ephemeral" });
1016
- }
1017
- return interaction.reply({ content, flags: "Ephemeral" });
1018
- }
1019
- try {
1020
- return await command.run(client, client, interaction);
1021
- } catch (err) {
1022
- await client.error.handleCommandError(err, interaction.guild, interaction);
1023
- }
1024
- }
1025
- });
1026
-
1027
- // src/client/vimcord.cli.ts
1028
- var import_node_readline = require("readline");
1029
- var import_qznt = require("qznt");
1030
- var VimcordCLI = class _VimcordCLI {
1031
- static mode = "off";
1032
- static setMode(mode) {
1033
- if (_VimcordCLI.mode === mode) return;
1034
- _VimcordCLI.mode = mode;
1035
- if (mode === "on") {
1036
- CLI.logger.log(`~ Type ${CLI.options.prefix}help to view available commands`);
1037
- } else {
1038
- CLI.logger.log(`~ Updated mode to "${mode}"`);
1039
- }
1040
- }
1041
- rl;
1042
- options;
1043
- commands = /* @__PURE__ */ new Map();
1044
- logger = new Logger({ prefixEmoji: "\u{1F680}", prefix: "CLI", showTimestamp: false });
1045
- constructor(options) {
1046
- this.options = options;
1047
- this.rl = (0, import_node_readline.createInterface)({
1048
- input: process.stdin,
1049
- output: process.stdout,
1050
- terminal: false
1051
- });
1052
- this.rl.on("line", (line) => {
1053
- if (_VimcordCLI.mode !== "on") return;
1054
- const { isCommand, commandName, content, args } = this.parseLine(line);
1055
- if (!isCommand) return;
1056
- const command = this.commands.get(commandName);
1057
- if (!command) {
1058
- const nearestMatches = Array.from(this.commands.keys()).filter(
1059
- (cmd) => cmd.toLowerCase().includes(commandName.toLowerCase())
1060
- );
1061
- return this.logger.error(
1062
- `Unknown command '${commandName}'${nearestMatches.length ? `. Did you mean ${nearestMatches.length > 1 ? `[${nearestMatches.map((m) => `'${this.options.prefix}${m}'`).join(", ")}]` : `'${this.options.prefix}${nearestMatches[0]}'`}?` : ""}`
1063
- );
1064
- }
1065
- command.fn(args, content);
1066
- });
1067
- }
1068
- parseLine(line) {
1069
- if (line.startsWith(this.options.prefix)) {
1070
- line = line.slice(this.options.prefix.length);
1071
- } else {
1072
- return { isCommand: false };
1073
- }
1074
- const args = line.split(" ").map((s) => s.trim());
1075
- const commandName = args.shift();
1076
- return { isCommand: true, commandName, content: args.join(" "), args };
1077
- }
1078
- getClientInstance(line) {
1079
- const clientIndex = import_qznt.$.str.getFlag(line, "--client", 1) || import_qznt.$.str.getFlag(line, "-c", 1);
1080
- if (clientIndex) {
1081
- const idx = Number(clientIndex);
1082
- if (isNaN(idx)) {
1083
- CLI.logger.error(`'${clientIndex}' is not a valid number`);
1084
- return void 0;
1085
- }
1086
- const client = useClient(idx);
1087
- if (!client) {
1088
- CLI.logger.error("Client instance not found");
1089
- return void 0;
1090
- }
1091
- return client;
1092
- } else {
1093
- const client = useClient(0);
1094
- if (!client) {
1095
- CLI.logger.error("Client instance not found");
1096
- return void 0;
1097
- }
1098
- return client;
1099
- }
1100
- }
1101
- addCommand(commandName, description, fn) {
1102
- this.commands.set(commandName, { description, fn });
1103
- }
1104
- removeCommand(commandName) {
1105
- if (!this.commands.has(commandName)) return false;
1106
- this.commands.delete(commandName);
1107
- return true;
1108
- }
1109
- };
1110
- var CLI = new VimcordCLI({ prefix: "/" });
1111
- CLI.addCommand("help", "View information about a command, or the available CLI options", (args) => {
1112
- const prefix = CLI.options.prefix;
1113
- const helpList = {};
1114
- for (const cmd of CLI.commands.entries()) {
1115
- const commandName = cmd[0];
1116
- const commandDescription = cmd[1].description;
1117
- helpList[`${prefix}${commandName}`] = `~ ${commandDescription}`;
1118
- }
1119
- CLI.logger.table("(help)", helpList);
1120
- });
1121
- CLI.addCommand("register", "Register app commands (slash & context) globally, or per guild", async (args, content) => {
1122
- const client = CLI.getClientInstance(content);
1123
- if (!client) return;
1124
- const mode = args[0]?.toLowerCase() || "";
1125
- if (!["guild", "global"].includes(mode)) {
1126
- return CLI.logger.error(`'${mode}' is not a valid option. Your options are [guild|global]`);
1127
- }
1128
- let guildIds = (import_qznt.$.str.getFlag(content, "--guilds", 1) || import_qznt.$.str.getFlag(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
1129
- if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
1130
- switch (mode) {
1131
- case "guild":
1132
- CLI.logger.info("Registering guild commands...");
1133
- await client.commands.registerGuild({ guilds: guildIds });
1134
- break;
1135
- case "global":
1136
- CLI.logger.info("Registering global commands...");
1137
- await client.commands.registerGlobal();
1138
- break;
1139
- }
1140
- });
1141
- CLI.addCommand("unregister", "Unregister app commands globally, or per guild", async (args, content) => {
1142
- const client = CLI.getClientInstance(content);
1143
- if (!client) return;
1144
- const mode = args[0]?.toLowerCase() || "";
1145
- if (!["guild", "global"].includes(mode)) {
1146
- return CLI.logger.error(`'${mode}' is not a valid option. Your options are [guild|global]`);
1147
- }
1148
- let guildIds = (import_qznt.$.str.getFlag(content, "--guilds", 1) || import_qznt.$.str.getFlag(content, "-g", 1) || "").replaceAll(/["']/g, "").split(" ").filter(Boolean).map((s) => s.replaceAll(",", "").trim());
1149
- if (!guildIds.length) guildIds = client.guilds.cache.map((g) => g.id);
1150
- switch (mode) {
1151
- case "guild":
1152
- CLI.logger.info("Unregistering guild commands...");
1153
- await client.commands.unregisterGuild({ guilds: guildIds });
1154
- break;
1155
- case "global":
1156
- CLI.logger.info("Unregistering global commands...");
1157
- await client.commands.unregisterGlobal();
1158
- break;
1159
- }
1160
- });
1161
- CLI.addCommand("stats", "View statistics about a client instance", (args, content) => {
1162
- const client = CLI.getClientInstance(content);
1163
- if (!client) return;
1164
- CLI.logger.table(`(stats) ~ ${client.config.app.name}`, {
1165
- "Guilds:": import_qznt.$.format.number(client.guilds.cache.size),
1166
- "Ping:": `${client.ws.ping || 0}ms`,
1167
- "Uptime:": `${import_qznt.$.math.secs(client.uptime || 0)}s`,
1168
- "Process Uptime:": `${Math.floor(process.uptime())}s`,
1169
- "Memory Usage:": `${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)} MB`
1170
- });
1171
- });
1172
- CLI.addCommand("cmds", "List the loaded commands", async (args, content) => {
1173
- const client = CLI.getClientInstance(content);
1174
- if (!client) return;
1175
- const mode = (args[0] || "slash").toLowerCase();
1176
- switch (mode) {
1177
- case "slash": {
1178
- const commands = Array.from(client.commands.slash.commands.values());
1179
- commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
1180
- const tableData = {};
1181
- for (const cmd of commands) {
1182
- tableData[`/${cmd.builder.name}`] = `~ ${cmd.builder.description || "No description"}`;
1183
- }
1184
- return CLI.logger.table(`(cmds) ~ slash (${import_qznt.$.format.number(commands.length)})`, tableData);
1185
- }
1186
- case "prefix": {
1187
- const commands = Array.from(client.commands.prefix.commands.values());
1188
- commands.sort((a, b) => {
1189
- const nameA = a.toConfig().name;
1190
- const nameB = b.toConfig().name;
1191
- return nameA.localeCompare(nameB);
1192
- });
1193
- const tableData = {};
1194
- const defaultPrefix = client.config.prefixCommands.defaultPrefix;
1195
- for (const cmd of commands) {
1196
- const config = cmd.toConfig();
1197
- const aliasIndicator = config.aliases?.length ? ` [${config.aliases.join(", ")}]` : "";
1198
- tableData[`${defaultPrefix}${config.name}${aliasIndicator}`] = `~ ${config.description || "No description"}`;
1199
- }
1200
- return CLI.logger.table(`(cmds) ~ prefix (${import_qznt.$.format.number(commands.length)})`, tableData);
1201
- }
1202
- case "ctx": {
1203
- const commands = Array.from(client.commands.context.commands.values());
1204
- commands.sort((a, b) => a.builder.name.localeCompare(b.builder.name));
1205
- const tableData = {};
1206
- for (const cmd of commands) {
1207
- const type = cmd.builder.type === 2 ? "User" : "Msg";
1208
- tableData[`[${type}] ${cmd.builder.name}`] = "";
1209
- }
1210
- return CLI.logger.table(`(cmds) ~ ctx (${import_qznt.$.format.number(commands.length)})`, tableData);
1211
- }
1212
- default:
1213
- return CLI.logger.error(`'${mode}' is not a valid option. Valid options: [slash|prefix|ctx]`);
1214
- }
1215
- });
1216
-
1217
- // src/tools/BetterEmbed.ts
1218
- var import_discord6 = require("discord.js");
1219
-
1220
- // src/utils/configFactory.ts
1221
- function createConfigFactory(defaultConfig6, validate) {
1222
- return (options = {}, existing) => {
1223
- const base = existing ? { ...existing } : { ...defaultConfig6 };
1224
- const result = deepMerge(base, options);
1225
- validate?.(result);
1226
- return result;
1227
- };
1228
- }
1229
-
1230
- // src/configs/tools.config.ts
1231
- var globalToolsConfig = {
1232
- devMode: false,
1233
- embedColor: [],
1234
- embedColorDev: [],
1235
- timeouts: {
1236
- collectorTimeout: 6e4,
1237
- collectorIdle: 6e4,
1238
- pagination: 6e4,
1239
- prompt: 3e4,
1240
- modalSubmit: 6e4
1241
- },
1242
- collector: {
1243
- notAParticipantMessage: "You are not allowed to use this.",
1244
- userLockMessage: "Please wait until your current action is finished.",
1245
- notAParticipantWarningCooldown: 5e3
1246
- },
1247
- paginator: {
1248
- notAParticipantMessage: "You are not allowed to use this.",
1249
- jumpableThreshold: 5,
1250
- longThreshold: 4,
1251
- buttons: {
1252
- first: { label: "\u25C0\u25C0", emoji: { name: "\u23EE\uFE0F", id: "\u23EE\uFE0F" } },
1253
- back: { label: "\u25C0", emoji: { name: "\u25C0\uFE0F", id: "\u25C0\uFE0F" } },
1254
- jump: { label: "\u{1F4C4}", emoji: { name: "\u{1F4C4}", id: "\u{1F4C4}" } },
1255
- next: { label: "\u25B6", emoji: { name: "\u25B6\uFE0F", id: "\u25B6\uFE0F" } },
1256
- last: { label: "\u25B6\u25B6", emoji: { name: "\u23ED\uFE0F", id: "\u23ED\uFE0F" } }
1257
- }
1258
- },
1259
- prompt: {
1260
- defaultTitle: "Are you sure?",
1261
- defaultDescription: "Make sure you know what you're doing.",
1262
- confirmLabel: "Confirm",
1263
- rejectLabel: "Cancel"
1264
- }
1265
- };
1266
- var createToolsConfig = createConfigFactory(globalToolsConfig);
1267
- var defineGlobalToolsConfig = (options) => {
1268
- Object.assign(globalToolsConfig, createToolsConfig(options, globalToolsConfig));
1269
- };
1270
-
1271
- // src/tools/dynaSend.ts
1272
- var import_discord5 = require("discord.js");
1273
-
1274
- // src/tools/types.ts
1275
- var SendMethod = /* @__PURE__ */ ((SendMethod2) => {
1276
- SendMethod2[SendMethod2["Reply"] = 0] = "Reply";
1277
- SendMethod2[SendMethod2["EditReply"] = 1] = "EditReply";
1278
- SendMethod2[SendMethod2["FollowUp"] = 2] = "FollowUp";
1279
- SendMethod2[SendMethod2["Channel"] = 3] = "Channel";
1280
- SendMethod2[SendMethod2["MessageReply"] = 4] = "MessageReply";
1281
- SendMethod2[SendMethod2["MessageEdit"] = 5] = "MessageEdit";
1282
- SendMethod2[SendMethod2["User"] = 6] = "User";
1283
- return SendMethod2;
1284
- })(SendMethod || {});
1285
-
1286
- // src/tools/dynaSend.ts
1287
- var DynaSend = class {
1288
- static forceArray(value) {
1289
- return Array.isArray(value) ? value : [value];
1290
- }
1291
- static isInteractionCallback(obj) {
1292
- return obj instanceof import_discord5.InteractionCallbackResponse;
1293
- }
1294
- static filterFlags(flags, excludeFlags) {
1295
- if (!flags) return void 0;
1296
- const flagArray = this.forceArray(flags);
1297
- return flagArray.filter((flag) => !excludeFlags.includes(flag));
1298
- }
1299
- static detectSendMethod(handler) {
1300
- if (handler instanceof import_discord5.BaseInteraction) {
1301
- return handler.replied || handler.deferred ? 1 /* EditReply */ : 0 /* Reply */;
1302
- }
1303
- if (handler instanceof import_discord5.BaseChannel) return 3 /* Channel */;
1304
- if (handler instanceof import_discord5.Message) return 4 /* MessageReply */;
1305
- if (handler instanceof import_discord5.GuildMember || handler instanceof import_discord5.User) return 6 /* User */;
1306
- throw new Error("[DynaSend] Unable to determine send method for handler type");
1307
- }
1308
- static validateSendMethod(handler, method) {
1309
- const interactionMethods = [0 /* Reply */, 1 /* EditReply */, 2 /* FollowUp */];
1310
- if (interactionMethods.includes(method) && !(handler instanceof import_discord5.BaseInteraction)) {
1311
- throw new TypeError(`[DynaSend] SendMethod '${SendMethod[method]}' requires BaseInteraction handler`);
1312
- }
1313
- if (method === 3 /* Channel */ && !(handler instanceof import_discord5.BaseChannel)) {
1314
- throw new TypeError(`[DynaSend] SendMethod '${SendMethod[method]}' requires BaseChannel handler`);
1315
- }
1316
- if ([4 /* MessageReply */, 5 /* MessageEdit */].includes(method) && !(handler instanceof import_discord5.Message)) {
1317
- throw new TypeError(`[DynaSend] SendMethod '${SendMethod[method]}' requires Message handler`);
1318
- }
1319
- if (method === 6 /* User */ && !(handler instanceof import_discord5.GuildMember || handler instanceof import_discord5.User)) {
1320
- throw new TypeError(`[DynaSend] SendMethod '${SendMethod[method]}' requires User or GuildMember handler`);
1321
- }
1322
- }
1323
- static createMessageData(options, method) {
1324
- const baseData = {
1325
- content: options.content,
1326
- embeds: options.embeds,
1327
- components: options.components,
1328
- files: options.files,
1329
- allowedMentions: options.allowedMentions,
1330
- tts: options.tts
1331
- };
1332
- switch (method) {
1333
- case 0 /* Reply */:
1334
- return {
1335
- ...baseData,
1336
- flags: options.flags,
1337
- withResponse: options.withResponse,
1338
- poll: options.poll
1339
- };
1340
- case 1 /* EditReply */:
1341
- return {
1342
- ...baseData,
1343
- flags: this.filterFlags(options.flags, ["Ephemeral", "SuppressNotifications"]),
1344
- withResponse: options.withResponse,
1345
- poll: options.poll
1346
- };
1347
- case 2 /* FollowUp */:
1348
- return {
1349
- ...baseData,
1350
- flags: options.flags,
1351
- withResponse: options.withResponse,
1352
- poll: options.poll
1353
- };
1354
- case 3 /* Channel */:
1355
- return {
1356
- ...baseData,
1357
- flags: this.filterFlags(options.flags, ["Ephemeral"]),
1358
- poll: options.poll,
1359
- stickers: options.stickers,
1360
- reply: options.reply
1361
- };
1362
- case 4 /* MessageReply */:
1363
- return {
1364
- ...baseData,
1365
- flags: this.filterFlags(options.flags, ["Ephemeral"]),
1366
- poll: options.poll,
1367
- stickers: options.stickers
1368
- };
1369
- case 5 /* MessageEdit */:
1370
- return {
1371
- ...baseData,
1372
- flags: this.filterFlags(options.flags, ["Ephemeral", "SuppressNotifications"])
1373
- };
1374
- case 6 /* User */:
1375
- return {
1376
- ...baseData,
1377
- flags: this.filterFlags(options.flags, ["Ephemeral"]),
1378
- poll: options.poll,
1379
- forward: options.forward,
1380
- stickers: options.stickers
1381
- };
1382
- default:
1383
- return baseData;
1384
- }
1385
- }
1386
- static async executeSend(handler, method, data) {
1387
- try {
1388
- switch (method) {
1389
- case 0 /* Reply */: {
1390
- const response = await handler.reply(data);
1391
- return this.isInteractionCallback(response) ? response.resource?.message ?? null : null;
1392
- }
1393
- case 1 /* EditReply */:
1394
- return await handler.editReply(data);
1395
- case 2 /* FollowUp */:
1396
- return await handler.followUp(data);
1397
- case 3 /* Channel */:
1398
- return await handler.send(data);
1399
- case 4 /* MessageReply */:
1400
- return await handler.reply(data);
1401
- case 5 /* MessageEdit */: {
1402
- const message = handler;
1403
- if (!message.editable) {
1404
- console.warn("[DynaSend] Message is not editable");
1405
- return null;
1406
- }
1407
- return await message.edit(data);
1408
- }
1409
- case 6 /* User */:
1410
- return await handler.send(data);
1411
- default:
1412
- throw new Error(`[DynaSend] Unknown send method '${method}'`);
1413
- }
1414
- } catch (error) {
1415
- console.error(`[DynaSend] Error with method '${SendMethod[method]}':`, error);
1416
- return null;
1417
- }
1418
- }
1419
- static scheduleDelete(message, delay) {
1420
- if (delay < 1e3) {
1421
- console.warn(`[DynaSend] Delete delay is less than 1 second (${delay}ms). Is this intentional?`);
1422
- }
1423
- setTimeout(async () => {
1424
- try {
1425
- if (message.deletable) {
1426
- await message.delete();
1427
- }
1428
- } catch (error) {
1429
- console.error("[DynaSend] Error deleting message:", error);
1430
- }
1431
- }, delay);
1432
- }
1433
- static async send(handler, options) {
1434
- const sendMethod = options.sendMethod ?? this.detectSendMethod(handler);
1435
- this.validateSendMethod(handler, sendMethod);
1436
- const messageData = this.createMessageData(options, sendMethod);
1437
- const message = await this.executeSend(handler, sendMethod, messageData);
1438
- if (options.deleteAfter && message) {
1439
- this.scheduleDelete(message, options.deleteAfter);
1440
- }
1441
- return message;
1442
- }
1443
- };
1444
- async function dynaSend(handler, options) {
1445
- return DynaSend.send(handler, options);
1446
- }
1447
-
1448
- // src/tools/BetterEmbed.ts
1449
- var BetterEmbed = class _BetterEmbed {
1450
- embed = new import_discord6.EmbedBuilder();
1451
- data;
1452
- config;
1453
- /** A powerful wrapper for `EmbedBuilder` that introduces useful features
1454
- *
1455
- * Auto-shorthand context formatting (_ACF_) is enabled by default
1456
- *
1457
- * All functions utilize _ACF_ unless `BetterEmbed.acf` is set to `false`
1458
- *
1459
- * ___Use a blackslash___ `\` ___to escape any context___
1460
- *
1461
- * \- - - Author Context - - -
1462
- * - __`$USER`__: _author's mention (@xsqu1znt)_
1463
- * - __`$USER_NAME`__: _author's username_
1464
- * - __`$DISPLAY_NAME`__: _author's display name (requires `GuildMember` context)_
1465
- * - __`$USER_AVATAR`__: _author's avatar_
1466
- *
1467
- * \- - - Client Context - - -
1468
- *
1469
- * - __`$BOT_AVATAR`__: _bot's avatar_
1470
- *
1471
- * \- - - Shorthand Context - - -
1472
- * - __`$YEAR`__: _YYYY_
1473
- * - __`$MONTH`__: _MM_
1474
- * - __`$DAY`__: _DD_
1475
- * - __`$year`__: _YY_
1476
- * - __`$month`__: _M or MM_
1477
- * - __`$day`__: _D or DD_ */
1478
- constructor(data = {}) {
1479
- this.config = data.config ? createToolsConfig(data.config) : globalToolsConfig;
1480
- this.data = {
1481
- context: data.context || null,
1482
- author: data.author || null,
1483
- title: data.title || null,
1484
- thumbnailUrl: data.thumbnailUrl || null,
1485
- description: data.description || null,
1486
- imageUrl: data.imageUrl || null,
1487
- footer: data.footer || null,
1488
- fields: data.fields || [],
1489
- color: data.color ?? (this.config.devMode ? this.config.embedColorDev : this.config.embedColor),
1490
- timestamp: data.timestamp || null,
1491
- acf: data.acf ?? true
1492
- };
1493
- this.build();
1494
- }
1495
- build() {
1496
- this.normalizeData();
1497
- this.applyContextFormatting();
1498
- this.configureEmbed();
1499
- }
1500
- normalizeData() {
1501
- if (typeof this.data.author === "string") {
1502
- this.data.author = { text: this.data.author };
1503
- }
1504
- if (typeof this.data.title === "string") {
1505
- this.data.title = { text: this.data.title };
1506
- }
1507
- if (typeof this.data.footer === "string") {
1508
- this.data.footer = { text: this.data.footer };
1509
- }
1510
- if (this.data.timestamp === true) {
1511
- this.data.timestamp = Date.now();
1512
- }
1513
- }
1514
- getContextUser() {
1515
- const context = this.data.context;
1516
- if (!context) return null;
1517
- return context.user || context.interaction?.member || context.interaction?.user || context.message?.member || context.message?.author || null;
1518
- }
1519
- getContextClient() {
1520
- const context = this.data.context;
1521
- if (!context) return null;
1522
- return context.client || context.interaction?.client || context.message?.client || null;
1523
- }
1524
- applyContextFormatting(str) {
1525
- if (!this.data.acf) return;
1526
- const user = this.getContextUser();
1527
- const guildMember = user instanceof import_discord6.GuildMember ? user : null;
1528
- const actualUser = guildMember?.user || (user instanceof import_discord6.User ? user : null);
1529
- const client = this.getContextClient();
1530
- const formatString = (str2) => {
1531
- if (!str2 || !str2.includes("$")) return str2;
1532
- return str2.replace(/(?<!\\)\$USER\b/g, actualUser?.toString() || "$USER").replace(/(?<!\\)\$USER_NAME\b/g, actualUser?.username || "$USER_NAME").replace(/(?<!\\)\$USER_AVATAR\b/g, actualUser?.avatarURL() || "$USER_AVATAR").replace(/(?<!\\)\$DISPLAY_NAME\b/g, guildMember?.displayName || "$DISPLAY_NAME").replace(/(?<!\\)\$BOT_AVATAR\b/g, client?.user?.avatarURL() || "$BOT_AVATAR").replace(/(?<!\\)\$INVIS\b/g, "\u200B").replace(/(?<!\\)\$YEAR/g, (/* @__PURE__ */ new Date()).getFullYear().toString()).replace(/(?<!\\)\$MONTH/g, String((/* @__PURE__ */ new Date()).getMonth() + 1).padStart(2, "0")).replace(/(?<!\\)\$DAY/g, String((/* @__PURE__ */ new Date()).getDate()).padStart(2, "0")).replace(/(?<!\\)\$year/g, String((/* @__PURE__ */ new Date()).getFullYear()).slice(-2)).replace(/(?<!\\)\$month/g, String((/* @__PURE__ */ new Date()).getMonth() + 1).padStart(2, "0")).replace(/(?<!\\)\$day/g, String((/* @__PURE__ */ new Date()).getDate()).padStart(2, "0")).replace(/(?<!\\|<)@([0-9]+)(?!>)/g, "<@$1>").replace(/(?<!\\|<)@&([0-9]+)(?!>)/g, "<@&$1>").replace(/(?<!\\|<)#([0-9]+)(?!>)/g, "<#$1>");
1533
- };
1534
- if (str) {
1535
- return formatString(str);
1536
- }
1537
- if (this.data.author && typeof this.data.author === "object") {
1538
- this.data.author.text = formatString(this.data.author.text);
1539
- if (this.data.author.icon === true && actualUser) {
1540
- this.data.author.icon = actualUser.avatarURL();
1541
- } else if (typeof this.data.author.icon === "string") {
1542
- this.data.author.icon = formatString(this.data.author.icon);
1543
- }
1544
- }
1545
- if (this.data.title && typeof this.data.title === "object") {
1546
- this.data.title.text = formatString(this.data.title.text);
1547
- }
1548
- if (this.data.description) {
1549
- this.data.description = formatString(
1550
- Array.isArray(this.data.description) ? this.data.description.filter((s) => s !== null && s !== void 0).join("\n") : this.data.description
1551
- );
1552
- }
1553
- if (this.data.footer && typeof this.data.footer === "object") {
1554
- this.data.footer.text = formatString(this.data.footer.text);
1555
- }
1556
- if (this.data.thumbnailUrl) {
1557
- this.data.thumbnailUrl = formatString(this.data.thumbnailUrl);
1558
- }
1559
- if (this.data.imageUrl) {
1560
- this.data.imageUrl = formatString(this.data.imageUrl);
1561
- }
1562
- this.data.fields = this.data.fields.filter(Boolean).map((field) => ({
1563
- ...field,
1564
- name: formatString(field.name),
1565
- value: formatString(field.value)
1566
- }));
1567
- }
1568
- configureEmbed() {
1569
- if (this.data.author && typeof this.data.author === "object" && this.data.author.text) {
1570
- try {
1571
- this.embed.setAuthor({
1572
- name: this.data.author.text,
1573
- iconURL: typeof this.data.author.icon === "string" ? this.data.author.icon : void 0,
1574
- url: this.data.author.hyperlink || void 0
1575
- });
1576
- } catch (error) {
1577
- console.error("[BetterEmbed] Invalid author configuration:", error);
1578
- }
1579
- }
1580
- if (this.data.title && typeof this.data.title === "object" && this.data.title.text) {
1581
- try {
1582
- this.embed.setTitle(this.data.title.text);
1583
- if (this.data.title.hyperlink) {
1584
- this.embed.setURL(this.data.title.hyperlink);
1585
- }
1586
- } catch (error) {
1587
- console.error("[BetterEmbed] Invalid title configuration:", error);
1588
- }
1589
- }
1590
- if (this.data.description) {
1591
- this.embed.setDescription(
1592
- Array.isArray(this.data.description) ? this.data.description.join("\n") : this.data.description
1593
- );
1594
- }
1595
- if (this.data.thumbnailUrl) {
1596
- try {
1597
- this.embed.setThumbnail(this.data.thumbnailUrl);
1598
- } catch (error) {
1599
- console.error("[BetterEmbed] Invalid thumbnail URL:", error);
1600
- }
1601
- }
1602
- if (this.data.imageUrl) {
1603
- try {
1604
- this.embed.setImage(this.data.imageUrl);
1605
- } catch (error) {
1606
- console.error("[BetterEmbed] Invalid image URL:", error);
1607
- }
1608
- }
1609
- if (this.data.footer && typeof this.data.footer === "object" && this.data.footer.text) {
1610
- try {
1611
- this.embed.setFooter({
1612
- text: this.data.footer.text,
1613
- iconURL: typeof this.data.footer.icon === "string" ? this.data.footer.icon : void 0
1614
- });
1615
- } catch (error) {
1616
- console.error("[BetterEmbed] Invalid footer configuration:", error);
1617
- }
1618
- }
1619
- if (this.data.color) {
1620
- try {
1621
- const color = Array.isArray(this.data.color) ? this.data.color[Math.floor(Math.random() * this.data.color.length)] ?? null : this.data.color;
1622
- this.embed.setColor(color);
1623
- } catch (error) {
1624
- console.error("[BetterEmbed] Invalid color:", error);
1625
- }
1626
- }
1627
- if (this.data.timestamp && this.data.timestamp !== true) {
1628
- try {
1629
- this.embed.setTimestamp(this.data.timestamp);
1630
- } catch (error) {
1631
- console.error("[BetterEmbed] Invalid timestamp:", error);
1632
- }
1633
- }
1634
- if (this.data.fields.length > 0) {
1635
- const validFields = this.data.fields.slice(0, 25);
1636
- if (this.data.fields.length > 25) {
1637
- console.warn("[BetterEmbed] Only first 25 fields will be used (Discord limit)");
1638
- }
1639
- this.embed.setFields(validFields);
1640
- }
1641
- }
1642
- setAuthor(author) {
1643
- this.data.author = author;
1644
- this.build();
1645
- return this;
1646
- }
1647
- setTitle(title) {
1648
- this.data.title = title;
1649
- this.build();
1650
- return this;
1651
- }
1652
- setDescription(description) {
1653
- this.data.description = description;
1654
- this.build();
1655
- return this;
1656
- }
1657
- setThumbnail(url) {
1658
- this.data.thumbnailUrl = url;
1659
- this.build();
1660
- return this;
1661
- }
1662
- setImage(url) {
1663
- this.data.imageUrl = url;
1664
- this.build();
1665
- return this;
1666
- }
1667
- setFooter(footer) {
1668
- this.data.footer = footer;
1669
- this.build();
1670
- return this;
1671
- }
1672
- setColor(color) {
1673
- this.data.color = color;
1674
- this.build();
1675
- return this;
1676
- }
1677
- setTimestamp(timestamp) {
1678
- this.data.timestamp = timestamp;
1679
- this.build();
1680
- return this;
1681
- }
1682
- addFields(fields) {
1683
- this.data.fields = [...this.data.fields, ...fields];
1684
- this.build();
1685
- return this;
1686
- }
1687
- setFields(fields) {
1688
- this.data.fields = fields;
1689
- this.build();
1690
- return this;
1691
- }
1692
- spliceFields(index, deleteCount, ...fields) {
1693
- this.data.fields.splice(index, deleteCount, ...fields);
1694
- this.build();
1695
- return this;
1696
- }
1697
- clone(overrides = {}) {
1698
- return new _BetterEmbed({ ...this.data, ...overrides });
1699
- }
1700
- toJSON() {
1701
- return this.embed.toJSON();
1702
- }
1703
- async send(handler, options = {}, overrides) {
1704
- this.build();
1705
- if (options.content && this.data.acf) {
1706
- options.content = this.applyContextFormatting(options.content);
1707
- }
1708
- return await dynaSend(handler, {
1709
- ...options,
1710
- embeds: [
1711
- overrides ? this.clone(overrides) : this,
1712
- ...Array.isArray(options?.embeds) ? options?.embeds : options?.embeds ? [options?.embeds] : []
1713
- ]
1714
- });
1715
- }
1716
- };
1717
-
1718
- // src/modules/builtins/commandError.builtin.ts
1719
- var import_discord7 = require("discord.js");
1720
- async function sendCommandErrorEmbed(client, error, guild, messageOrInteraction) {
1721
- if (!client.features.enableCommandErrorMessage) return null;
1722
- const config = typeof client.features.enableCommandErrorMessage !== "boolean" ? client.features.enableCommandErrorMessage : void 0;
1723
- const buttons = {
1724
- supportServer: new import_discord7.ButtonBuilder({
1725
- url: config?.inviteUrl || client.config.staff.guild.inviteUrl || "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
1726
- // may or may not be a rickroll
1727
- label: config?.inviteButtonLabel || "Support Support",
1728
- style: import_discord7.ButtonStyle.Link
1729
- }),
1730
- details: new import_discord7.ButtonBuilder({
1731
- customId: "btn_details",
1732
- label: config?.detailButtonLabel || "Details",
1733
- style: import_discord7.ButtonStyle.Secondary
1734
- })
1735
- };
1736
- const actionRow = new import_discord7.ActionRowBuilder({
1737
- components: config?.inviteUrl && guild?.id !== (config.inviteUrl || client.config.staff.guild.id) ? [buttons.supportServer, buttons.details] : [buttons.details]
1738
- });
1739
- const embed_error = config?.embed?.(new BetterEmbed(), error, guild) || new BetterEmbed({
1740
- color: "Red",
1741
- title: "Something went wrong",
1742
- description: "If you keep encountering this error, please report it."
1743
- });
1744
- const msg = await dynaSend(messageOrInteraction, {
1745
- embeds: [embed_error],
1746
- components: [actionRow],
1747
- flags: config?.ephemeral ? "Ephemeral" : void 0,
1748
- deleteAfter: config?.deleteAfter
1749
- });
1750
- if (!msg) return null;
1751
- const collector = msg.createMessageComponentCollector({
1752
- componentType: import_discord7.ComponentType.Button,
1753
- idle: config?.detailButtonIdleTimeout ?? 3e4,
1754
- filter: (i) => i.customId === "btn_details"
1755
- });
1756
- collector.on("collect", (i) => {
1757
- const attachment = new import_discord7.AttachmentBuilder(Buffer.from(`${error.message}
1758
-
1759
- ${error.stack}`), {
1760
- name: "error.txt"
1761
- });
1762
- i.reply({ files: [attachment], flags: "Ephemeral" });
1763
- });
1764
- collector.on("end", () => {
1765
- buttons.details.setDisabled(true);
1766
- dynaSend(messageOrInteraction, {
1767
- embeds: [embed_error],
1768
- sendMethod: messageOrInteraction instanceof import_discord7.Message ? 5 /* MessageEdit */ : void 0,
1769
- components: [actionRow]
1770
- });
1771
- });
1772
- return msg;
1773
- }
1774
-
1775
- // src/client/vimcord.errorHandler.ts
1776
- var VimcordErrorHandler = class {
1777
- client;
1778
- constructor(client) {
1779
- this.client = client;
1780
- }
1781
- /** Handles command errors - sends error embed to user, then rethrows. */
1782
- async handleCommandError(error, guild, messageOrInteraction) {
1783
- await sendCommandErrorEmbed(this.client, error, guild, messageOrInteraction);
1784
- throw error;
1785
- }
1786
- /** Handles internal Vimcord errors - logs with [Vimcord] prefix. */
1787
- handleVimcordError(error, context) {
1788
- this.client.logger.error(`[Vimcord] [${context}]`, error);
1789
- }
1790
- /** Sets up global process error handlers. */
1791
- setupGlobalHandlers() {
1792
- process.on("uncaughtException", (err) => this.handleVimcordError(err, "Uncaught Exception"));
1793
- process.on("unhandledRejection", (err) => this.handleVimcordError(err, "Unhandled Rejection"));
1794
- process.on("exit", (code) => this.client.logger.debug(`Process exited with code ${code}`));
1795
- this.client.on("error", (err) => this.handleVimcordError(err, "Client Error"));
1796
- this.client.on("shardError", (err) => this.handleVimcordError(err, "Client Shard Error"));
1797
- }
1798
- };
1799
-
1800
- // src/client/vimcord.logger.ts
1801
- var import_chalk2 = __toESM(require("chalk"));
1802
-
1803
- // package.json
1804
- var version = "1.0.57";
1805
-
1806
- // src/client/vimcord.logger.ts
1807
- var clientLoggerFactory = (client) => new Logger({ prefixEmoji: "\u26A1", prefix: `vimcord (i${client.clientId})` }).extend({
1808
- clientBanner(client2) {
1809
- if (client2.config.app.disableBanner) return;
1810
- const border = "\u2550".repeat(50);
1811
- console.log(import_chalk2.default.hex(this.colors.primary)(`
1812
- \u2554${border}\u2557`));
1813
- console.log(
1814
- import_chalk2.default.hex(this.colors.primary)("\u2551") + import_chalk2.default.bold.hex(this.colors.text)(
1815
- ` \u{1F680} ${client2.$name} v${client2.$version}`.padEnd(50 - (client2.$devMode ? 12 : 0))
1816
- ) + import_chalk2.default.hex(this.colors.primary)(`${client2.$devMode ? import_chalk2.default.hex(this.colors.warn)("devMode \u26A0\uFE0F ") : ""}\u2551`)
1817
- );
1818
- console.log(import_chalk2.default.hex(this.colors.primary)(`\u2551${"".padEnd(50)}\u2551`));
1819
- console.log(
1820
- import_chalk2.default.hex(this.colors.primary)("\u2551") + import_chalk2.default.hex(this.colors.muted)(
1821
- ` # Powered by Vimcord v${version}`.padEnd(50 - 3 - `${client2.clientId}`.length)
1822
- ) + import_chalk2.default.hex(this.colors.primary)(`${import_chalk2.default.hex(this.colors.muted)(`i${client2.clientId}`)} \u2551`)
1823
- );
1824
- console.log(import_chalk2.default.hex(this.colors.primary)(`\u255A${border}\u255D
1825
- `));
1826
- },
1827
- clientReady(clientTag, guildCount) {
1828
- console.log(
1829
- this.formatTimestamp(),
1830
- this.formatPrefix(),
1831
- import_chalk2.default.hex(this.colors.success)("\u{1F916} READY"),
1832
- import_chalk2.default.white(`Connected as ${import_chalk2.default.bold.hex(this.colors.primary)(clientTag)}`),
1833
- import_chalk2.default.hex(this.colors.muted)(`\u2022 ${guildCount} guilds`)
1834
- );
1835
- },
1836
- moduleLoaded(moduleName, count, ignoredCount) {
1837
- const countText = count ? import_chalk2.default.hex(this.colors.muted)(`(${count} items)`) : "";
1838
- console.log(
1839
- this.formatTimestamp(),
1840
- this.formatPrefix(),
1841
- import_chalk2.default.hex("#9B59B6")("\u{1F4E6} MODULE"),
1842
- import_chalk2.default.hex(this.colors.warn)(`${moduleName} loaded`),
1843
- ignoredCount ? import_chalk2.default.hex(this.colors.muted)(`(${ignoredCount} ignored)`) : "",
1844
- countText
1845
- );
1846
- },
1847
- commandExecuted(commandName, username, guildName) {
1848
- const location = guildName ? `in ${import_chalk2.default.hex(this.colors.muted)(guildName)}` : "in DMs";
1849
- console.log(
1850
- this.formatTimestamp(),
1851
- this.formatPrefix(),
1852
- import_chalk2.default.hex("#87CEEB")("\u{1F4DD} COMMAND"),
1853
- import_chalk2.default.hex(this.colors.warn)(`/${commandName}`),
1854
- import_chalk2.default.white(`used by ${import_chalk2.default.bold(username)}`),
1855
- import_chalk2.default.hex(this.colors.muted)(location)
1856
- );
1857
- },
1858
- plugin(pluginName, action, details) {
1859
- console.log(
1860
- this.formatTimestamp(),
1861
- this.formatPrefix(),
1862
- import_chalk2.default.hex("#FF6B9D")(`\u{1F50C} ${pluginName}`),
1863
- import_chalk2.default.white(action),
1864
- details ? import_chalk2.default.hex(this.colors.muted)(details) : ""
1865
- );
1866
- }
1867
- });
1868
-
1869
- // src/utils/processUtils.ts
1870
- var import_node_fs = require("fs");
1871
- var import_node_path = require("path");
1872
- function getPackageJson() {
1873
- return JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path.join)(process.cwd(), "package.json"), "utf-8"));
1874
- }
1875
- function getDevMode() {
1876
- return process.argv.includes("--dev");
1877
- }
1878
-
1879
- // src/configs/app.config.ts
1880
- var defaultConfig = {
1881
- name: "Discord Bot",
1882
- version: getPackageJson()?.version ?? "1.0.0",
1883
- devMode: getDevMode(),
1884
- verbose: false,
1885
- enableCLI: false,
1886
- disableBanner: false
1887
- };
1888
- var createAppConfig = createConfigFactory(defaultConfig, (config) => {
1889
- if (!config.name) throw new Error("App name is required");
1890
- defineGlobalToolsConfig({ devMode: config.devMode });
1891
- });
1892
-
1893
- // src/configs/contextCommand.config.ts
1894
- var defaultConfig2 = {
1895
- enabled: true,
1896
- logExecution: true
1897
- };
1898
- var createContextCommandConfig = createConfigFactory(defaultConfig2);
1899
-
1900
- // src/configs/prefixCommand.config.ts
1901
- var defaultConfig3 = {
1902
- enabled: true,
1903
- defaultPrefix: "!",
1904
- allowMentionAsPrefix: true,
1905
- allowCaseInsensitiveCommandNames: true,
1906
- logExecution: true
1907
- };
1908
- var createPrefixCommandConfig = createConfigFactory(defaultConfig3);
1909
-
1910
- // src/configs/slashCommand.config.ts
1911
- var defaultConfig4 = {
1912
- enabled: true,
1913
- logExecution: true
1914
- };
1915
- var createSlashCommandConfig = createConfigFactory(defaultConfig4);
1916
-
1917
- // src/configs/staff.config.ts
1918
- var defaultConfig5 = {
1919
- ownerId: null,
1920
- superUsers: [],
1921
- superUserRoles: [],
1922
- bypassers: [],
1923
- bypassesGuildAdmin: {
1924
- allBotStaff: false,
1925
- botOwner: false,
1926
- superUsers: false,
1927
- bypassers: false
1928
- },
1929
- guild: {
1930
- id: null,
1931
- inviteUrl: null,
1932
- channels: {}
1933
- }
1934
- };
1935
- var createStaffConfig = createConfigFactory(defaultConfig5);
1936
-
1937
- // src/client/vimcord.utils.ts
1938
- var DEFAULT_MODULE_SUFFIXES = {
1939
- slashCommands: ".slash",
1940
- contextCommands: ".ctx",
1941
- prefixCommands: ".prefix",
1942
- events: ".event"
1943
- };
1944
- var configSetters = {
1945
- app: createAppConfig,
1946
- staff: createStaffConfig,
1947
- slashCommands: createSlashCommandConfig,
1948
- contextCommands: createContextCommandConfig,
1949
- prefixCommands: createPrefixCommandConfig
1950
- };
1951
- var moduleImporters = {
1952
- slashCommands: (client, options, set) => {
1953
- const opt = options;
1954
- const dir = Array.isArray(options) ? options : typeof options === "string" ? options : options?.dir ?? [];
1955
- const suffix = Array.isArray(options) ? DEFAULT_MODULE_SUFFIXES.slashCommands : opt?.suffix ?? DEFAULT_MODULE_SUFFIXES.slashCommands;
1956
- return client.commands.slash.importFrom(dir, set, suffix);
1957
- },
1958
- contextCommands: (client, options, set) => {
1959
- const opt = options;
1960
- const dir = Array.isArray(options) ? options : typeof options === "string" ? options : options?.dir ?? [];
1961
- const suffix = Array.isArray(options) ? DEFAULT_MODULE_SUFFIXES.contextCommands : opt?.suffix ?? DEFAULT_MODULE_SUFFIXES.contextCommands;
1962
- return client.commands.context.importFrom(dir, set, suffix);
1963
- },
1964
- prefixCommands: (client, options, set) => {
1965
- const opt = options;
1966
- const dir = Array.isArray(options) ? options : typeof options === "string" ? options : options?.dir ?? [];
1967
- const suffix = Array.isArray(options) ? DEFAULT_MODULE_SUFFIXES.prefixCommands : opt?.suffix ?? DEFAULT_MODULE_SUFFIXES.prefixCommands;
1968
- return client.commands.prefix.importFrom(dir, set, suffix);
1969
- },
1970
- events: (client, options, set) => {
1971
- const opt = options;
1972
- const dir = Array.isArray(options) ? options : typeof options === "string" ? options : options?.dir ?? [];
1973
- const suffix = Array.isArray(options) ? DEFAULT_MODULE_SUFFIXES.events : opt?.suffix ?? DEFAULT_MODULE_SUFFIXES.events;
1974
- return client.events.importFrom(dir, set, suffix);
1975
- }
1976
- };
1977
- function defineClientOptions(options) {
1978
- return options;
1979
- }
1980
- function defineVimcordFeatures(features) {
1981
- return features;
1982
- }
1983
- function defineVimcordConfig(config) {
1984
- return {
1985
- app: createAppConfig(config.app),
1986
- staff: createStaffConfig(config.staff),
1987
- slashCommands: createSlashCommandConfig(config.slashCommands),
1988
- prefixCommands: createPrefixCommandConfig(config.prefixCommands),
1989
- contextCommands: createContextCommandConfig(config.contextCommands)
1990
- };
1991
- }
1992
- function useClient(clientId) {
1993
- return Vimcord.getInstance(clientId);
1994
- }
1995
- function useReadyClient(clientId, timeoutMs) {
1996
- return Vimcord.getReadyInstance(clientId, timeoutMs);
1997
- }
1998
- function createClient(options, features, config) {
1999
- return Vimcord.create(options, features, config);
2000
- }
2001
-
2002
- // src/modules/command.manager.ts
2003
- var import_discord8 = require("discord.js");
2004
-
2005
- // src/utils/importUtils.ts
2006
- var import_node_path2 = __toESM(require("path"));
2007
- var import_qznt2 = require("qznt");
2008
- function getProcessDir() {
2009
- const mainPath = process.argv[1];
2010
- if (!mainPath) return "";
2011
- return import_node_path2.default.dirname(mainPath);
2012
- }
2013
- function testFilenameSuffix(filename, suffix) {
2014
- if (!suffix) return filename.endsWith(".ts") || filename.endsWith(".js");
2015
- if (Array.isArray(suffix)) {
2016
- return suffix.some((s) => filename.endsWith(`${s}.ts`) || filename.endsWith(`${s}.js`));
2017
- } else {
2018
- return filename.endsWith(`${suffix}.ts`) || filename.endsWith(`${suffix}.js`);
2019
- }
2020
- }
2021
- async function importModulesFromDir(dir, suffix) {
2022
- const cwd = getProcessDir();
2023
- const MODULE_RELATIVE_PATH = import_node_path2.default.join(cwd, dir);
2024
- const MODULE_LOG_PATH = dir;
2025
- const files = import_qznt2.$.fs.readDir(MODULE_RELATIVE_PATH).filter((filename) => testFilenameSuffix(filename, suffix));
2026
- if (!files.length) return [];
2027
- const modules = await Promise.all(
2028
- files.map(async (fn) => {
2029
- let _path = import_node_path2.default.join(MODULE_RELATIVE_PATH, fn);
2030
- let _logPath = `./${import_node_path2.default.join(MODULE_LOG_PATH, fn)}`;
2031
- let _module;
2032
- try {
2033
- delete require.cache[require.resolve(_path)];
2034
- _module = require(_path);
2035
- } catch (err) {
2036
- console.warn(`Failed to import module at '${_logPath}'`, err);
2037
- _module = null;
2038
- }
2039
- return { module: _module, path: _logPath };
2040
- })
2041
- );
2042
- const filteredModules = modules.filter((m) => m.module);
2043
- if (!filteredModules.length) {
2044
- console.warn(`No valid modules were found in directory '${dir}'`);
2045
- }
2046
- return filteredModules;
2047
- }
2048
-
2049
- // src/modules/importers/baseModule.importer.ts
2050
- var ModuleImporter = class {
2051
- client;
2052
- constructor(client) {
2053
- this.client = client;
2054
- }
2055
- async importFrom(dir, set = false, suffix) {
2056
- if (set) this.items.clear();
2057
- const dirs = Array.isArray(dir) ? dir : [dir];
2058
- const modules = [];
2059
- for (const _dir of dirs) {
2060
- const results = await importModulesFromDir(_dir, suffix ?? this.itemSuffix);
2061
- modules.push(...results.map(({ module: module2 }) => module2.default));
2062
- }
2063
- for (const module2 of modules) {
2064
- const name = this.getName(module2);
2065
- this.items.set(name, module2);
2066
- }
2067
- this.client.logger.moduleLoaded(this.itemName, modules.length);
2068
- return this.items;
2069
- }
2070
- };
2071
-
2072
- // src/modules/command.manager.ts
2073
- var BaseCommandManager = class extends ModuleImporter {
2074
- type;
2075
- items = /* @__PURE__ */ new Map();
2076
- itemSuffix;
2077
- constructor(client, type, itemSuffix) {
2078
- super(client);
2079
- this.type = type;
2080
- switch (type) {
2081
- case 0 /* Slash */:
2082
- this.itemSuffix = itemSuffix ?? DEFAULT_MODULE_SUFFIXES.slashCommands;
2083
- break;
2084
- case 2 /* Context */:
2085
- this.itemSuffix = itemSuffix ?? DEFAULT_MODULE_SUFFIXES.contextCommands;
2086
- break;
2087
- case 1 /* Prefix */:
2088
- this.itemSuffix = itemSuffix ?? DEFAULT_MODULE_SUFFIXES.prefixCommands;
2089
- break;
2090
- }
2091
- }
2092
- get commands() {
2093
- return this.items;
2094
- }
2095
- get itemName() {
2096
- switch (this.type) {
2097
- case 0 /* Slash */:
2098
- return "Slash Commands";
2099
- case 2 /* Context */:
2100
- return "Context Commands";
2101
- case 1 /* Prefix */:
2102
- return "Prefix Commands";
2103
- }
2104
- }
2105
- getName(module2) {
2106
- return "builder" in module2 ? module2.builder.name : module2.options.name;
2107
- }
2108
- /**
2109
- * Gets a command by name.
2110
- */
2111
- get(name) {
2112
- if (this.type === 1 /* Prefix */) {
2113
- const config = this.client.config.prefixCommands;
2114
- const search = config.allowCaseInsensitiveCommandNames ? name.toLowerCase() : name;
2115
- return Array.from(this.items.values()).find((cmd) => {
2116
- const commandName = "builder" in cmd ? cmd.builder.name : cmd.options.name;
2117
- const trigger = config.allowCaseInsensitiveCommandNames ? commandName.toLowerCase() : commandName;
2118
- if (trigger === search) return true;
2119
- if ("aliases" in cmd.options) {
2120
- return cmd.options.aliases?.some(
2121
- (a) => config.allowCaseInsensitiveCommandNames ? a.toLowerCase() === search : a === search
2122
- );
2123
- }
2124
- });
2125
- } else {
2126
- return this.items.get(name);
2127
- }
2128
- }
2129
- /**
2130
- * Gets/filters commands and orders them alphabetically.
2131
- */
2132
- getAll(options = {}) {
2133
- const matchedCommands = /* @__PURE__ */ new Map();
2134
- const isDev = this.client.config.app.devMode;
2135
- for (const cmd of this.items.values()) {
2136
- const commandName = "builder" in cmd ? cmd.builder.name : cmd.options.name;
2137
- if (options.names || options.fuzzyNames) {
2138
- const nameMatched = options.names?.includes(commandName) || options.fuzzyNames?.some((fuzzy) => commandName.includes(fuzzy));
2139
- if (!nameMatched) continue;
2140
- }
2141
- if (options.ignoreDeploymentOptions) {
2142
- matchedCommands.set(commandName, cmd);
2143
- continue;
2144
- }
2145
- const deployment = "deployment" in cmd.options ? cmd.options.deployment ?? {} : {};
2146
- const isProperEnv = !deployment.environments || deployment.environments.includes(isDev ? "development" : "production");
2147
- if (!isProperEnv) continue;
2148
- if (options.globalOnly && deployment.global === false) continue;
2149
- matchedCommands.set(commandName, cmd);
2150
- }
2151
- return Array.from(matchedCommands.values()).sort((a, b) => {
2152
- const commandNameA = "builder" in a ? a.builder.name : a.options.name;
2153
- const commandNameB = "builder" in b ? b.builder.name : b.options.name;
2154
- return commandNameA.localeCompare(commandNameB);
2155
- });
2156
- }
2157
- /**
2158
- * Groups commands by category alphabetically.
2159
- */
2160
- sortByCategory() {
2161
- const categories = /* @__PURE__ */ new Map();
2162
- for (const cmd of this.items.values()) {
2163
- const metadata = cmd.options.metadata;
2164
- if (!metadata?.category) continue;
2165
- let entry = categories.get(metadata.category);
2166
- if (!entry) {
2167
- entry = {
2168
- name: metadata.category,
2169
- emoji: metadata.categoryEmoji,
2170
- commands: []
2171
- };
2172
- categories.set(metadata.category, entry);
2173
- }
2174
- entry.commands.push(cmd);
2175
- }
2176
- return Array.from(categories.values()).sort((a, b) => a.name.localeCompare(b.name)).map((cat) => {
2177
- cat.commands.sort((a, b) => {
2178
- const commandNameA = "builder" in a ? a.builder.name : a.options.name;
2179
- const commandNameB = "builder" in b ? b.builder.name : b.options.name;
2180
- return commandNameA.localeCompare(commandNameB);
2181
- });
2182
- return cat;
2183
- });
2184
- }
2185
- };
2186
- var SlashCommandManager = class extends BaseCommandManager {
2187
- constructor(client) {
2188
- super(client, 0 /* Slash */);
2189
- }
2190
- };
2191
- var ContextCommandManager = class extends BaseCommandManager {
2192
- constructor(client) {
2193
- super(client, 2 /* Context */);
2194
- }
2195
- };
2196
- var PrefixCommandManager = class extends BaseCommandManager {
2197
- constructor(client) {
2198
- super(client, 1 /* Prefix */);
2199
- }
2200
- };
2201
- var CommandManager = class {
2202
- client;
2203
- slash;
2204
- prefix;
2205
- context;
2206
- constructor(client) {
2207
- this.client = client;
2208
- this.slash = new SlashCommandManager(client);
2209
- this.prefix = new PrefixCommandManager(client);
2210
- this.context = new ContextCommandManager(client);
2211
- }
2212
- getAllAppCommands(options = {}) {
2213
- return [...this.slash.getAll(options), ...this.context.getAll(options)];
2214
- }
2215
- async registerGlobal(options = {}) {
2216
- const client = await Vimcord.getReadyInstance(this.client.clientId);
2217
- if (!client.rest) {
2218
- console.error(`[CommandManager] \u2716 Failed to register app commands globally: REST is not initialized`);
2219
- return;
2220
- }
2221
- const commands = this.getAllAppCommands(options).map((cmd) => cmd.builder.toJSON());
2222
- if (!commands.length) {
2223
- console.log("[CommandManager] No commands to register globally");
2224
- return;
2225
- }
2226
- console.log(
2227
- `[CommandManager] Registering (${commands.length}) ${commands.length === 1 ? "command" : "commands"} globally...`
2228
- );
2229
- try {
2230
- await client.rest.put(import_discord8.Routes.applicationCommands(client.user.id), { body: commands });
2231
- console.log(`[CommandManager] \u2714 Registered app ${commands.length === 1 ? "command" : "commands"} globally`);
2232
- } catch (err) {
2233
- console.error(
2234
- `[CommandManager] \u2716 Failed to register app ${commands.length === 1 ? "command" : "commands"} globally`,
2235
- err
2236
- );
2237
- }
2238
- }
2239
- async unregisterGlobal() {
2240
- const client = await Vimcord.getReadyInstance(this.client.clientId);
2241
- if (!client.rest) {
2242
- console.error(`[CommandManager] \u2716 Failed to remove app commands globally: REST is not initialized`);
2243
- return;
2244
- }
2245
- try {
2246
- await client.rest.put(import_discord8.Routes.applicationCommands(client.user.id), { body: [] });
2247
- console.log(`[CommandManager] \u2714 Removed app commands globally`);
2248
- } catch (err) {
2249
- console.error(`[CommandManager] \u2716 Failed to remove app commands globally`, err);
2250
- }
2251
- }
2252
- async registerGuild(options = {}) {
2253
- const client = await Vimcord.getReadyInstance(this.client.clientId);
2254
- if (!client.rest) {
2255
- console.error(`[CommandManager] \u2716 Failed to register app commands by guild: REST is not initialized`);
2256
- return;
2257
- }
2258
- const commands = this.getAllAppCommands(options).map((cmd) => cmd.builder.toJSON());
2259
- if (!commands.length) {
2260
- console.log("[CommandManager] No commands to register by guild");
2261
- return;
2262
- }
2263
- const guildIds = options.guilds || client.guilds.cache.map((g) => g.id);
2264
- console.log(
2265
- `[CommandManager] Registering (${commands.length}) ${commands.length === 1 ? "command" : "commands"} for ${guildIds.length} guilds...`
2266
- );
2267
- await Promise.all(
2268
- guildIds.map(
2269
- (guildId) => client.rest.put(import_discord8.Routes.applicationGuildCommands(client.user.id, guildId), { body: commands }).then(() => {
2270
- const gName = client.guilds.cache.get(guildId)?.name || "n/a";
2271
- console.log(
2272
- `[CommandManager] \u2714 Set app ${commands.length === 1 ? "command" : "commands"} in guild: ${guildId} (${gName})`
2273
- );
2274
- }).catch((err) => {
2275
- const gName = client.guilds.cache.get(guildId)?.name || "n/a";
2276
- console.log(
2277
- `[CommandManager] \u2716 Failed to set app ${commands.length === 1 ? "command" : "commands"} in guild: ${guildId} (${gName})`,
2278
- err
2279
- );
2280
- })
2281
- )
2282
- );
2283
- }
2284
- async unregisterGuild(options = {}) {
2285
- const client = await Vimcord.getReadyInstance(this.client.clientId);
2286
- if (!client.rest) {
2287
- console.error(`[CommandManager] \u2716 Failed to register app commands by guild: REST is not initialized`);
2288
- return;
2289
- }
2290
- const guildIds = options.guilds || client.guilds.cache.map((g) => g.id);
2291
- console.log(`[CommandManager] Unregistering commands from ${guildIds.length} guilds...`);
2292
- await Promise.all(
2293
- guildIds.map(
2294
- (guildId) => client.rest.put(import_discord8.Routes.applicationGuildCommands(client.user.id, guildId), { body: [] }).then(() => console.log(`[CommandManager] \u2714 Removed app commands in guild: ${guildId}`)).catch((err) => console.log(`[CommandManager] \u2716 Failed to remove app commands in guild: ${guildId}`, err))
2295
- )
2296
- );
2297
- }
2298
- };
2299
-
2300
- // src/modules/event.manager.ts
2301
- var import_discord9 = require("discord.js");
2302
- var EventManager = class extends ModuleImporter {
2303
- items = /* @__PURE__ */ new Map();
2304
- itemSuffix = "event";
2305
- itemName = "Event Handlers";
2306
- logger;
2307
- constructor(client) {
2308
- super(client);
2309
- this.logger = new Logger({ prefixEmoji: "\u{1F4CB}", prefix: `EventManager (i${this.client.clientId})` });
2310
- for (const event of Object.values(import_discord9.Events)) {
2311
- client.on(
2312
- event.toString(),
2313
- async (...args) => this.executeEvents.apply(this, [event, ...args])
2314
- );
2315
- }
2316
- }
2317
- getName(module2) {
2318
- return module2.name;
2319
- }
2320
- register(...events) {
2321
- for (const event of events) {
2322
- this.items.set(event.name, event);
2323
- if (this.client.config.app.verbose) {
2324
- this.logger.debug(`'${event.name}' registered for EventType '${event.event}'`);
2325
- }
2326
- }
2327
- }
2328
- unregister(...names) {
2329
- for (const name of names) {
2330
- const event = this.items.get(name);
2331
- if (!event) continue;
2332
- this.items.delete(name);
2333
- if (this.client.config.app.verbose) {
2334
- this.logger.debug(`'${event.name}' unregistered for EventType '${event.event}'`);
2335
- }
2336
- }
2337
- }
2338
- clear() {
2339
- this.items.forEach((e) => this.unregister(e.name));
2340
- this.items.clear();
2341
- }
2342
- get(name) {
2343
- return this.items.get(name);
2344
- }
2345
- getByTag(tag) {
2346
- return Array.from(this.items.values()).filter((event) => event.metadata?.tags?.includes(tag));
2347
- }
2348
- getByCategory(category) {
2349
- return Array.from(this.items.values()).filter((event) => event.metadata?.category?.includes(category));
2350
- }
2351
- getByEvent(eventType) {
2352
- return Array.from(this.items.values()).filter((event) => event.event === eventType);
2353
- }
2354
- async executeEvents(eventType, ...args) {
2355
- const events = this.getByEvent(eventType);
2356
- if (!events.length) return;
2357
- const sortedEvents = events.sort((a, b) => b.priority - a.priority);
2358
- await Promise.all(
2359
- sortedEvents.map(async (event) => {
2360
- try {
2361
- await event.execute?.(this.client, ...args);
2362
- if (event.once) {
2363
- this.unregister(event.name);
2364
- }
2365
- } catch (err) {
2366
- this.logger.error(`'${event.name}' failed to execute`, err);
2367
- }
2368
- })
2369
- );
2370
- }
2371
- };
2372
-
2373
- // src/tools/utils.ts
2374
- var MENTION_OR_SNOWFLAKE_REGEX = /<@[#&]?[\d]{6,}>|[\d]{6,}/;
2375
- var fetchUserPromises = /* @__PURE__ */ new Map();
2376
- var fetchGuildPromises = /* @__PURE__ */ new Map();
2377
- var fetchMemberPromises = /* @__PURE__ */ new Map();
2378
- var fetchChannelPromises = /* @__PURE__ */ new Map();
2379
- var fetchMessagePromises = /* @__PURE__ */ new Map();
2380
- var fetchRolePromises = /* @__PURE__ */ new Map();
2381
- function createCachedFetch(cache, fetchFn, cacheKey) {
2382
- const cached = cache.get(cacheKey);
2383
- if (cached) return cached;
2384
- const promise = fetchFn().finally(() => cache.delete(cacheKey));
2385
- cache.set(cacheKey, promise);
2386
- return promise;
2387
- }
2388
- function __zero(str) {
2389
- return str?.length ? str : "0";
2390
- }
2391
- function isMentionOrSnowflake(str) {
2392
- return str ? MENTION_OR_SNOWFLAKE_REGEX.test(str) : false;
2393
- }
2394
- function cleanMention(str) {
2395
- return str ? str.replaceAll(/[<@#&>]/g, "").trim() : void 0;
2396
- }
2397
- async function getMessageMention(message, content, type, index = 0, idOnly) {
2398
- const args = content?.split(" ");
2399
- const arg = isMentionOrSnowflake(args?.[index]) ? cleanMention(args?.[index]) : void 0;
2400
- switch (type) {
2401
- case "user": {
2402
- const userMention2 = message.mentions.users.at(index) || null;
2403
- if (!userMention2 && arg) {
2404
- return idOnly ? arg : await fetchUser(message.client, arg);
2405
- }
2406
- return idOnly ? userMention2?.id || null : userMention2;
2407
- }
2408
- case "member": {
2409
- if (!message.guild) return null;
2410
- const member = await fetchMember(message.guild, message.mentions.users.at(index)?.id ?? arg);
2411
- return idOnly ? member?.id || null : member;
2412
- }
2413
- case "channel": {
2414
- const channelMention = message.mentions.channels.at(index) || null;
2415
- if (!channelMention && arg) {
2416
- if (idOnly) return arg;
2417
- const channel = message.guild ? await fetchChannel(message.guild, arg) : message.client.channels.cache.get(__zero(arg)) ?? message.client.channels.fetch(__zero(arg));
2418
- return channel;
2419
- }
2420
- return idOnly ? channelMention?.id || null : channelMention;
2421
- }
2422
- case "role": {
2423
- const roleMention = message.mentions.roles.at(index) || null;
2424
- if (!roleMention && arg) {
2425
- if (idOnly) return arg;
2426
- return message.guild ? await fetchRole(message.guild, arg) : null;
2427
- }
2428
- return idOnly ? roleMention?.id || null : roleMention;
2429
- }
2430
- default:
2431
- return null;
2432
- }
2433
- }
2434
- function getFirstMentionId(options) {
2435
- let mentionId = "";
2436
- if (options.message) {
2437
- switch (options.type) {
2438
- case "user":
2439
- mentionId = options.message.mentions.users.first()?.id || "";
2440
- break;
2441
- case "member":
2442
- mentionId = options.message.mentions.members?.first()?.id || "";
2443
- break;
2444
- case "channel":
2445
- mentionId = options.message.mentions.channels.first()?.id || "";
2446
- break;
2447
- case "role":
2448
- mentionId = options.message.mentions.roles.first()?.id || "";
2449
- break;
2450
- }
2451
- }
2452
- const firstArg = options.content?.split(" ")[0] || "";
2453
- return mentionId || isMentionOrSnowflake(firstArg) ? cleanMention(firstArg) : "";
2454
- }
2455
- async function fetchUser(client, userId) {
2456
- if (!userId) return null;
2457
- const key = `${client.user.id}-${userId}`;
2458
- const cached = client.users.cache.get(__zero(userId));
2459
- if (cached) return cached;
2460
- return createCachedFetch(fetchUserPromises, () => client.users.fetch(__zero(userId)).catch(() => null), key);
2461
- }
2462
- async function fetchGuild(client, guildId) {
2463
- if (!guildId) return null;
2464
- const key = `${client.user.id}-${guildId}`;
2465
- const cached = client.guilds.cache.get(__zero(guildId));
2466
- if (cached) return cached;
2467
- return createCachedFetch(fetchGuildPromises, () => client.guilds.fetch(__zero(guildId)).catch(() => null), key);
2468
- }
2469
- async function fetchMember(guild, memberId) {
2470
- if (!memberId) return null;
2471
- const key = `${guild.id}-${memberId}`;
2472
- const cached = guild.members.cache.get(__zero(memberId));
2473
- if (cached) return cached;
2474
- return createCachedFetch(fetchMemberPromises, () => guild.members.fetch(__zero(memberId)).catch(() => null), key);
2475
- }
2476
- async function fetchChannel(guild, channelId, type) {
2477
- if (!channelId) return null;
2478
- const key = `${guild.id}-${channelId}`;
2479
- const cached = guild.channels.cache.get(__zero(channelId)) ?? null;
2480
- if (cached) {
2481
- if (type && cached.type !== type) return null;
2482
- return cached;
2483
- }
2484
- const channel = await createCachedFetch(
2485
- fetchChannelPromises,
2486
- () => guild.channels.fetch(__zero(channelId)).catch(() => null),
2487
- key
2488
- );
2489
- if (type && channel?.type !== type) return null;
2490
- return channel;
2491
- }
2492
- async function fetchMessage(channel, messageId) {
2493
- if (!messageId) return null;
2494
- const key = `${channel.guild.id}-${messageId}`;
2495
- const cached = channel.messages.cache.get(__zero(messageId));
2496
- if (cached) return cached;
2497
- return createCachedFetch(fetchMessagePromises, () => channel.messages.fetch(__zero(messageId)).catch(() => null), key);
2498
- }
2499
- async function fetchRole(guild, roleId) {
2500
- if (!roleId) return null;
2501
- const key = `${guild.id}-${roleId}`;
2502
- const cached = guild.roles.cache.get(__zero(roleId));
2503
- if (cached) return cached;
2504
- return createCachedFetch(fetchRolePromises, () => guild.roles.fetch(__zero(roleId)).catch(() => null), key);
2505
- }
2506
-
2507
- // src/types/status.ts
2508
- var import_discord10 = require("discord.js");
2509
- var StatusType = /* @__PURE__ */ ((StatusType2) => {
2510
- StatusType2["DND"] = "dnd";
2511
- StatusType2["Idle"] = "idle";
2512
- StatusType2["Online"] = "online";
2513
- StatusType2["Invisible"] = "invisible";
2514
- return StatusType2;
2515
- })(StatusType || {});
2516
- var defaultPresence = {
2517
- production: {
2518
- interval: 6e4,
2519
- randomize: false,
2520
- activity: [
2521
- { status: "online" /* Online */, type: import_discord10.ActivityType.Custom, name: "Need help? Use /help or !help" },
2522
- { status: "online" /* Online */, type: import_discord10.ActivityType.Custom, name: "Join our community!" },
2523
- { status: "online" /* Online */, type: import_discord10.ActivityType.Watching, name: "\u2728 $GUILD_COUNT servers" }
2524
- ]
2525
- },
2526
- development: {
2527
- activity: { status: "dnd" /* DND */, type: import_discord10.ActivityType.Custom, name: "In development!" }
2528
- }
2529
- };
2530
- function createVimcordStatusConfig(options = {}) {
2531
- return deepMerge({ ...defaultPresence }, options);
2532
- }
2533
-
2534
- // src/modules/status.manager.ts
2535
- var import_node_events = __toESM(require("events"));
2536
- var import_qznt3 = require("qznt");
2537
- var StatusManager = class {
2538
- client;
2539
- logger;
2540
- emitter = new import_node_events.default();
2541
- lastActivity = null;
2542
- lastActivityIndex = 0;
2543
- task = null;
2544
- constructor(client) {
2545
- this.client = client;
2546
- this.logger = new Logger({ prefixEmoji: "\u{1F4AC}", prefix: `StatusManager (i${this.client.clientId})` });
2547
- this.emitter.on("changed", (activity) => {
2548
- if (this.client.config.app.verbose) {
2549
- this.logger.debug(`Status changed to '${activity.name}'`);
2550
- }
2551
- });
2552
- this.emitter.on("cleared", () => {
2553
- if (this.client.config.app.verbose) {
2554
- this.logger.debug("Status cleared");
2555
- }
2556
- });
2557
- }
2558
- clearData() {
2559
- this.task?.stop();
2560
- this.task = null;
2561
- this.lastActivity = null;
2562
- this.lastActivityIndex = 0;
2563
- return this;
2564
- }
2565
- async getReadyClient() {
2566
- const client = await Vimcord.getReadyInstance(this.client.clientId);
2567
- if (!client.user) throw new Error("Cannot manage the client's activity when its user is not hydrated");
2568
- return client;
2569
- }
2570
- async formatActivityName(name) {
2571
- name = name.replace("$USER_COUNT", import_qznt3.$.format.number(this.client.users.cache.size)).replace("$GUILD_COUNT", import_qznt3.$.format.number(this.client.guilds.cache.size)).replace(
2572
- "$INVITE",
2573
- this.client.config.staff.guild.inviteUrl ? this.client.config.staff.guild.inviteUrl : "<STAFF_INVITE_URL_NOT_SET>"
2574
- );
2575
- if (name.includes("$STAFF_GUILD_MEMBER_COUNT")) {
2576
- await fetchGuild(this.client, this.client.config.staff.guild.id).then((guild) => {
2577
- if (!guild) return name = name.replace("$STAFF_GUILD_MEMBER_COUNT", "<STAFF_GUILD_NOT_FOUND>");
2578
- name = name.replace("$STAFF_GUILD_MEMBER_COUNT", import_qznt3.$.format.number(guild.members.cache.size));
2579
- }).catch((err) => this.logger.error("Failed to fetch the staff guild", err));
2580
- }
2581
- return name;
2582
- }
2583
- async setActivity(activity) {
2584
- const client = await this.getReadyClient();
2585
- activity.name = await this.formatActivityName(activity.name);
2586
- client.user.setStatus(activity.status);
2587
- client.user.setActivity({ name: activity.name, type: activity.type, url: activity.streamUrl });
2588
- this.emitter.emit("changed", activity);
2589
- }
2590
- async statusRotationTask(clientStatus) {
2591
- let activity;
2592
- if (clientStatus.randomize && Array.isArray(clientStatus.activity)) {
2593
- activity = import_qznt3.$.rnd.choice(clientStatus.activity, { not: this.lastActivity });
2594
- this.lastActivity = activity;
2595
- } else {
2596
- const activityIndex = (this.lastActivityIndex + 1) % clientStatus.activity.length;
2597
- this.lastActivityIndex = activityIndex;
2598
- activity = clientStatus.activity[activityIndex];
2599
- }
2600
- await this.setActivity(activity);
2601
- this.emitter.emit("rotation", activity);
2602
- }
2603
- async scheduleStatusRotation(clientStatus) {
2604
- if (!clientStatus.interval) throw new Error("Cannot create client activity interval without interval time");
2605
- this.task?.stop();
2606
- this.task = null;
2607
- this.task = new import_qznt3.$.Loop(() => this.statusRotationTask(clientStatus), import_qznt3.$.math.ms(clientStatus.interval), true);
2608
- this.start();
2609
- }
2610
- start() {
2611
- if (this.task) {
2612
- this.task.start();
2613
- this.emitter.emit("started", this.task);
2614
- }
2615
- return this;
2616
- }
2617
- pause() {
2618
- if (this.task) {
2619
- this.task.stop();
2620
- this.emitter.emit("paused", this.task);
2621
- }
2622
- return this;
2623
- }
2624
- async set(status) {
2625
- const statusConfig = createVimcordStatusConfig(status);
2626
- let clientStatus;
2627
- if (this.client.config.app.devMode) {
2628
- clientStatus = statusConfig.development;
2629
- } else {
2630
- clientStatus = statusConfig.production;
2631
- }
2632
- if (!clientStatus.interval) {
2633
- await this.setActivity(Array.isArray(clientStatus.activity) ? clientStatus.activity[0] : clientStatus.activity);
2634
- } else {
2635
- await this.scheduleStatusRotation(clientStatus);
2636
- }
2637
- return this;
2638
- }
2639
- async destroy() {
2640
- if (this.task) {
2641
- this.task.stop();
2642
- this.task = null;
2643
- this.emitter.emit("destroyed");
2644
- await this.clear();
2645
- }
2646
- return this;
2647
- }
2648
- async clear() {
2649
- const client = await this.getReadyClient();
2650
- this.clearData();
2651
- client.user.setActivity({ name: "" });
2652
- this.emitter.emit("cleared");
2653
- return this;
2654
- }
2655
- };
2656
-
2657
- // src/client/Vimcord.ts
2658
- var import_discord11 = require("discord.js");
2659
- var import_dotenv = require("dotenv");
2660
- var import_node_crypto3 = require("crypto");
2661
- var import_node_events2 = __toESM(require("events"));
2662
- var import_qznt4 = require("qznt");
2663
- var Vimcord = class _Vimcord extends import_discord11.Client {
2664
- static instances = /* @__PURE__ */ new Map();
2665
- static emitter = new import_node_events2.default();
2666
- clientStartingPromise = null;
2667
- static create(optionsOrConfig, features, config) {
2668
- if ("options" in optionsOrConfig) {
2669
- const { options, features: features2, config: config2 } = optionsOrConfig;
2670
- return new _Vimcord(options, features2, config2);
2671
- } else {
2672
- return new _Vimcord(optionsOrConfig, features, config);
2673
- }
2674
- }
2675
- /**
2676
- * Returns an instance of Vimcord.
2677
- * @param clientId [default: 0]
2678
- */
2679
- static getInstance(clientId) {
2680
- if (clientId === void 0) {
2681
- return _Vimcord.instances.values().next().value;
2682
- }
2683
- return _Vimcord.instances.get(clientId);
2684
- }
2685
- /**
2686
- * Waits for a Vimcord instance to be ready.
2687
- * @param clientId [default: 0]
2688
- * @param timeoutMs [default: 60000]
2689
- */
2690
- static async getReadyInstance(clientId, timeoutMs = 6e4) {
2691
- const client = _Vimcord.getInstance(clientId);
2692
- if (client?.isReady()) {
2693
- _Vimcord.emitter.emit("ready", client);
2694
- return client;
2695
- }
2696
- if (client) {
2697
- return new Promise((resolve, reject) => {
2698
- const timeout = setTimeout(() => {
2699
- _Vimcord.emitter.off("ready", listener);
2700
- reject(new Error(`Client (i${clientId ?? 0}) timed out waiting for ready`));
2701
- }, timeoutMs);
2702
- const listener = (c) => {
2703
- if (c.clientId === (clientId ?? 0)) {
2704
- clearTimeout(timeout);
2705
- _Vimcord.emitter.off("ready", listener);
2706
- resolve(c);
2707
- }
2708
- };
2709
- client.once("clientReady", () => {
2710
- clearTimeout(timeout);
2711
- _Vimcord.emitter.emit("ready", client);
2712
- resolve(client);
2713
- });
2714
- });
2715
- }
2716
- return new Promise((resolve, reject) => {
2717
- const timeout = setTimeout(() => {
2718
- _Vimcord.emitter.off("ready", listener);
2719
- reject(new Error(`Vimcord instance (i${clientId ?? 0}) failed to initialize within ${timeoutMs / 1e3}s.`));
2720
- }, timeoutMs);
2721
- const listener = (c) => {
2722
- if (c.clientId === (clientId ?? 0)) {
2723
- clearTimeout(timeout);
2724
- _Vimcord.emitter.off("ready", listener);
2725
- resolve(c);
2726
- }
2727
- };
2728
- _Vimcord.emitter.on("ready", listener);
2729
- });
2730
- }
2731
- uuid = (0, import_node_crypto3.randomUUID)();
2732
- clientId = _Vimcord.instances.size;
2733
- clientOptions;
2734
- features;
2735
- config;
2736
- status;
2737
- events;
2738
- commands;
2739
- db;
2740
- logger = clientLoggerFactory(this);
2741
- error;
2742
- constructor(options, features = {}, config = {}) {
2743
- super(options);
2744
- this.clientOptions = options;
2745
- this.features = features;
2746
- this.config = defineVimcordConfig(config);
2747
- this.error = new VimcordErrorHandler(this);
2748
- this.status = new StatusManager(this);
2749
- this.events = new EventManager(this);
2750
- this.commands = new CommandManager(this);
2751
- this.once("clientReady", (client) => this.logger.clientReady(client.user.tag, client.guilds.cache.size));
2752
- _Vimcord.instances.set(this.clientId, this);
2753
- }
2754
- /** Builds the client by importing modules and registering builtin handlers. */
2755
- async build() {
2756
- this.configure("app", this.config.app);
2757
- this.configure("staff", this.config.staff);
2758
- this.configure("slashCommands", this.config.slashCommands);
2759
- this.configure("prefixCommands", this.config.prefixCommands);
2760
- this.configure("contextCommands", this.config.contextCommands);
2761
- if (this.features.useGlobalErrorHandlers) {
2762
- this.error.setupGlobalHandlers();
2763
- }
2764
- if (this.features.useDefaultSlashCommandHandler) {
2765
- this.events.register(slashCommandHandler);
2766
- }
2767
- if (this.features.useDefaultContextCommandHandler) {
2768
- this.events.register(contextCommandHandler);
2769
- }
2770
- if (this.features.useDefaultPrefixCommandHandler) {
2771
- this.events.register(prefixCommandHandler);
2772
- }
2773
- if (this.features.importModules) {
2774
- const importModules = this.features.importModules;
2775
- await Promise.all([
2776
- importModules.events && this.importModules("events", importModules.events),
2777
- importModules.slashCommands && this.importModules("slashCommands", importModules.slashCommands),
2778
- importModules.prefixCommands && this.importModules("prefixCommands", importModules.prefixCommands),
2779
- importModules.contextCommands && this.importModules("contextCommands", importModules.contextCommands)
2780
- ]);
2781
- }
2782
- this.logger.clientBanner(this);
2783
- if (this.config.app.enableCLI) {
2784
- VimcordCLI.setMode("on");
2785
- }
2786
- return this;
2787
- }
2788
- /** Current app name */
2789
- // prettier-ignore
2790
- get $name() {
2791
- return this.config.app.name;
2792
- }
2793
- // prettier-ignore
2794
- set $name(name) {
2795
- this.config.app.name = name;
2796
- }
2797
- /** Current app version */
2798
- // prettier-ignore
2799
- get $version() {
2800
- return this.config.app.version;
2801
- }
2802
- // prettier-ignore
2803
- set $version(version2) {
2804
- this.config.app.version = version2;
2805
- }
2806
- /** Current dev mode state */
2807
- // prettier-ignore
2808
- get $devMode() {
2809
- return this.config.app.devMode;
2810
- }
2811
- // prettier-ignore
2812
- set $devMode(mode) {
2813
- this.config.app.devMode = mode;
2814
- }
2815
- /** Current verbose mode state */
2816
- // prettier-ignore
2817
- get $verboseMode() {
2818
- return this.config.app.verbose;
2819
- }
2820
- // prettier-ignore
2821
- set $verboseMode(mode) {
2822
- this.config.app.verbose = mode;
2823
- }
2824
- /** Returns the options, features, and config of this client. */
2825
- toJSON() {
2826
- return { options: this.clientOptions, features: this.features, config: this.config };
2827
- }
2828
- /** Makes a clone of this client. */
2829
- clone() {
2830
- const { options, features, config } = this.toJSON();
2831
- return new _Vimcord(options, features, config);
2832
- }
2833
- /**
2834
- * Modifies a client config.
2835
- * @param type The type of config to modify.
2836
- * @param options The options to set for the config.
2837
- */
2838
- configure(type, options = {}) {
2839
- this.config[type] = configSetters[type](options, this.config[type]);
2840
- return this;
2841
- }
2842
- /**
2843
- * Allows Vimcord to handle environment variables using [dotenv](https://www.npmjs.com/package/dotenv).
2844
- * @param options Options for dotenv
2845
- * @see https://www.npmjs.com/package/dotenv
2846
- */
2847
- useEnv(options) {
2848
- this.logger.plugin("ENV", "Using", "dotenv");
2849
- (0, import_dotenv.configDotenv)({ quiet: true, ...options });
2850
- return this;
2851
- }
2852
- /**
2853
- * Connects to a database.
2854
- * @param db The database manager to use.
2855
- */
2856
- async useDatabase(db) {
2857
- this.db = db;
2858
- this.logger.plugin("DATABASE", "Using", db.moduleName);
2859
- return this.db.connect();
2860
- }
2861
- /**
2862
- * Imports modules into the client.
2863
- * @param type The type of modules to import.
2864
- * @param options The options to import the module with.
2865
- * @param set Replaces already imported modules with the ones found.
2866
- */
2867
- async importModules(type, options, set) {
2868
- await moduleImporters[type](this, options, set);
2869
- return this;
2870
- }
2871
- /**
2872
- * Fetches a user from the client, checking the cache first.
2873
- * @param userId The ID of the user to fetch.
2874
- */
2875
- async fetchUser(userId) {
2876
- const client = await _Vimcord.getReadyInstance(this.clientId);
2877
- return fetchUser(client, userId);
2878
- }
2879
- /**
2880
- * Fetches a guild from the client, checking the cache first.
2881
- * @param guildId The ID of the guild to fetch.
2882
- */
2883
- async fetchGuild(guildId) {
2884
- const client = await _Vimcord.getReadyInstance(this.clientId);
2885
- return fetchGuild(client, guildId);
2886
- }
2887
- async start(tokenOrPreHook, callback) {
2888
- if (this.clientStartingPromise) return this.clientStartingPromise;
2889
- const execute = async () => {
2890
- let token = typeof tokenOrPreHook === "string" ? tokenOrPreHook : void 0;
2891
- token ??= this.$devMode ? process.env.TOKEN_DEV : process.env.TOKEN;
2892
- if (!token) {
2893
- throw new Error(
2894
- `TOKEN Missing: ${this.$devMode ? "devMode is enabled, but TOKEN_DEV is not set" : "TOKEN not set"}`
2895
- );
2896
- }
2897
- await this.build();
2898
- try {
2899
- const stopLoader = this.logger.loader("Connecting to Discord...");
2900
- const loginResult = await import_qznt4.$.async.retry(() => super.login(token), {
2901
- retries: this.features.maxLoginAttempts ?? 3,
2902
- delay: 1e3
2903
- });
2904
- stopLoader("Connected to Discord ");
2905
- this.logger.debug("Waiting for the client to be ready...");
2906
- if (typeof tokenOrPreHook === "function") {
2907
- await tokenOrPreHook(this);
2908
- } else {
2909
- await callback?.(this);
2910
- }
2911
- return loginResult;
2912
- } catch (err) {
2913
- this.logger.error(
2914
- `Failed to log into Discord after ${this.features.maxLoginAttempts} attempt(s)`,
2915
- err
2916
- );
2917
- return null;
2918
- } finally {
2919
- this.clientStartingPromise = null;
2920
- }
2921
- };
2922
- this.clientStartingPromise = execute();
2923
- return this.clientStartingPromise;
2924
- }
2925
- /** Destroys the client and disconnects from Discord. */
2926
- async kill() {
2927
- await super.destroy();
2928
- _Vimcord.instances.delete(this.clientId);
2929
- this.logger.debug("Logged out of Discord");
2930
- }
2931
- };
2932
-
2933
- // src/client/error.handler.ts
2934
- var ErrorHandler = class {
2935
- client;
2936
- constructor(client) {
2937
- this.client = client;
2938
- }
2939
- /** Handles command errors - sends error embed to user, then rethrows. */
2940
- async handleCommandError(error, guild, messageOrInteraction) {
2941
- await sendCommandErrorEmbed(this.client, error, guild, messageOrInteraction);
2942
- throw error;
2943
- }
2944
- /** Handles internal Vimcord errors - logs with [Vimcord] prefix. */
2945
- handleVimcordError(error, context) {
2946
- this.client.logger.error(`[Vimcord] [${context}]`, error);
2947
- }
2948
- /** Sets up global process error handlers. */
2949
- setupGlobalHandlers() {
2950
- process.on("uncaughtException", (err) => this.handleVimcordError(err, "Uncaught Exception"));
2951
- process.on("unhandledRejection", (err) => this.handleVimcordError(err, "Unhandled Rejection"));
2952
- process.on("exit", (code) => this.client.logger.debug(`Process exited with code ${code}`));
2953
- this.client.on("error", (err) => this.handleVimcordError(err, "Client Error"));
2954
- this.client.on("shardError", (err) => this.handleVimcordError(err, "Client Shard Error"));
2955
- }
2956
- };
2957
-
2958
- // src/db/mongo/mongo.database.ts
2959
- var import_mongoose = __toESM(require("mongoose"));
2960
- var import_node_events3 = __toESM(require("events"));
2961
- var import_qznt5 = require("qznt");
2962
- try {
2963
- import("mongoose");
2964
- } catch {
2965
- throw new Error("MongoDatabase requires the mongoose package, install it with `npm install mongoose`");
2966
- }
2967
- var MongoDatabase = class _MongoDatabase {
2968
- static instances = /* @__PURE__ */ new Map();
2969
- static emitter = new import_node_events3.default();
2970
- moduleName = "MongoDatabase";
2971
- clientId;
2972
- client;
2973
- mongoose;
2974
- isConnecting = false;
2975
- /**
2976
- * Returns an instance of MongoDatabase.
2977
- * @param clientId [default: 0]
2978
- */
2979
- static getInstance(clientId) {
2980
- const id = (typeof clientId === "number" ? clientId : clientId?.clientId) ?? 0;
2981
- return _MongoDatabase.instances.get(id);
2982
- }
2983
- /**
2984
- * Waits for a MongoDatabase instance to be ready. First waiting for the instance to initialize if it doesn't exist.
2985
- * @param clientId [default: 0]
2986
- * @param timeoutMs [default: 60000]
2987
- */
2988
- static async getReadyInstance(clientId, timeoutMs = 6e4) {
2989
- const existing = _MongoDatabase.getInstance(clientId);
2990
- if (existing) return await existing.waitForReady();
2991
- return new Promise((resolve, reject) => {
2992
- const timeout = setTimeout(() => {
2993
- _MongoDatabase.emitter.off("ready", listener);
2994
- reject(
2995
- new Error(
2996
- `MongoDatabase instance (${clientId}) failed to initialize within ${timeoutMs / 1e3}s. Check your connection logic.`
2997
- )
2998
- );
2999
- }, timeoutMs);
3000
- const listener = (db) => {
3001
- if (db.clientId === clientId) {
3002
- clearTimeout(timeout);
3003
- _MongoDatabase.emitter.off("ready", listener);
3004
- resolve(db);
3005
- }
3006
- };
3007
- _MongoDatabase.emitter.on("ready", listener);
3008
- });
3009
- }
3010
- /**
3011
- * Starts a new Mongo client session.
3012
- * @param options Options for the new session
3013
- * @param clientId [default: 0]
3014
- */
3015
- static async startSession(options, clientId) {
3016
- return (await _MongoDatabase.getReadyInstance(clientId))?.startSession(options);
3017
- }
3018
- constructor(client, options) {
3019
- this.client = client;
3020
- this.mongoose = new import_mongoose.default.Mongoose(options);
3021
- this.clientId = this.client.clientId;
3022
- _MongoDatabase.instances.set(this.clientId, this);
3023
- }
3024
- get connection() {
3025
- return this.mongoose.connection;
3026
- }
3027
- get isReady() {
3028
- return this.connection.readyState === import_mongoose.ConnectionStates.connected;
3029
- }
3030
- async waitForReady() {
3031
- if (!this.isReady && this.isConnecting) {
3032
- return new Promise((resolve) => _MongoDatabase.emitter.once("ready", (db) => resolve(db)));
3033
- }
3034
- return this;
3035
- }
3036
- async connect(uri, connectionOptions, options = {}) {
3037
- const { maxRetries = 3 } = options;
3038
- if (this.isReady) {
3039
- return true;
3040
- }
3041
- if (!this.isReady && this.isConnecting) {
3042
- return new Promise((resolve) => _MongoDatabase.emitter.once("ready", () => resolve(true)));
3043
- }
3044
- const connectionUri = uri ?? (this.client.config.app.devMode ? process.env.MONGO_URI_DEV : process.env.MONGO_URI);
3045
- options = { ...options, maxRetries: options.maxRetries ?? 3 };
3046
- if (!connectionUri) {
3047
- throw new Error(
3048
- `MONGO_URI Missing: ${this.client.config.app.devMode ? "DEV MODE is enabled, but MONGO_URI_DEV is not set" : "MONGO_URI not set"}`
3049
- );
3050
- }
3051
- this.isConnecting = true;
3052
- try {
3053
- const stopLoader = this.client.logger.loader("Connecting to MongoDB...");
3054
- await import_qznt5.$.async.retry(() => this.mongoose.connect(connectionUri, { autoIndex: true, ...connectionOptions }), {
3055
- retries: maxRetries
3056
- });
3057
- _MongoDatabase.emitter.emit("ready", this);
3058
- stopLoader("Connected to MongoDB ");
3059
- } catch (err) {
3060
- this.client.logger.error(`Failed to connect to MongoDB after ${maxRetries} attempt(s)`, err);
3061
- } finally {
3062
- this.isConnecting = false;
3063
- }
3064
- return true;
3065
- }
3066
- async disconnect() {
3067
- await this.mongoose.disconnect();
3068
- }
3069
- async startSession(options) {
3070
- return this.mongoose.startSession(options);
3071
- }
3072
- async useTransaction(fn) {
3073
- const session = await this.startSession();
3074
- session.startTransaction();
3075
- try {
3076
- await fn(session);
3077
- await session.commitTransaction();
3078
- } catch (err) {
3079
- await session.abortTransaction();
3080
- throw err;
3081
- } finally {
3082
- await session.endSession();
3083
- }
3084
- }
3085
- };
3086
-
3087
- // src/db/mongo/mongoSchema.builder.ts
3088
- var import_mongoose2 = require("mongoose");
3089
- var import_qznt6 = require("qznt");
3090
- try {
3091
- import("mongoose");
3092
- } catch {
3093
- throw new Error("MongoSchemaBuilder requires the mongoose package, install it with `npm install mongoose`");
3094
- }
3095
- function createMongoSchema(collection, definition, options) {
3096
- return MongoSchemaBuilder.create(collection, definition, options);
3097
- }
3098
- function createMongoPlugin(plugin) {
3099
- return plugin;
3100
- }
3101
- var MongoSchemaBuilder = class _MongoSchemaBuilder {
3102
- static globalPlugins = [];
3103
- collection;
3104
- instanceId;
3105
- schema;
3106
- model = null;
3107
- db = null;
3108
- logger;
3109
- compilingModel = null;
3110
- static create(collection, definition, options) {
3111
- return new _MongoSchemaBuilder(collection, definition, options);
3112
- }
3113
- /**
3114
- * Registers a plugin globally to be used by all future schemas.
3115
- */
3116
- static use(plugin) {
3117
- this.globalPlugins.push(plugin);
3118
- }
3119
- constructor(collection, definition, options = {}) {
3120
- const { instanceId = 0, ...schemaOptions } = options;
3121
- this.instanceId = instanceId;
3122
- this.collection = collection;
3123
- this.schema = new import_mongoose2.Schema(definition, { versionKey: false, ...schemaOptions });
3124
- this.logger = new Logger({
3125
- prefixEmoji: "\u{1F96D}",
3126
- prefix: `MongoSchema (i${instanceId}) [${collection}]`,
3127
- colors: { primary: "#F29B58" }
3128
- });
3129
- for (const plugin of _MongoSchemaBuilder.globalPlugins) {
3130
- plugin(this);
3131
- }
3132
- }
3133
- async getModel() {
3134
- if (this.model) return this.model;
3135
- if (this.compilingModel) return this.compilingModel;
3136
- const fn = async () => {
3137
- this.db = await MongoDatabase.getReadyInstance(this.instanceId) || null;
3138
- if (!this.db) {
3139
- throw new Error(`MongoDatabase instance (${this.instanceId}) not found for schema ${this.collection}`);
3140
- }
3141
- this.model = this.db.mongoose.model(this.collection, this.schema);
3142
- if (this.db?.client.config.app.verbose) {
3143
- this.logger.debug(`Compiled! | ${this.db?.client.config.app.name}`);
3144
- }
3145
- this.compilingModel = null;
3146
- return this.model;
3147
- };
3148
- this.compilingModel = fn();
3149
- const res = await this.compilingModel;
3150
- return res;
3151
- }
3152
- extend(extras) {
3153
- for (const [key, fn] of Object.entries(extras)) {
3154
- if (typeof fn === "function") {
3155
- this[key] = function(...args) {
3156
- return fn.call(this, ...args);
3157
- };
3158
- }
3159
- }
3160
- return this;
3161
- }
3162
- /**
3163
- * Registers a plugin to just this instance.
3164
- */
3165
- use(plugin) {
3166
- plugin(this);
3167
- return this;
3168
- }
3169
- /**
3170
- * Handles model resolution, connection ready, and retries.
3171
- * @param fn The function to execute
3172
- * @param maxRetries [default: 3]
3173
- */
3174
- async execute(fn, maxRetries = 3) {
3175
- return (0, import_qznt6.retry)(
3176
- async () => {
3177
- const model = await this.getModel();
3178
- return await fn(model);
3179
- },
3180
- { retries: maxRetries }
3181
- );
3182
- }
3183
- async startSession(options) {
3184
- return this.execute(async () => {
3185
- if (!this.db) throw new Error("No database instance found");
3186
- return await this.db.startSession(options);
3187
- });
3188
- }
3189
- async useTransaction(fn) {
3190
- return this.execute(async (model) => {
3191
- if (!this.db) throw new Error("No database instance found");
3192
- return await this.db.useTransaction((session) => fn(session, model));
3193
- });
3194
- }
3195
- async createUniqueId(collisionPath, fn, maxRetries = 10) {
3196
- return this.execute(async (model) => {
3197
- let id;
3198
- let tries = 0;
3199
- do {
3200
- if (tries >= maxRetries) throw new Error(`Failed to generate a unique ID after ${tries} attempt(s)`);
3201
- id = fn();
3202
- tries++;
3203
- } while (await model.exists({ [collisionPath]: id }));
3204
- return id;
3205
- });
3206
- }
3207
- async count(filter, options) {
3208
- return this.execute(async (model) => model.countDocuments(filter, options));
3209
- }
3210
- async exists(filter) {
3211
- return this.execute(async (model) => !!await model.exists(filter));
3212
- }
3213
- async create(query, options) {
3214
- return this.execute(async (model) => model.create(query, options));
3215
- }
3216
- async upsert(filter, query, options) {
3217
- return this.execute(
3218
- async (model) => model.findOneAndUpdate(filter, query, { returnDocument: "after", ...options, upsert: true })
3219
- );
3220
- }
3221
- async delete(filter, options) {
3222
- return this.execute(async (model) => model.deleteOne(filter, options));
3223
- }
3224
- async deleteAll(filter, options) {
3225
- return this.execute(async (model) => model.deleteMany(filter, options));
3226
- }
3227
- async distinct(key, filter, options) {
3228
- return this.execute(async (model) => model.distinct(key, filter, options));
3229
- }
3230
- async fetch(filter, projection, options) {
3231
- const result = await this.execute(
3232
- async (model) => model.findOne(filter, projection, { ...options, lean: options?.lean ?? true })
3233
- );
3234
- if (options?.required && !result) throw new Error("Document not found");
3235
- return result;
3236
- }
3237
- async fetchAll(filter = {}, projection, options) {
3238
- return this.execute(async (model) => model.find(filter, projection, { ...options, lean: options?.lean ?? true }));
3239
- }
3240
- async update(filter, update, options) {
3241
- return this.execute(
3242
- async (model) => model.findOneAndUpdate(filter, update, { ...options, lean: options?.lean ?? true })
3243
- );
3244
- }
3245
- async updateAll(filter, update, options) {
3246
- return this.execute(async (model) => model.updateMany(filter, update, options));
3247
- }
3248
- async aggregate(pipeline, options) {
3249
- return this.execute(async (model) => {
3250
- const result = await model.aggregate(pipeline, options);
3251
- return result?.length ? result : [];
3252
- });
3253
- }
3254
- async bulkWrite(ops, options) {
3255
- return this.execute(async (model) => model.bulkWrite(ops, options));
3256
- }
3257
- async bulkSave(docs, options) {
3258
- return this.execute(async (model) => model.bulkSave(docs, options));
3259
- }
3260
- };
3261
-
3262
- // src/tools/BetterCollector.ts
3263
- var CollectorTimeoutType = /* @__PURE__ */ ((CollectorTimeoutType2) => {
3264
- CollectorTimeoutType2[CollectorTimeoutType2["DisableComponents"] = 0] = "DisableComponents";
3265
- CollectorTimeoutType2[CollectorTimeoutType2["DeleteMessage"] = 1] = "DeleteMessage";
3266
- CollectorTimeoutType2[CollectorTimeoutType2["DoNothing"] = 2] = "DoNothing";
3267
- return CollectorTimeoutType2;
3268
- })(CollectorTimeoutType || {});
3269
- var BetterCollector = class _BetterCollector {
3270
- message;
3271
- collector;
3272
- options;
3273
- activeUsers = /* @__PURE__ */ new Set();
3274
- participantWarningCooldowns = /* @__PURE__ */ new Map();
3275
- config;
3276
- events = {
3277
- collectId: /* @__PURE__ */ new Map(),
3278
- collect: [],
3279
- end: [],
3280
- timeout: []
3281
- };
3282
- static create(message, options) {
3283
- return new _BetterCollector(message, options);
3284
- }
3285
- async validateParticipant(interaction, participants) {
3286
- const allowedParticipants = participants || this.options?.participants || [];
3287
- if (!allowedParticipants.length) return true;
3288
- const isAllowed = allowedParticipants.some((user) => {
3289
- if (typeof user === "string") return user === interaction.user.id;
3290
- if (typeof user === "object" && "id" in user) return user.id === interaction.user.id;
3291
- return false;
3292
- });
3293
- if (!isAllowed) {
3294
- const now = Date.now();
3295
- const lastWarned = this.participantWarningCooldowns.get(interaction.user.id) || 0;
3296
- if (now - lastWarned > this.config.collector.notAParticipantWarningCooldown) {
3297
- this.participantWarningCooldowns.set(interaction.user.id, now);
3298
- if (this.config.collector.notAParticipantMessage) {
3299
- await interaction.reply({
3300
- content: this.config.collector.notAParticipantMessage,
3301
- flags: "Ephemeral"
3302
- }).catch(() => {
3303
- });
3304
- } else {
3305
- await interaction.deferUpdate().catch(() => {
3306
- });
3307
- }
3308
- } else {
3309
- await interaction.deferUpdate().catch(() => {
3310
- });
3311
- }
3312
- }
3313
- return isAllowed;
3314
- }
3315
- build() {
3316
- if (!this.message) return;
3317
- if (this.collector) return;
3318
- this.collector = this.message.createMessageComponentCollector({
3319
- idle: this.options?.timeout ? void 0 : this.options?.idle ?? this.config.timeouts.collectorIdle,
3320
- time: this.options?.timeout ?? this.config.timeouts.collectorTimeout,
3321
- componentType: this.options?.type,
3322
- max: this.options?.max,
3323
- maxComponents: this.options?.maxComponents,
3324
- maxUsers: this.options?.maxUsers
3325
- });
3326
- this.setupListeners();
3327
- }
3328
- setupListeners() {
3329
- if (!this.collector) return;
3330
- this.collector.on("collect", async (interaction) => {
3331
- if (this.options?.userLock && this.activeUsers.has(interaction.user.id)) {
3332
- return interaction.reply({ content: this.config.collector.userLockMessage, flags: "Ephemeral" }).catch(() => {
3333
- });
3334
- }
3335
- if (this.options?.userLock) {
3336
- this.activeUsers.add(interaction.user.id);
3337
- }
3338
- const globalListeners = this.events.collect;
3339
- const idListeners = this.events.collectId.get(interaction.customId) || [];
3340
- const allListeners = [...globalListeners, ...idListeners];
3341
- const shouldBeDeferred = allListeners.find((l) => l.options?.defer)?.options?.defer;
3342
- const validListeners = [];
3343
- for (const listener of allListeners) {
3344
- const isAllowed = await this.validateParticipant(interaction, listener.options?.participants);
3345
- if (isAllowed) validListeners.push(listener);
3346
- }
3347
- if (validListeners.length === 0) return;
3348
- try {
3349
- if (shouldBeDeferred) {
3350
- if (typeof shouldBeDeferred === "object") {
3351
- if (shouldBeDeferred.update) {
3352
- await interaction.deferUpdate().catch(Boolean);
3353
- } else {
3354
- await interaction.deferReply({ flags: shouldBeDeferred.ephemeral ? "Ephemeral" : void 0 }).catch(Boolean);
3355
- }
3356
- } else {
3357
- await interaction.deferReply().catch(Boolean);
3358
- }
3359
- }
3360
- if (this.options?.sequential) {
3361
- for (const listener of allListeners) {
3362
- try {
3363
- const isAllowed = await this.validateParticipant(interaction, listener.options?.participants);
3364
- if (!isAllowed) return;
3365
- await listener.fn(interaction).finally(() => listener.options?.finally?.(interaction));
3366
- } catch (err) {
3367
- this.handleListenerError(err);
3368
- }
3369
- }
3370
- } else {
3371
- Promise.all(
3372
- allListeners.map((l) => {
3373
- const isAllowed = this.validateParticipant(interaction, l.options?.participants);
3374
- if (!isAllowed) return;
3375
- return l.fn(interaction).catch(this.handleListenerError).finally(() => l.options?.finally?.(interaction));
3376
- })
3377
- );
3378
- }
3379
- } finally {
3380
- if (this.options?.userLock) {
3381
- this.activeUsers.delete(interaction.user.id);
3382
- }
3383
- }
3384
- });
3385
- this.collector.on("end", async (collected, reason) => {
3386
- if (this.options?.sequential) {
3387
- for (const listener of this.events.end) {
3388
- try {
3389
- await listener.fn(collected, reason);
3390
- } catch (err) {
3391
- this.handleListenerError(err);
3392
- }
3393
- }
3394
- } else {
3395
- Promise.all(this.events.end.map((l) => l.fn(collected, reason).catch(this.handleListenerError)));
3396
- }
3397
- switch (this.options?.onTimeout) {
3398
- case 0 /* DisableComponents */:
3399
- if (!this.message?.editable) break;
3400
- try {
3401
- const disabledRows = this.message.components.map((row) => {
3402
- const updatedRow = row.toJSON();
3403
- if ("components" in updatedRow) {
3404
- updatedRow.components = updatedRow.components.map((component) => ({
3405
- ...component,
3406
- disabled: true
3407
- }));
3408
- }
3409
- return updatedRow;
3410
- });
3411
- await this.message.edit({ components: disabledRows });
3412
- } catch (err) {
3413
- if (!(err instanceof Error && err.message.includes("Unknown Message"))) {
3414
- this.handleListenerError(err);
3415
- }
3416
- }
3417
- break;
3418
- case 1 /* DeleteMessage */:
3419
- if (!this.message?.deletable) break;
3420
- await this.message.delete().catch(Boolean);
3421
- break;
3422
- case 2 /* DoNothing */:
3423
- default:
3424
- break;
3425
- }
3426
- });
3427
- }
3428
- handleListenerError(err) {
3429
- console.error("[BetterCollector] Listener Error:", err);
3430
- }
3431
- constructor(message, options = {}) {
3432
- this.config = options.config ? createToolsConfig(options.config) : globalToolsConfig;
3433
- this.message = message || void 0;
3434
- this.options = options;
3435
- this.build();
3436
- }
3437
- on(idOrFunc, fnOrOptions, options) {
3438
- let finalFn;
3439
- let finalOptions;
3440
- let customId;
3441
- if (typeof idOrFunc === "function") {
3442
- finalFn = idOrFunc;
3443
- finalOptions = fnOrOptions;
3444
- } else {
3445
- if (typeof fnOrOptions !== "function") {
3446
- throw new Error("[BetterCollector] Second argument must be a function when a customId is provided.");
3447
- }
3448
- customId = idOrFunc;
3449
- finalFn = fnOrOptions;
3450
- finalOptions = options;
3451
- }
3452
- if (customId) {
3453
- const listeners = this.events.collectId.get(customId) || [];
3454
- listeners.push({ fn: finalFn, options: finalOptions });
3455
- this.events.collectId.set(customId, listeners);
3456
- } else {
3457
- this.events.collect.push({ fn: finalFn, options: finalOptions });
3458
- }
3459
- return this;
3460
- }
3461
- onEnd(fn, options) {
3462
- this.events.end.push({ fn, options });
3463
- return this;
3464
- }
3465
- /** Manually stop the collector and trigger cleanup */
3466
- stop(reason = "manual") {
3467
- this.collector?.stop(reason);
3468
- }
3469
- };
3470
-
3471
- // src/tools/BetterContainer.ts
3472
- var import_discord12 = require("discord.js");
3473
- var BetterContainer = class {
3474
- container = new import_discord12.ContainerBuilder();
3475
- data;
3476
- config;
3477
- constructor(data = {}) {
3478
- this.config = data.config ? createToolsConfig(data.config) : globalToolsConfig;
3479
- this.data = {
3480
- color: data.color ?? (this.config.devMode ? this.config.embedColorDev : this.config.embedColor),
3481
- ...data
3482
- };
3483
- this.build();
3484
- }
3485
- configure() {
3486
- if (this.data.color !== void 0) {
3487
- try {
3488
- const color = Array.isArray(this.data.color) ? this.data.color[Math.floor(Math.random() * this.data.color.length)] ?? null : this.data.color;
3489
- if (color) {
3490
- this.container.setAccentColor(parseInt(color.replace("#", ""), 16));
3491
- } else {
3492
- this.container.clearAccentColor();
3493
- }
3494
- } catch (error) {
3495
- console.error("[BetterContainer] Invalid color:", error);
3496
- }
3497
- }
3498
- }
3499
- build() {
3500
- this.configure();
3501
- }
3502
- setColor(color) {
3503
- this.data.color = color;
3504
- return this;
3505
- }
3506
- clearColor() {
3507
- this.data.color = null;
3508
- return this;
3509
- }
3510
- addSeparator(options) {
3511
- this.container.addSeparatorComponents((sb) => {
3512
- if (options?.divider !== void 0) sb.setDivider(options.divider);
3513
- if (options?.spacing !== void 0) sb.setSpacing(options.spacing);
3514
- return sb;
3515
- });
3516
- return this;
3517
- }
3518
- addText(text) {
3519
- this.container.addTextDisplayComponents(
3520
- (tdb) => tdb.setContent(Array.isArray(text) ? text.filter((t) => t !== null && t !== void 0).join("\n") : text)
3521
- );
3522
- return this;
3523
- }
3524
- addMedia(...media) {
3525
- this.container.addMediaGalleryComponents((mb) => {
3526
- for (const m of media) {
3527
- const urls = Array.isArray(m.url) ? m.url : [m.url];
3528
- for (const u of urls) {
3529
- mb.addItems((mgb) => {
3530
- mgb.setURL(u);
3531
- if (m.spoiler) mgb.setSpoiler(true);
3532
- if (m.description) mgb.setDescription(m.description);
3533
- return mgb;
3534
- });
3535
- }
3536
- }
3537
- return mb;
3538
- });
3539
- return this;
3540
- }
3541
- addSection(data) {
3542
- this.container.addSectionComponents((sb) => {
3543
- if (data.text) {
3544
- sb.addTextDisplayComponents(
3545
- (tdb) => tdb.setContent(
3546
- Array.isArray(data.text) ? data.text.filter((t) => t !== null && t !== void 0).join("\n") : data.text
3547
- )
3548
- );
3549
- }
3550
- if (data.thumbnail) sb.setThumbnailAccessory(new import_discord12.ThumbnailBuilder(data.thumbnail));
3551
- if (data.button) sb.setButtonAccessory(new import_discord12.ButtonBuilder(data.button));
3552
- return sb;
3553
- });
3554
- return this;
3555
- }
3556
- addActionRow(...components) {
3557
- this.container.addActionRowComponents(...components);
3558
- return this;
3559
- }
3560
- toJSON() {
3561
- return this.container.toJSON();
3562
- }
3563
- async send(handler, options = {}) {
3564
- this.build();
3565
- return await dynaSend(handler, {
3566
- ...options,
3567
- withResponse: true,
3568
- components: [this.container],
3569
- flags: Array.isArray(options.flags) ? [...options.flags, "IsComponentsV2"] : options.flags ? [options.flags, "IsComponentsV2"] : "IsComponentsV2"
3570
- });
3571
- }
3572
- };
3573
-
3574
- // src/tools/BetterModal.ts
3575
- var import_discord13 = require("discord.js");
3576
- var DEFAULT_CONFIG = {
3577
- timeout: 6e4
3578
- };
3579
- function createRandomId() {
3580
- return `v-${Math.random().toString(36).split(".")[1]}`;
3581
- }
3582
- var BetterModal = class _BetterModal {
3583
- customId;
3584
- components = /* @__PURE__ */ new Map();
3585
- labelComponents = [];
3586
- modal;
3587
- constructor(options) {
3588
- this.customId = options?.customId ?? createRandomId();
3589
- this.modal = new import_discord13.ModalBuilder().setCustomId(this.customId);
3590
- if (options?.title) this.setTitle(options.title);
3591
- if (options?.components?.length) this.addComponents(...options.components);
3592
- }
3593
- validateComponentLength() {
3594
- if ((this.components.size ?? 0) >= 25) {
3595
- throw new Error("[BetterModal] Modal can only have 25 components");
3596
- }
3597
- }
3598
- createComponentId() {
3599
- return `${this.customId}:${createRandomId()}`;
3600
- }
3601
- createLabelComponent(data) {
3602
- const component = new import_discord13.LabelBuilder().setLabel(data.label);
3603
- if (data.description) component.setDescription(data.description);
3604
- return component;
3605
- }
3606
- build() {
3607
- if (!this.modal.data.title) throw new Error("[BetterModal] Modal must have a title");
3608
- this.modal.setLabelComponents(this.labelComponents);
3609
- return this.modal;
3610
- }
3611
- clone() {
3612
- return new _BetterModal({
3613
- customId: this.customId,
3614
- title: this.modal.data.title,
3615
- components: Array.from(this.components.values())
3616
- });
3617
- }
3618
- toJSON() {
3619
- return this.build().toJSON();
3620
- }
3621
- /**
3622
- * Sets the title of the modal.
3623
- * @param title The title of the modal.
3624
- */
3625
- setTitle(title) {
3626
- this.modal.setTitle(title);
3627
- return this;
3628
- }
3629
- /** Sets components for the modal. */
3630
- setComponents(...components) {
3631
- this.components.clear();
3632
- this.labelComponents = [];
3633
- this.addComponents(...components);
3634
- return this;
3635
- }
3636
- /** Adds components to the modal. */
3637
- addComponents(...components) {
3638
- for (const component of components) {
3639
- if ("textInput" in component) {
3640
- this.addTextInput(component.textInput);
3641
- } else if ("checkbox" in component) {
3642
- this.addCheckbox(component.checkbox);
3643
- } else if ("checkboxGroup" in component) {
3644
- this.addCheckboxGroup(component.checkboxGroup);
3645
- } else if ("radioGroup" in component) {
3646
- this.addRadioGroup(component.radioGroup);
3647
- } else if ("stringSelect" in component) {
3648
- this.addStringSelect(component.stringSelect);
3649
- } else if ("channelSelect" in component) {
3650
- this.addChannelSelect(component.channelSelect);
3651
- } else if ("userSelect" in component) {
3652
- this.addUserSelect(component.userSelect);
3653
- } else if ("roleSelect" in component) {
3654
- this.addRoleSelect(component.roleSelect);
3655
- } else if ("mentionableSelect" in component) {
3656
- this.addMentionableSelect(component.mentionableSelect);
3657
- } else if ("fileUpload" in component) {
3658
- this.addFileUpload(component.fileUpload);
3659
- }
3660
- }
3661
- return this;
3662
- }
3663
- addTextInput(data) {
3664
- this.validateComponentLength();
3665
- const customId = data.customId ?? this.createComponentId();
3666
- const { label: _, ...textInputData } = data;
3667
- const textInput = new import_discord13.TextInputBuilder({ style: import_discord13.TextInputStyle.Short, required: false, ...textInputData, customId });
3668
- const label = this.createLabelComponent(data);
3669
- label.setTextInputComponent(textInput);
3670
- this.components.set(customId, { textInput: data });
3671
- this.labelComponents.push(label);
3672
- return this;
3673
- }
3674
- addStringSelect(data) {
3675
- this.validateComponentLength();
3676
- const customId = data.customId ?? this.createComponentId();
3677
- const select = new import_discord13.StringSelectMenuBuilder({ ...data, customId });
3678
- const label = this.createLabelComponent(data);
3679
- label.setStringSelectMenuComponent(select);
3680
- this.components.set(customId, { stringSelect: data });
3681
- this.labelComponents.push(label);
3682
- return this;
3683
- }
3684
- addCheckbox(data) {
3685
- this.validateComponentLength();
3686
- const customId = data.custom_id ?? this.createComponentId();
3687
- const checkbox = new import_discord13.CheckboxBuilder({ ...data, custom_id: customId });
3688
- const label = this.createLabelComponent(data);
3689
- label.setCheckboxComponent(checkbox);
3690
- this.components.set(customId, { checkbox: data });
3691
- this.labelComponents.push(label);
3692
- return this;
3693
- }
3694
- addCheckboxGroup(data) {
3695
- this.validateComponentLength();
3696
- const customId = data.custom_id ?? this.createComponentId();
3697
- const checkboxGroup = new import_discord13.CheckboxGroupBuilder({ ...data, custom_id: customId });
3698
- const label = this.createLabelComponent(data);
3699
- label.setCheckboxGroupComponent(checkboxGroup);
3700
- this.components.set(customId, { checkboxGroup: data });
3701
- this.labelComponents.push(label);
3702
- return this;
3703
- }
3704
- addRadioGroup(data) {
3705
- this.validateComponentLength();
3706
- const customId = data.custom_id ?? this.createComponentId();
3707
- const radioGroup = new import_discord13.RadioGroupBuilder({ ...data, custom_id: customId });
3708
- const label = this.createLabelComponent(data);
3709
- label.setRadioGroupComponent(radioGroup);
3710
- this.components.set(customId, { radioGroup: data });
3711
- this.labelComponents.push(label);
3712
- return this;
3713
- }
3714
- addChannelSelect(data) {
3715
- this.validateComponentLength();
3716
- const customId = data.customId ?? this.createComponentId();
3717
- const channelSelect = new import_discord13.ChannelSelectMenuBuilder({ ...data, customId });
3718
- const label = this.createLabelComponent(data);
3719
- label.setChannelSelectMenuComponent(channelSelect);
3720
- this.components.set(customId, { channelSelect: data });
3721
- this.labelComponents.push(label);
3722
- return this;
3723
- }
3724
- addUserSelect(data) {
3725
- this.validateComponentLength();
3726
- const customId = data.customId ?? this.createComponentId();
3727
- const userSelect = new import_discord13.UserSelectMenuBuilder({ ...data, customId });
3728
- const label = this.createLabelComponent(data);
3729
- label.setUserSelectMenuComponent(userSelect);
3730
- this.components.set(customId, { userSelect: data });
3731
- this.labelComponents.push(label);
3732
- return this;
3733
- }
3734
- addRoleSelect(data) {
3735
- this.validateComponentLength();
3736
- const customId = data.customId ?? this.createComponentId();
3737
- const roleSelect = new import_discord13.RoleSelectMenuBuilder({ ...data, customId });
3738
- const label = this.createLabelComponent(data);
3739
- label.setRoleSelectMenuComponent(roleSelect);
3740
- this.components.set(customId, { roleSelect: data });
3741
- this.labelComponents.push(label);
3742
- return this;
3743
- }
3744
- addMentionableSelect(data) {
3745
- this.validateComponentLength();
3746
- const customId = data.customId ?? this.createComponentId();
3747
- const mentionableSelect = new import_discord13.MentionableSelectMenuBuilder({ ...data, customId });
3748
- const label = this.createLabelComponent(data);
3749
- label.setMentionableSelectMenuComponent(mentionableSelect);
3750
- this.components.set(customId, { mentionableSelect: data });
3751
- this.labelComponents.push(label);
3752
- return this;
3753
- }
3754
- addFileUpload(data) {
3755
- this.validateComponentLength();
3756
- const customId = data.custom_id ?? this.createComponentId();
3757
- const fileUpload = new import_discord13.FileUploadBuilder({ ...data, custom_id: customId });
3758
- const label = this.createLabelComponent(data);
3759
- label.setFileUploadComponent(fileUpload);
3760
- this.components.set(customId, { fileUpload: data });
3761
- this.labelComponents.push(label);
3762
- return this;
3763
- }
3764
- /**
3765
- * Shows the modal to the user via interaction.
3766
- * @param interaction The command interaction to show the modal with.
3767
- * @param options Modal options.
3768
- */
3769
- async show(interaction, options) {
3770
- if (!interaction) throw new Error("[BetterModal] Interaction is null or undefined");
3771
- const modal = this.build();
3772
- await interaction.showModal(modal, options);
3773
- }
3774
- /**
3775
- * Shows the modal and waits for it to be submitted.
3776
- * @param interaction The interaction to show the modal with.
3777
- * @param options Modal submission options.
3778
- */
3779
- async showAndAwait(interaction, options) {
3780
- await this.show(interaction);
3781
- return this.awaitSubmit(interaction, options);
3782
- }
3783
- /**
3784
- * Waits for this modal to be submitted, returning a helper utility object.
3785
- * @param interaction The interaction to show the modal with.
3786
- * @param options Modal submission options.
3787
- */
3788
- async awaitSubmit(interaction, options) {
3789
- if (!interaction) throw new Error("[BetterModal] Interaction is null or undefined");
3790
- const timeout = options?.timeout ?? DEFAULT_CONFIG.timeout;
3791
- try {
3792
- const modalSubmit = await interaction.awaitModalSubmit({
3793
- filter: (i) => i.customId === this.customId,
3794
- time: timeout
3795
- });
3796
- if (options?.deferUpdate) {
3797
- await modalSubmit.deferUpdate();
3798
- }
3799
- const fields = /* @__PURE__ */ new Map();
3800
- const values = [];
3801
- for (const customId of this.components.keys()) {
3802
- let value = null;
3803
- try {
3804
- value = modalSubmit.fields.getTextInputValue(customId);
3805
- } catch {
3806
- try {
3807
- const field = modalSubmit.fields.fields.get(customId);
3808
- if (field && "values" in field) {
3809
- value = field.values;
3810
- }
3811
- } catch {
3812
- }
3813
- }
3814
- fields.set(customId, value);
3815
- values.push(value);
3816
- }
3817
- return {
3818
- values,
3819
- interaction: modalSubmit,
3820
- getField: (customId) => fields.get(customId),
3821
- reply: (options2) => dynaSend(modalSubmit, options2),
3822
- followUp: async (options2) => dynaSend(modalSubmit, options2),
3823
- deferUpdate: () => modalSubmit.deferUpdate()
3824
- };
3825
- } catch {
3826
- return null;
3827
- }
3828
- }
3829
- };
3830
-
3831
- // src/tools/Paginator.ts
3832
- var import_discord14 = require("discord.js");
3833
- var import_node_events4 = __toESM(require("events"));
3834
- var PaginationType = /* @__PURE__ */ ((PaginationType2) => {
3835
- PaginationType2[PaginationType2["Short"] = 0] = "Short";
3836
- PaginationType2[PaginationType2["ShortJump"] = 1] = "ShortJump";
3837
- PaginationType2[PaginationType2["Long"] = 2] = "Long";
3838
- PaginationType2[PaginationType2["LongJump"] = 3] = "LongJump";
3839
- return PaginationType2;
3840
- })(PaginationType || {});
3841
- var PaginationTimeoutType = /* @__PURE__ */ ((PaginationTimeoutType2) => {
3842
- PaginationTimeoutType2[PaginationTimeoutType2["DisableComponents"] = 0] = "DisableComponents";
3843
- PaginationTimeoutType2[PaginationTimeoutType2["ClearComponents"] = 1] = "ClearComponents";
3844
- PaginationTimeoutType2[PaginationTimeoutType2["DeleteMessage"] = 2] = "DeleteMessage";
3845
- PaginationTimeoutType2[PaginationTimeoutType2["DoNothing"] = 3] = "DoNothing";
3846
- return PaginationTimeoutType2;
3847
- })(PaginationTimeoutType || {});
3848
- function wrapPositive(num, max) {
3849
- return (num % (max + 1) + (max + 1)) % (max + 1);
3850
- }
3851
- function createNavButton(id, config) {
3852
- const data = config.paginator.buttons[id];
3853
- const btn = new import_discord14.ButtonBuilder({ customId: `btn_${id}`, style: import_discord14.ButtonStyle.Secondary });
3854
- if (data.label) {
3855
- btn.setLabel(data.label);
3856
- } else {
3857
- btn.setEmoji(data.emoji.name);
3858
- }
3859
- return btn;
3860
- }
3861
- function isEmbed(item) {
3862
- return item instanceof import_discord14.EmbedBuilder || item instanceof BetterEmbed;
3863
- }
3864
- function resolvePages(pages) {
3865
- if (Array.isArray(pages)) {
3866
- if (pages.length === 0) return [];
3867
- if (Array.isArray(pages[0])) return pages;
3868
- return pages;
3869
- }
3870
- return [pages];
3871
- }
3872
- var Paginator = class {
3873
- chapters = [];
3874
- options;
3875
- config;
3876
- data;
3877
- events;
3878
- eventEmitter = new import_node_events4.default();
3879
- constructor(options = {}) {
3880
- this.config = options.config ? createToolsConfig(options.config) : globalToolsConfig;
3881
- this.options = {
3882
- type: options.type ?? 0 /* Short */,
3883
- participants: options.participants ?? [],
3884
- pages: options.pages ?? [],
3885
- useReactions: options.useReactions ?? false,
3886
- dynamic: options.dynamic ?? false,
3887
- timeout: options.timeout ?? this.config.timeouts.pagination,
3888
- onTimeout: options.onTimeout ?? 1 /* ClearComponents */
3889
- };
3890
- this.data = {
3891
- message: null,
3892
- messageActionRows: [],
3893
- messageSendOptions: void 0,
3894
- extraButtons: [],
3895
- page: { current: null, index: { chapter: 0, nested: 0 } },
3896
- navigation: { reactions: [], isRequired: false, isLong: false, canJump: false },
3897
- collectors: { component: null, reaction: null },
3898
- components: {
3899
- chapterSelect: new import_discord14.StringSelectMenuBuilder({ customId: "ssm_chapterSelect" }),
3900
- navigation: {
3901
- first: createNavButton("first", this.config),
3902
- back: createNavButton("back", this.config),
3903
- jump: createNavButton("jump", this.config),
3904
- next: createNavButton("next", this.config),
3905
- last: createNavButton("last", this.config)
3906
- },
3907
- actionRows: {
3908
- chapterSelect: new import_discord14.ActionRowBuilder(),
3909
- navigation: new import_discord14.ActionRowBuilder()
3910
- }
3911
- }
3912
- };
3913
- this.data.components.actionRows.chapterSelect.setComponents(this.data.components.chapterSelect);
3914
- this.events = {
3915
- beforeChapterChange: [],
3916
- chapterChange: [],
3917
- beforePageChange: [],
3918
- pageChange: [],
3919
- first: [],
3920
- back: [],
3921
- jump: [],
3922
- next: [],
3923
- last: [],
3924
- collect: [],
3925
- react: [],
3926
- preTimeout: [],
3927
- postTimeout: []
3928
- };
3929
- if (this.options.pages.length) {
3930
- this.addChapter(this.options.pages, { label: "Default" });
3931
- }
3932
- for (const [key, val] of Object.entries(this.config.paginator.buttons)) {
3933
- if (!val.emoji.id) throw new Error(`[Paginator] Button '${key}.id' is not defined`);
3934
- if (!val.emoji.name) throw new Error(`[Paginator] Button '${key}.name' is not defined`);
3935
- }
3936
- }
3937
- async build() {
3938
- await this.setPage();
3939
- this.data.components.actionRows.navigation.setComponents([]);
3940
- this.data.navigation.reactions = [];
3941
- this.data.messageActionRows = [];
3942
- if (this.data.navigation.isRequired) {
3943
- let navTypes = [];
3944
- if (this.options.dynamic) {
3945
- const isLong = this.data.navigation.isLong;
3946
- const isJump = this.options.type === 1 /* ShortJump */ || this.options.type === 3 /* LongJump */;
3947
- if (isLong) {
3948
- this.options.type = isJump ? 3 /* LongJump */ : 2 /* Long */;
3949
- } else {
3950
- this.options.type = isJump ? 1 /* ShortJump */ : 0 /* Short */;
3951
- }
3952
- }
3953
- switch (this.options.type) {
3954
- case 0 /* Short */:
3955
- navTypes = ["back", "next"];
3956
- break;
3957
- case 1 /* ShortJump */:
3958
- navTypes = ["back", "jump", "next"];
3959
- break;
3960
- case 2 /* Long */:
3961
- navTypes = ["first", "back", "next", "last"];
3962
- break;
3963
- case 3 /* LongJump */:
3964
- navTypes = ["first", "back", "jump", "next", "last"];
3965
- break;
3966
- }
3967
- if (this.options.useReactions) {
3968
- this.data.navigation.reactions = navTypes.map((type) => {
3969
- const data = this.config.paginator.buttons[type];
3970
- return { name: data.emoji.name, id: data.emoji.id };
3971
- });
3972
- } else {
3973
- this.data.components.actionRows.navigation.setComponents(
3974
- navTypes.map((type) => this.data.components.navigation[type])
3975
- );
3976
- }
3977
- }
3978
- if (this.chapters.length > 1) {
3979
- this.data.messageActionRows.push(this.data.components.actionRows.chapterSelect);
3980
- }
3981
- if (this.data.navigation.isRequired && !this.options.useReactions || this.data.extraButtons.length) {
3982
- for (const btn of this.data.extraButtons) {
3983
- this.data.components.actionRows.navigation.components.splice(btn.index, 0, btn.component);
3984
- }
3985
- this.data.messageActionRows.push(this.data.components.actionRows.navigation);
3986
- }
3987
- }
3988
- buildSendOptions(options = {}) {
3989
- const sendOptions = {
3990
- content: "",
3991
- embeds: [],
3992
- components: [],
3993
- flags: [],
3994
- files: [],
3995
- // Explicitly empty to clear previous files on page switch
3996
- ...options,
3997
- withResponse: true
3998
- };
3999
- const page = this.data.page.current;
4000
- const currentChapter = this.chapters[this.data.page.index.chapter];
4001
- const chapterFile = currentChapter?.files?.[this.data.page.index.nested];
4002
- if (chapterFile) sendOptions.files.push(chapterFile);
4003
- if (Array.isArray(page)) {
4004
- sendOptions.embeds.push(...page);
4005
- } else if (typeof page === "string") {
4006
- sendOptions.content = page;
4007
- } else if (isEmbed(page)) {
4008
- sendOptions.embeds.push(page);
4009
- } else if (page instanceof import_discord14.AttachmentBuilder) {
4010
- sendOptions.files.push(page);
4011
- } else if (page instanceof import_discord14.ContainerBuilder || page instanceof BetterContainer) {
4012
- sendOptions.components.push(page);
4013
- if (!sendOptions.flags.includes("IsComponentsV2")) {
4014
- sendOptions.flags.push("IsComponentsV2");
4015
- }
4016
- }
4017
- sendOptions.components.push(...this.data.messageActionRows);
4018
- return sendOptions;
4019
- }
4020
- async handlePostTimeout() {
4021
- if (!this.data.message) return;
4022
- this.callEventStack("preTimeout", this.data.message);
4023
- this.data.collectors.component?.stop();
4024
- this.data.collectors.reaction?.stop();
4025
- switch (this.options.onTimeout) {
4026
- case 0 /* DisableComponents */:
4027
- if (!this.data.message.editable) break;
4028
- const disabledNavComponents = this.data.components.actionRows.navigation.components.map((component) => {
4029
- if ("setDisabled" in component) {
4030
- return component.setDisabled(true);
4031
- }
4032
- return component;
4033
- });
4034
- const disabledNavRow = import_discord14.ActionRowBuilder.from(this.data.components.actionRows.navigation).setComponents(
4035
- disabledNavComponents
4036
- );
4037
- const newComponents = [];
4038
- const currentPage = this.data.page.current;
4039
- if (currentPage instanceof import_discord14.ContainerBuilder || currentPage instanceof BetterContainer) {
4040
- newComponents.push(currentPage);
4041
- }
4042
- if (this.chapters.length > 1) {
4043
- const disabledSelect = import_discord14.StringSelectMenuBuilder.from(this.data.components.chapterSelect).setDisabled(
4044
- true
4045
- );
4046
- newComponents.push(
4047
- import_discord14.ActionRowBuilder.from(this.data.components.actionRows.chapterSelect).setComponents(disabledSelect)
4048
- );
4049
- }
4050
- if (disabledNavRow.components.length > 0) {
4051
- newComponents.push(disabledNavRow);
4052
- }
4053
- await this.data.message.edit({ components: newComponents }).catch(Boolean);
4054
- if (this.options.useReactions) {
4055
- await this.nav_removeFromMessage();
4056
- }
4057
- break;
4058
- case 1 /* ClearComponents */:
4059
- if (this.data.message.editable) {
4060
- await this.nav_removeFromMessage();
4061
- }
4062
- break;
4063
- case 2 /* DeleteMessage */:
4064
- await this.data.message.delete().catch(Boolean);
4065
- break;
4066
- case 3 /* DoNothing */:
4067
- break;
4068
- }
4069
- this.callEventStack("postTimeout", this.data.message);
4070
- }
4071
- async nav_removeFromMessage() {
4072
- if (!this.data.message?.editable) return;
4073
- if (this.options.useReactions) {
4074
- await this.data.message.reactions.removeAll().catch(Boolean);
4075
- } else {
4076
- const newComponents = this.data.message.components.filter((c) => c.type !== import_discord14.ComponentType.Container);
4077
- await this.data.message.edit({ components: newComponents }).catch(Boolean);
4078
- }
4079
- }
4080
- async nav_addReactions() {
4081
- if (!this.data.message || !this.options.useReactions || !this.data.navigation.reactions.length) return;
4082
- for (const r of this.data.navigation.reactions) {
4083
- await this.data.message.react(r.id).catch(Boolean);
4084
- }
4085
- }
4086
- async collect_components() {
4087
- if (!this.data.message) return;
4088
- if (this.data.collectors.component) {
4089
- this.data.collectors.component.resetTimer();
4090
- return;
4091
- }
4092
- const participantIds = this.options.participants.map((p) => typeof p === "string" ? p : p.id);
4093
- const collector = this.data.message.createMessageComponentCollector({
4094
- filter: async (i) => {
4095
- if (!participantIds.length) return true;
4096
- if (participantIds.includes(i.user.id)) {
4097
- return true;
4098
- } else {
4099
- await i.reply({ content: this.config.paginator.notAParticipantMessage, flags: "Ephemeral" });
4100
- return false;
4101
- }
4102
- },
4103
- ...this.options.timeout ? { idle: this.options.timeout } : {}
4104
- });
4105
- this.data.collectors.component = collector;
4106
- collector.on("collect", async (i) => {
4107
- if (!i.isStringSelectMenu() && !i.isButton()) return;
4108
- collector.resetTimer();
4109
- this.callEventStack("collect", i, this.data.page.current, this.data.page.index);
4110
- try {
4111
- if (i.customId === "btn_jump") {
4112
- this.callEventStack("jump", this.data.page.current, this.data.page.index);
4113
- await i.reply({ content: "Jump not implemented yet.", flags: "Ephemeral" });
4114
- return;
4115
- }
4116
- switch (i.customId) {
4117
- case "ssm_chapterSelect":
4118
- await i.deferUpdate().catch(Boolean);
4119
- const chapterIndex = this.chapters.findIndex(
4120
- (c) => c.id === i.values[0]
4121
- );
4122
- await this.setPage(chapterIndex, 0);
4123
- await this.refresh();
4124
- break;
4125
- case "btn_first":
4126
- await i.deferUpdate().catch(Boolean);
4127
- this.callEventStack("first", this.data.page.current, this.data.page.index);
4128
- await this.setPage(this.data.page.index.chapter, 0);
4129
- await this.refresh();
4130
- break;
4131
- case "btn_back":
4132
- await i.deferUpdate().catch(Boolean);
4133
- this.callEventStack("back", this.data.page.current, this.data.page.index);
4134
- await this.setPage(this.data.page.index.chapter, this.data.page.index.nested - 1);
4135
- await this.refresh();
4136
- break;
4137
- case "btn_next":
4138
- await i.deferUpdate().catch(Boolean);
4139
- this.callEventStack("next", this.data.page.current, this.data.page.index);
4140
- await this.setPage(this.data.page.index.chapter, this.data.page.index.nested + 1);
4141
- await this.refresh();
4142
- break;
4143
- case "btn_last":
4144
- await i.deferUpdate().catch(Boolean);
4145
- this.callEventStack("last", this.data.page.current, this.data.page.index);
4146
- await this.setPage(
4147
- this.data.page.index.chapter,
4148
- this.chapters[this.data.page.index.chapter].pages.length - 1
4149
- );
4150
- await this.refresh();
4151
- break;
4152
- }
4153
- } catch (err) {
4154
- console.error("[Paginator] Component navigation error", err);
4155
- }
4156
- });
4157
- collector.on("end", async () => {
4158
- this.data.collectors.component = null;
4159
- this.handlePostTimeout();
4160
- });
4161
- }
4162
- async callEventStack(event, ...args) {
4163
- if (!this.events[event].length) return;
4164
- const listeners = [...this.events[event]];
4165
- for (const _event of listeners) {
4166
- await _event.listener(...args);
4167
- if (_event.once) {
4168
- const originalIndex = this.events[event].indexOf(_event);
4169
- if (originalIndex > -1) this.events[event].splice(originalIndex, 1);
4170
- }
4171
- }
4172
- }
4173
- on(event, listener, once = false) {
4174
- this.events[event].push({ listener, once });
4175
- return this;
4176
- }
4177
- /** Adds a chapter to the paginator.
4178
- * @param pages The pages for this chapter.
4179
- * Note: `[Embed1, Embed2]` = 2 Pages. `[[Embed1, Embed2]]` = 1 Page (Group of 2).
4180
- * @param data Metadata for the chapter select menu. */
4181
- addChapter(pages, data) {
4182
- if (data.default === void 0 && !this.chapters.length) {
4183
- data.default = true;
4184
- }
4185
- if (data.default) {
4186
- this.data.components.chapterSelect.options.forEach((opt) => opt.setDefault(false));
4187
- }
4188
- if (!data.value) {
4189
- data.value = `ssm_c:${this.chapters.length}`;
4190
- }
4191
- const normalizedPages = resolvePages(pages);
4192
- this.chapters.push({ id: data.value, pages: normalizedPages, files: data.files });
4193
- this.data.components.chapterSelect.addOptions(data);
4194
- return this;
4195
- }
4196
- spliceChapters(index, deleteCount) {
4197
- this.chapters.splice(index, deleteCount);
4198
- this.data.components.chapterSelect.spliceOptions(index, deleteCount);
4199
- return this;
4200
- }
4201
- hydrateChapter(index, pages, set) {
4202
- if (!this.chapters[index]) {
4203
- throw new Error(`[Paginator] Could not find chapter at index ${index}`);
4204
- }
4205
- const normalizedPages = resolvePages(pages);
4206
- if (set) {
4207
- this.chapters[index].pages = normalizedPages;
4208
- } else {
4209
- this.chapters[index].pages.push(...normalizedPages);
4210
- }
4211
- return this;
4212
- }
4213
- setPaginationType(type) {
4214
- this.options.type = type;
4215
- return this;
4216
- }
4217
- insertButtonAt(index, component) {
4218
- if (this.data.components.actionRows.navigation.components.length >= 5) {
4219
- throw new Error("[Paginator] You cannot have more than 5 components in 1 action row. Use a separate ActionRow");
4220
- }
4221
- this.data.extraButtons.push({ index, component });
4222
- return this;
4223
- }
4224
- removeButtonAt(...index) {
4225
- index.forEach((i) => this.data.extraButtons.splice(i, 1));
4226
- return this;
4227
- }
4228
- async setPage(chapterIndex = this.data.page.index.chapter, nestedIndex = this.data.page.index.nested) {
4229
- const _oldChapterIndex = this.data.page.index.chapter;
4230
- this.data.page.index.chapter = wrapPositive(chapterIndex, this.chapters.length - 1);
4231
- const currentChapter = this.chapters[this.data.page.index.chapter];
4232
- if (!currentChapter) {
4233
- throw new Error(`[Paginator] Could not find chapter at index ${this.data.page.index.chapter}`);
4234
- }
4235
- await this.callEventStack("beforeChapterChange", this.data.page.index.chapter);
4236
- await this.callEventStack("beforePageChange", nestedIndex);
4237
- const _oldNestedIndex = this.data.page.index.nested;
4238
- this.data.page.index.nested = this.data.page.index.chapter !== _oldChapterIndex ? 0 : wrapPositive(nestedIndex, currentChapter.pages.length - 1);
4239
- const currentPage = currentChapter.pages[this.data.page.index.nested];
4240
- if (!currentPage) {
4241
- throw new Error(`[Paginator] Could not find page at index ${this.data.page.index.nested}`);
4242
- }
4243
- this.data.page.current = currentPage;
4244
- this.data.components.chapterSelect.options.forEach((opt) => opt.setDefault(false));
4245
- this.data.components.chapterSelect.options.at(this.data.page.index.chapter)?.setDefault(true);
4246
- const { jumpableThreshold, longThreshold } = this.config.paginator;
4247
- this.data.navigation.isRequired = currentChapter.pages.length >= 2;
4248
- this.data.navigation.canJump = currentChapter.pages.length >= jumpableThreshold;
4249
- this.data.navigation.isLong = currentChapter.pages.length >= longThreshold;
4250
- if (this.data.page.index.chapter !== _oldChapterIndex) {
4251
- this.callEventStack(
4252
- "chapterChange",
4253
- this.data.components.chapterSelect.options.at(this.data.page.index.chapter),
4254
- this.data.page.current,
4255
- this.data.page.index
4256
- );
4257
- this.callEventStack("pageChange", this.data.page.current, this.data.page.index);
4258
- } else if (this.data.page.index.nested !== _oldNestedIndex) {
4259
- this.callEventStack("pageChange", this.data.page.current, this.data.page.index);
4260
- }
4261
- }
4262
- async refresh() {
4263
- if (!this.data.message) {
4264
- throw new Error("[Paginator] Cannot refresh, message not sent");
4265
- }
4266
- if (!this.data.message.editable) {
4267
- throw new Error("[Paginator] Cannot refresh, message is not editable");
4268
- }
4269
- await this.build();
4270
- this.data.message = await dynaSend(this.data.message, {
4271
- sendMethod: 5 /* MessageEdit */,
4272
- ...this.buildSendOptions(this.data.messageSendOptions)
4273
- });
4274
- return this.data.message;
4275
- }
4276
- async send(handler, options) {
4277
- this.data.messageSendOptions = options;
4278
- await this.build();
4279
- this.data.message = await dynaSend(handler, this.buildSendOptions(options));
4280
- if (!this.data.message) return null;
4281
- this.collect_components();
4282
- return this.data.message;
4283
- }
4284
- };
4285
-
4286
- // src/tools/Prompt.ts
4287
- var import_discord15 = require("discord.js");
4288
- var PromptResolveType = /* @__PURE__ */ ((PromptResolveType2) => {
4289
- PromptResolveType2[PromptResolveType2["DisableComponents"] = 0] = "DisableComponents";
4290
- PromptResolveType2[PromptResolveType2["ClearComponents"] = 1] = "ClearComponents";
4291
- PromptResolveType2[PromptResolveType2["DeleteOnConfirm"] = 3] = "DeleteOnConfirm";
4292
- PromptResolveType2[PromptResolveType2["DeleteOnReject"] = 4] = "DeleteOnReject";
4293
- return PromptResolveType2;
4294
- })(PromptResolveType || {});
4295
- var Prompt = class {
4296
- participants;
4297
- content;
4298
- embed;
4299
- container;
4300
- textOnly;
4301
- buttons;
4302
- customButtons;
4303
- onResolve;
4304
- timeout;
4305
- config;
4306
- message = null;
4307
- constructor(options = {}) {
4308
- this.config = options.config ? createToolsConfig(options.config) : globalToolsConfig;
4309
- this.participants = options.participants ?? [];
4310
- this.timeout = options.timeout ?? this.config.timeouts.prompt;
4311
- this.content = options.content;
4312
- this.embed = options.embed ?? this.createDefaultForm();
4313
- this.container = options?.container;
4314
- this.textOnly = options.textOnly;
4315
- this.buttons = this.createButtons(options.buttons);
4316
- this.customButtons = this.createCustomButtons(options.customButtons);
4317
- this.onResolve = options.onResolve ?? [3 /* DeleteOnConfirm */, 4 /* DeleteOnReject */];
4318
- }
4319
- createDefaultForm() {
4320
- return new BetterEmbed({
4321
- title: this.config.prompt.defaultTitle,
4322
- description: this.config.prompt.defaultDescription
4323
- });
4324
- }
4325
- createButtons(buttonOptions) {
4326
- const confirm = this.buildButton(
4327
- buttonOptions?.confirm,
4328
- "btn_confirm",
4329
- this.config.prompt.confirmLabel,
4330
- import_discord15.ButtonStyle.Success
4331
- );
4332
- const reject = this.buildButton(
4333
- buttonOptions?.reject,
4334
- "btn_reject",
4335
- this.config.prompt.rejectLabel,
4336
- import_discord15.ButtonStyle.Danger
4337
- );
4338
- return { confirm, reject };
4339
- }
4340
- createCustomButtons(customOptions) {
4341
- const map = /* @__PURE__ */ new Map();
4342
- if (!customOptions) return map;
4343
- for (const [customId, { builder, handler, index = 2 }] of Object.entries(customOptions)) {
4344
- const button = this.buildButton(builder, customId, customId, import_discord15.ButtonStyle.Primary);
4345
- map.set(customId, { button, handler, index });
4346
- }
4347
- return map;
4348
- }
4349
- buildButton(option, customId, defaultLabel, defaultStyle) {
4350
- if (typeof option === "function") {
4351
- return option(new import_discord15.ButtonBuilder());
4352
- }
4353
- if (option instanceof import_discord15.ButtonBuilder) {
4354
- return option;
4355
- }
4356
- return new import_discord15.ButtonBuilder({
4357
- customId,
4358
- label: defaultLabel,
4359
- style: defaultStyle,
4360
- ...option
4361
- });
4362
- }
4363
- buildActionRow(disable = {}) {
4364
- const confirmBtn = disable.confirm ? new import_discord15.ButtonBuilder(this.buttons.confirm.data).setDisabled(true) : this.buttons.confirm;
4365
- const rejectBtn = disable.reject ? new import_discord15.ButtonBuilder(this.buttons.reject.data).setDisabled(true) : this.buttons.reject;
4366
- const buttons = [];
4367
- const customButtonsArray = Array.from(this.customButtons.entries()).map(([customId, data]) => ({
4368
- customId,
4369
- button: disable[customId] ? new import_discord15.ButtonBuilder(data.button.data).setDisabled(true) : data.button,
4370
- index: data.index
4371
- }));
4372
- customButtonsArray.sort((a, b) => a.index - b.index);
4373
- for (const custom of customButtonsArray) {
4374
- if (custom.index === 0) {
4375
- buttons.push(custom.button);
4376
- }
4377
- }
4378
- buttons.push(confirmBtn);
4379
- for (const custom of customButtonsArray) {
4380
- if (custom.index === 1) {
4381
- buttons.push(custom.button);
4382
- }
4383
- }
4384
- buttons.push(rejectBtn);
4385
- for (const custom of customButtonsArray) {
4386
- if (custom.index >= 2) {
4387
- buttons.push(custom.button);
4388
- }
4389
- }
4390
- return new import_discord15.ActionRowBuilder({ components: buttons });
4391
- }
4392
- buildSendOptions(options) {
4393
- const sendData = { ...options };
4394
- if (!this.textOnly && this.container) {
4395
- sendData.components = Array.isArray(sendData.components) ? [...sendData.components, this.container] : [this.container];
4396
- const existingFlags = sendData.flags ? Array.isArray(sendData.flags) ? sendData.flags : [sendData.flags] : [];
4397
- if (!existingFlags.includes("IsComponentsV2")) {
4398
- sendData.flags = [...existingFlags, "IsComponentsV2"];
4399
- } else {
4400
- sendData.flags = existingFlags;
4401
- }
4402
- } else {
4403
- if (!this.textOnly) {
4404
- sendData.embeds = Array.isArray(sendData.embeds) ? [this.embed, ...sendData.embeds] : [this.embed];
4405
- }
4406
- }
4407
- if (this.content) {
4408
- sendData.content = this.content;
4409
- }
4410
- sendData.components = Array.isArray(sendData.components) ? [...sendData.components, this.buildActionRow()] : [this.buildActionRow()];
4411
- return sendData;
4412
- }
4413
- isParticipant(userId) {
4414
- if (this.participants.length === 0) return true;
4415
- return this.participants.some((p) => {
4416
- if (typeof p === "string") return p === userId;
4417
- if (typeof p === "object" && "id" in p) return p.id === userId;
4418
- return false;
4419
- });
4420
- }
4421
- async handleResolve(confirmed) {
4422
- if (!this.message) return;
4423
- const shouldDelete = confirmed === true && this.onResolve.includes(3 /* DeleteOnConfirm */) || confirmed === false && this.onResolve.includes(4 /* DeleteOnReject */);
4424
- if (shouldDelete) {
4425
- await this.message.delete().catch(Boolean);
4426
- return;
4427
- }
4428
- if (this.onResolve.includes(1 /* ClearComponents */)) {
4429
- await this.message.edit({ components: [] }).catch(Boolean);
4430
- } else if (this.onResolve.includes(0 /* DisableComponents */)) {
4431
- const disableAll = { confirm: true, reject: true };
4432
- for (const customId of this.customButtons.keys()) {
4433
- disableAll[customId] = true;
4434
- }
4435
- await this.message.edit({ components: [this.buildActionRow(disableAll)] }).catch(Boolean);
4436
- }
4437
- }
4438
- async send(handler, options) {
4439
- const sendData = this.buildSendOptions(options);
4440
- this.message = await dynaSend(handler, sendData);
4441
- return this.message;
4442
- }
4443
- async awaitResponse() {
4444
- if (!this.message) {
4445
- throw new Error("Prompt must be sent before awaiting response");
4446
- }
4447
- const validCustomIds = /* @__PURE__ */ new Set(["btn_confirm", "btn_reject", ...this.customButtons.keys()]);
4448
- try {
4449
- const interaction = await this.message.awaitMessageComponent({
4450
- componentType: import_discord15.ComponentType.Button,
4451
- filter: (i) => validCustomIds.has(i.customId) && this.isParticipant(i.user.id),
4452
- time: this.timeout
4453
- });
4454
- await interaction.deferUpdate().catch(Boolean);
4455
- let confirmed = null;
4456
- if (interaction.customId === "btn_confirm") {
4457
- confirmed = true;
4458
- } else if (interaction.customId === "btn_reject") {
4459
- confirmed = false;
4460
- }
4461
- const customButton = this.customButtons.get(interaction.customId);
4462
- if (customButton?.handler) {
4463
- await customButton.handler(interaction);
4464
- }
4465
- await this.handleResolve(confirmed);
4466
- return {
4467
- message: this.message,
4468
- confirmed,
4469
- customId: interaction.customId,
4470
- timedOut: false
4471
- };
4472
- } catch (error) {
4473
- await this.handleResolve(false);
4474
- return {
4475
- message: this.message,
4476
- confirmed: null,
4477
- customId: null,
4478
- timedOut: true
4479
- };
4480
- }
4481
- }
4482
- };
4483
- async function prompt(handler, options, sendOptions) {
4484
- const p = new Prompt(options);
4485
- await p.send(handler, sendOptions);
4486
- return await p.awaitResponse();
4487
- }
20
+ __reExport(index_exports, require("@vimcord/core"), module.exports);
4488
21
  // Annotate the CommonJS export names for ESM import in node:
4489
22
  0 && (module.exports = {
4490
- BaseCommandBuilder,
4491
- BaseCommandManager,
4492
- BetterCollector,
4493
- BetterContainer,
4494
- BetterEmbed,
4495
- BetterModal,
4496
- CLI,
4497
- CollectorTimeoutType,
4498
- CommandManager,
4499
- CommandType,
4500
- ContextCommandBuilder,
4501
- ContextCommandManager,
4502
- DEFAULT_MODULE_SUFFIXES,
4503
- DynaSend,
4504
- ErrorHandler,
4505
- EventBuilder,
4506
- EventManager,
4507
- LOGGER_COLORS,
4508
- LogLevel,
4509
- Logger,
4510
- MissingPermissionReason,
4511
- ModuleImporter,
4512
- MongoDatabase,
4513
- MongoSchemaBuilder,
4514
- PaginationTimeoutType,
4515
- PaginationType,
4516
- Paginator,
4517
- PrefixCommandBuilder,
4518
- PrefixCommandManager,
4519
- Prompt,
4520
- PromptResolveType,
4521
- RateLimitScope,
4522
- SendMethod,
4523
- SlashCommandBuilder,
4524
- SlashCommandManager,
4525
- StatusManager,
4526
- StatusType,
4527
- Vimcord,
4528
- VimcordCLI,
4529
- VimcordErrorHandler,
4530
- __zero,
4531
- cleanMention,
4532
- clientLoggerFactory,
4533
- configSetters,
4534
- contextCommandHandler,
4535
- createAppConfig,
4536
- createClient,
4537
- createConfigFactory,
4538
- createContextCommandConfig,
4539
- createMongoPlugin,
4540
- createMongoSchema,
4541
- createPrefixCommandConfig,
4542
- createSlashCommandConfig,
4543
- createStaffConfig,
4544
- createToolsConfig,
4545
- createVimcordStatusConfig,
4546
- deepMerge,
4547
- defineClientOptions,
4548
- defineGlobalToolsConfig,
4549
- defineVimcordConfig,
4550
- defineVimcordFeatures,
4551
- dynaSend,
4552
- fetchChannel,
4553
- fetchGuild,
4554
- fetchMember,
4555
- fetchMessage,
4556
- fetchRole,
4557
- fetchUser,
4558
- getDevMode,
4559
- getFirstMentionId,
4560
- getMessageMention,
4561
- getPackageJson,
4562
- globalToolsConfig,
4563
- importModulesFromDir,
4564
- isMentionOrSnowflake,
4565
- logger,
4566
- moduleImporters,
4567
- prefixCommandHandler,
4568
- prompt,
4569
- sendCommandErrorEmbed,
4570
- slashCommandHandler,
4571
- useClient,
4572
- useReadyClient,
4573
- validateCommandPermissions
23
+ ...require("@vimcord/core")
4574
24
  });
4575
- //# sourceMappingURL=index.cjs.map