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