djs-next 1.0.0-dev.2 → 1.0.0-dev.3
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.
Potentially problematic release.
This version of djs-next might be problematic. Click here for more details.
- package/README.md +51 -29
- package/bin/create-djs-next.js +78 -0
- package/dist/index.d.mts +46 -4
- package/dist/index.d.ts +46 -4
- package/dist/index.js +578 -290
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +578 -280
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +213 -106
- package/src/index.ts +4 -1
- package/src/plugins/dnxt.ts +215 -176
- package/src/templates/cjs.ts +11 -6
- package/src/templates/esm.ts +11 -4
- package/src/templates/ts.ts +11 -5
- package/src/types.ts +21 -1
- package/src/utils/PaginationBuilder.ts +94 -0
- package/src/utils/paginate.ts +32 -13
- package/src/utils/prompts.ts +76 -0
- package/test_payload.js +3 -0
- package/test_reply.js +4 -0
- package/tsup.config.ts +1 -1
- package/dist/cli.d.mts +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -344
- package/dist/cli.js.map +0 -1
- package/dist/cli.mjs +0 -321
- package/dist/cli.mjs.map +0 -1
- package/src/cli.ts +0 -203
package/src/client.ts
CHANGED
|
@@ -30,7 +30,10 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
30
30
|
private _developers: string[];
|
|
31
31
|
private _middleware?: (interaction: Interaction | Message, client: Client) => Promise<boolean> | boolean;
|
|
32
32
|
private _prefixes: string[];
|
|
33
|
-
private
|
|
33
|
+
private _enableSlashCommands: boolean;
|
|
34
|
+
private _enableTextCommands: boolean;
|
|
35
|
+
private _enableMentionPrefix: boolean | string[];
|
|
36
|
+
private _enableNoPrefix: boolean | string[];
|
|
34
37
|
|
|
35
38
|
constructor(options: DJSNextClientOptions) {
|
|
36
39
|
super(options);
|
|
@@ -49,7 +52,13 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
49
52
|
|
|
50
53
|
const prefs = options.prefixes || [];
|
|
51
54
|
this._prefixes = Array.isArray(prefs) ? prefs : [prefs];
|
|
55
|
+
this._prefixes = this._prefixes.filter(p => p !== ''); // Remove empty strings, handle explicitly via enableNoPrefix
|
|
56
|
+
|
|
57
|
+
this._enableSlashCommands = options.enableSlashCommands ?? true;
|
|
58
|
+
this._enableTextCommands = options.enableTextCommands ?? true;
|
|
52
59
|
this._enableMentionPrefix = options.enableMentionPrefix ?? true;
|
|
60
|
+
this._enableNoPrefix = options.enableNoPrefix ?? false;
|
|
61
|
+
if (options.db) this.db = options.db as any;
|
|
53
62
|
|
|
54
63
|
this.attachCoreListeners();
|
|
55
64
|
}
|
|
@@ -69,6 +78,8 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
69
78
|
|
|
70
79
|
// 2. Chat Input Commands Execution
|
|
71
80
|
if (interaction.isChatInputCommand()) {
|
|
81
|
+
if (!this._enableSlashCommands) return;
|
|
82
|
+
|
|
72
83
|
let commandKey = interaction.commandName;
|
|
73
84
|
const group = interaction.options.getSubcommandGroup(false);
|
|
74
85
|
const sub = interaction.options.getSubcommand(false);
|
|
@@ -79,56 +90,12 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
79
90
|
const command = this.commands.get(commandKey);
|
|
80
91
|
if (!command || !command.execute) return;
|
|
81
92
|
|
|
82
|
-
|
|
83
|
-
if (command.developerOnly && !this._developers.includes(interaction.user.id)) {
|
|
84
|
-
return interaction.reply({ content: 'Only developers can use this command.', ephemeral: true }) as never;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (command.guildOnly && !interaction.inGuild()) {
|
|
88
|
-
return interaction.reply({ content: 'This command can only be used in a server.', ephemeral: true }) as never;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (command.userPermissions && interaction.memberPermissions) {
|
|
92
|
-
const missing = interaction.memberPermissions.missing(command.userPermissions);
|
|
93
|
-
if (missing.length > 0) {
|
|
94
|
-
return interaction.reply({ content: `You are missing permissions: \`${missing.join(', ')}\``, ephemeral: true }) as never;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (command.botPermissions && interaction.guild?.members.me?.permissions) {
|
|
99
|
-
const missing = interaction.guild.members.me.permissions.missing(command.botPermissions);
|
|
100
|
-
if (missing.length > 0) {
|
|
101
|
-
return interaction.reply({ content: `I am missing permissions to run this: \`${missing.join(', ')}\``, ephemeral: true }) as never;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// --- Built-in Cooldowns ---
|
|
106
|
-
if (command.cooldown) {
|
|
107
|
-
if (!this.cooldowns.has(commandKey)) this.cooldowns.set(commandKey, new Collection());
|
|
108
|
-
const now = Date.now();
|
|
109
|
-
const timestamps = this.cooldowns.get(commandKey)!;
|
|
110
|
-
const cooldownAmount = command.cooldown * 1000;
|
|
111
|
-
|
|
112
|
-
if (timestamps.has(interaction.user.id)) {
|
|
113
|
-
const expirationTime = timestamps.get(interaction.user.id)! + cooldownAmount;
|
|
114
|
-
if (now < expirationTime) {
|
|
115
|
-
return interaction.reply({
|
|
116
|
-
content: `Please wait, you are on a cooldown. You can use it again <t:${Math.round(expirationTime / 1000)}:R>.`,
|
|
117
|
-
ephemeral: true
|
|
118
|
-
}) as never;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
timestamps.set(interaction.user.id, now);
|
|
122
|
-
setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount);
|
|
123
|
-
}
|
|
93
|
+
if (!(await this.handlePreconditions(command, interaction, commandKey))) return;
|
|
124
94
|
|
|
125
95
|
try {
|
|
126
96
|
await command.execute(interaction, this);
|
|
127
97
|
} catch (error) {
|
|
128
|
-
|
|
129
|
-
const msg = { content: 'We ran into an internal error executing this command. The developers have been notified.', ephemeral: true };
|
|
130
|
-
if (interaction.replied || interaction.deferred) await interaction.followUp(msg).catch(()=>null);
|
|
131
|
-
else await interaction.reply(msg).catch(()=>null);
|
|
98
|
+
await this.handleCommandError(error, commandKey, interaction);
|
|
132
99
|
}
|
|
133
100
|
}
|
|
134
101
|
|
|
@@ -175,13 +142,12 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
175
142
|
}
|
|
176
143
|
}
|
|
177
144
|
if (component) {
|
|
145
|
+
if (!(await this.handlePreconditions(component, interaction, interaction.customId))) return;
|
|
146
|
+
|
|
178
147
|
try {
|
|
179
148
|
await component.execute(interaction, this, params);
|
|
180
149
|
} catch (error) {
|
|
181
|
-
|
|
182
|
-
const msg = { content: 'We ran into an internal error executing this component.', ephemeral: true };
|
|
183
|
-
if (interaction.replied || interaction.deferred) await interaction.followUp(msg).catch(()=>null);
|
|
184
|
-
else await interaction.reply(msg).catch(()=>null);
|
|
150
|
+
await this.handleCommandError(error, interaction.customId, interaction);
|
|
185
151
|
}
|
|
186
152
|
}
|
|
187
153
|
}
|
|
@@ -189,6 +155,7 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
189
155
|
|
|
190
156
|
this.on('messageCreate', async (message: Message) => {
|
|
191
157
|
if (message.author.bot) return;
|
|
158
|
+
if (!this._enableTextCommands) return;
|
|
192
159
|
|
|
193
160
|
// Global Middleware execution for Messages
|
|
194
161
|
if (this._middleware) {
|
|
@@ -207,26 +174,29 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
207
174
|
|
|
208
175
|
// 1. Check Mention Prefix
|
|
209
176
|
const mentionRegex = new RegExp(`^<@!?${this.user?.id}>\\s*`);
|
|
210
|
-
|
|
177
|
+
const canUseMention = this._enableMentionPrefix === true || (Array.isArray(this._enableMentionPrefix) && this._enableMentionPrefix.includes(message.author.id));
|
|
178
|
+
|
|
179
|
+
if (canUseMention && mentionRegex.test(content)) {
|
|
211
180
|
matchedPrefix = content.match(mentionRegex)![0];
|
|
212
181
|
isCommand = true;
|
|
213
182
|
} else {
|
|
214
|
-
// 2. Check Standard
|
|
215
|
-
// Sort by length descending so longer prefixes match first
|
|
183
|
+
// 2. Check Standard Prefixes
|
|
216
184
|
const sortedPrefs = [...this._prefixes].sort((a, b) => b.length - a.length);
|
|
217
185
|
|
|
218
|
-
// If prefixes array is empty or contains an empty string, it means "no prefix" is supported!
|
|
219
|
-
if (sortedPrefs.includes('')) {
|
|
220
|
-
isCommand = true;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
186
|
for (const p of sortedPrefs) {
|
|
224
|
-
if (
|
|
187
|
+
if (content.startsWith(p)) {
|
|
225
188
|
matchedPrefix = p;
|
|
226
189
|
isCommand = true;
|
|
227
190
|
break;
|
|
228
191
|
}
|
|
229
192
|
}
|
|
193
|
+
|
|
194
|
+
// 3. Check No Prefix
|
|
195
|
+
const canUseNoPrefix = this._enableNoPrefix === true || (Array.isArray(this._enableNoPrefix) && this._enableNoPrefix.includes(message.author.id));
|
|
196
|
+
if (!isCommand && canUseNoPrefix) {
|
|
197
|
+
isCommand = true;
|
|
198
|
+
matchedPrefix = '';
|
|
199
|
+
}
|
|
230
200
|
}
|
|
231
201
|
|
|
232
202
|
if (!isCommand) return;
|
|
@@ -244,42 +214,13 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
244
214
|
|
|
245
215
|
if (!command || !command.executeText) return;
|
|
246
216
|
|
|
247
|
-
|
|
248
|
-
if (command.developerOnly && !this._developers.includes(message.author.id)) return;
|
|
249
|
-
if (command.guildOnly && !message.guild) return;
|
|
250
|
-
|
|
251
|
-
if (command.userPermissions && message.member?.permissions) {
|
|
252
|
-
const missing = message.member.permissions.missing(command.userPermissions);
|
|
253
|
-
if (missing.length > 0) return;
|
|
254
|
-
}
|
|
255
|
-
if (command.botPermissions && message.guild?.members.me?.permissions) {
|
|
256
|
-
const missing = message.guild.members.me.permissions.missing(command.botPermissions);
|
|
257
|
-
if (missing.length > 0) return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Cooldowns
|
|
261
|
-
if (command.cooldown) {
|
|
262
|
-
if (!this.cooldowns.has(commandName)) this.cooldowns.set(commandName, new Collection());
|
|
263
|
-
const now = Date.now();
|
|
264
|
-
const timestamps = this.cooldowns.get(commandName)!;
|
|
265
|
-
const cooldownAmount = command.cooldown * 1000;
|
|
266
|
-
|
|
267
|
-
if (timestamps.has(message.author.id)) {
|
|
268
|
-
const expirationTime = timestamps.get(message.author.id)! + cooldownAmount;
|
|
269
|
-
if (now < expirationTime) {
|
|
270
|
-
return void await message.reply(`Please wait, you are on a cooldown. You can use it again <t:${Math.round(expirationTime / 1000)}:R>.`);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
timestamps.set(message.author.id, now);
|
|
274
|
-
setTimeout(() => timestamps.delete(message.author.id), cooldownAmount);
|
|
275
|
-
}
|
|
217
|
+
if (!(await this.handlePreconditions(command, message, commandName))) return;
|
|
276
218
|
|
|
277
219
|
// Execute Text Command
|
|
278
220
|
try {
|
|
279
221
|
await command.executeText(message, args, this);
|
|
280
222
|
} catch (error) {
|
|
281
|
-
|
|
282
|
-
await message.reply('We ran into an internal error executing this command.').catch(() => null);
|
|
223
|
+
await this.handleCommandError(error, commandName, message);
|
|
283
224
|
}
|
|
284
225
|
});
|
|
285
226
|
}
|
|
@@ -289,15 +230,17 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
289
230
|
|
|
290
231
|
this.config = await loadConfig();
|
|
291
232
|
|
|
292
|
-
// Fallback options to config
|
|
233
|
+
// Fallback options to config, then to default 'src' folders
|
|
293
234
|
this._guildId = this._guildId || this.config.devGuildId;
|
|
294
|
-
|
|
295
|
-
if (!this.
|
|
296
|
-
if (!this.
|
|
297
|
-
if (!this.
|
|
298
|
-
if (!this.
|
|
235
|
+
|
|
236
|
+
if (!this._commandsDir) this._commandsDir = path.resolve(process.cwd(), this.config.directories?.commands || 'src/commands');
|
|
237
|
+
if (!this._eventsDir) this._eventsDir = path.resolve(process.cwd(), this.config.directories?.events || 'src/events');
|
|
238
|
+
if (!this._componentsDir) this._componentsDir = path.resolve(process.cwd(), this.config.directories?.components || 'src/components');
|
|
239
|
+
if (!this._tasksDir) this._tasksDir = path.resolve(process.cwd(), this.config.directories?.tasks || 'src/tasks');
|
|
240
|
+
if (!this._localesDir) this._localesDir = path.resolve(process.cwd(), this.config.directories?.locales || 'src/locales');
|
|
299
241
|
|
|
300
|
-
if
|
|
242
|
+
// Only load if the directories actually exist
|
|
243
|
+
if (fs.existsSync(this._localesDir)) loadLocales(this._localesDir, this.config.defaultLocale);
|
|
301
244
|
|
|
302
245
|
// Load middleware from root
|
|
303
246
|
if (!this._middleware) {
|
|
@@ -320,17 +263,18 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
320
263
|
}
|
|
321
264
|
}
|
|
322
265
|
|
|
323
|
-
if (this._eventsDir) await loadEvents(this, this._eventsDir);
|
|
324
|
-
if (this._componentsDir) this.components = await loadComponents(this._componentsDir);
|
|
325
|
-
if (this._tasksDir) await loadTasks(this, this._tasksDir);
|
|
326
|
-
|
|
327
|
-
if (this._commandsDir) {
|
|
328
|
-
if (!this._clientId) throw new Error("[djs-next] You must provide a clientId to deploy commands.");
|
|
329
|
-
this.commands = await loadAndDeployCommands(this._commandsDir, token, this._clientId, this._guildId);
|
|
330
|
-
}
|
|
266
|
+
if (fs.existsSync(this._eventsDir)) await loadEvents(this, this._eventsDir);
|
|
267
|
+
if (fs.existsSync(this._componentsDir)) this.components = await loadComponents(this._componentsDir);
|
|
268
|
+
if (fs.existsSync(this._tasksDir)) await loadTasks(this, this._tasksDir);
|
|
331
269
|
|
|
332
270
|
await this.login(token);
|
|
333
271
|
console.log(`[djs-next] Bot is ready and logged in as ${this.user?.tag}!`);
|
|
272
|
+
|
|
273
|
+
// Load and deploy commands AFTER login so we can automatically use this.user.id
|
|
274
|
+
if (fs.existsSync(this._commandsDir)) {
|
|
275
|
+
this._clientId = this._clientId || this.user!.id;
|
|
276
|
+
this.commands = await loadAndDeployCommands(this._commandsDir, token, this._clientId, this._guildId);
|
|
277
|
+
}
|
|
334
278
|
}
|
|
335
279
|
|
|
336
280
|
public enableDevTools(prefix: 'dnxt' | 'nxt' = 'dnxt'): void {
|
|
@@ -382,4 +326,167 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
382
326
|
console.warn(`[djs-next] chokidar not installed. HMR is disabled. Please run "npm install chokidar" to use this feature.`);
|
|
383
327
|
}
|
|
384
328
|
}
|
|
329
|
+
|
|
330
|
+
private async handleCommandError(error: any, commandKey: string, context: Interaction | Message) {
|
|
331
|
+
console.error(`[djs-next] Error executing command/component: "${commandKey}"`, error);
|
|
332
|
+
const djs = require('discord.js');
|
|
333
|
+
if (this.config.responses?.errorBoundary === null) {
|
|
334
|
+
// Developer explicitly opted out of error boundary user messages
|
|
335
|
+
} else {
|
|
336
|
+
try {
|
|
337
|
+
const errorContent = this.config.responses?.errorBoundary || `### ⚠️ Execution Error\n> The framework encountered a fatal error while executing \`${commandKey}\`.`;
|
|
338
|
+
const codeBlock = `\`\`\`js\n${String(error.stack || error.message).substring(0, 1500)}\n\`\`\``;
|
|
339
|
+
|
|
340
|
+
if ('commandName' in context || 'customId' in context) {
|
|
341
|
+
const container = new djs.ContainerBuilder()
|
|
342
|
+
.setAccentColor(0xED4245)
|
|
343
|
+
.addTextDisplayComponents(new djs.TextDisplayBuilder().setContent(errorContent))
|
|
344
|
+
.addSeparatorComponents(new djs.SeparatorBuilder())
|
|
345
|
+
.addTextDisplayComponents(new djs.TextDisplayBuilder().setContent(codeBlock));
|
|
346
|
+
|
|
347
|
+
const payload = { components: [container], flags: 32768, ephemeral: true };
|
|
348
|
+
const i = context as Interaction;
|
|
349
|
+
if (i.isRepliable()) {
|
|
350
|
+
if (i.replied || i.deferred) await i.followUp(payload);
|
|
351
|
+
else await i.reply(payload);
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
const embed = new djs.EmbedBuilder()
|
|
355
|
+
.setColor(0xED4245)
|
|
356
|
+
.setDescription(`${errorContent}\n\n${codeBlock}`);
|
|
357
|
+
|
|
358
|
+
const m = context as Message;
|
|
359
|
+
await m.reply({ embeds: [embed] });
|
|
360
|
+
}
|
|
361
|
+
} catch (e) {
|
|
362
|
+
// Fallback ignore if channel is dead
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (this.config.errorLogChannelId) {
|
|
367
|
+
try {
|
|
368
|
+
const channel = await this.channels.fetch(this.config.errorLogChannelId);
|
|
369
|
+
if (channel && channel.isTextBased()) {
|
|
370
|
+
const userId = 'user' in context ? context.user.id : context.author.id;
|
|
371
|
+
await (channel as any).send(`🚨 **Global Error Boundary Caught Exception**\n**Context:** \`${commandKey}\`\n**User:** <@${userId}>\n\`\`\`js\n${String(error.stack || error.message).substring(0, 1900)}\n\`\`\``);
|
|
372
|
+
}
|
|
373
|
+
} catch (e) {
|
|
374
|
+
console.error(`[djs-next] Failed to send error to log channel:`, e);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private async handlePreconditions(command: FileCommand | FileComponent, context: Interaction | Message, commandKey: string): Promise<boolean> {
|
|
380
|
+
const userId = 'user' in context ? context.user.id : context.author.id;
|
|
381
|
+
const isGuild = 'guild' in context && context.guild !== null;
|
|
382
|
+
const member = context.member as any;
|
|
383
|
+
|
|
384
|
+
const sendErr = async (msg: string | null) => {
|
|
385
|
+
if (msg === null) return;
|
|
386
|
+
if ('reply' in context && typeof context.reply === 'function') {
|
|
387
|
+
try {
|
|
388
|
+
if ('replied' in context && (context as any).isRepliable() && ((context as any).replied || (context as any).deferred)) {
|
|
389
|
+
await (context as any).followUp({ content: msg, ephemeral: true });
|
|
390
|
+
} else {
|
|
391
|
+
await (context as any).reply({ content: msg, ephemeral: true, allowedMentions: { repliedUser: false } });
|
|
392
|
+
}
|
|
393
|
+
} catch(e) {}
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
if ('developerOnly' in command && command.developerOnly && !this._developers.includes(userId)) {
|
|
398
|
+
await sendErr(this.config.responses?.developerOnly !== undefined ? this.config.responses.developerOnly : 'Only developers can use this command.');
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
if ('guildOnly' in command && command.guildOnly && !isGuild) {
|
|
402
|
+
await sendErr(this.config.responses?.guildOnly !== undefined ? this.config.responses.guildOnly : 'This command can only be used in a server.');
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
if ('userPermissions' in command && command.userPermissions && member?.permissions) {
|
|
406
|
+
const missing = member.permissions.missing(command.userPermissions);
|
|
407
|
+
if (missing.length > 0) {
|
|
408
|
+
await sendErr(this.config.responses?.missingPerms !== undefined ? (this.config.responses.missingPerms === null ? null : this.config.responses.missingPerms.replace('{perms}', missing.join(', '))) : `You are missing permissions: \`${missing.join(', ')}\``);
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if ('botPermissions' in command && command.botPermissions && context.guild?.members.me?.permissions) {
|
|
413
|
+
const missing = context.guild.members.me.permissions.missing(command.botPermissions);
|
|
414
|
+
if (missing.length > 0) {
|
|
415
|
+
await sendErr(this.config.responses?.missingPerms !== undefined ? (this.config.responses.missingPerms === null ? null : this.config.responses.missingPerms.replace('{perms}', missing.join(', '))) : `I am missing permissions to run this: \`${missing.join(', ')}\``);
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
let totalCooldown = 'cooldown' in command && command.cooldown ? command.cooldown * 1000 : 0;
|
|
421
|
+
|
|
422
|
+
if (command.preconditions) {
|
|
423
|
+
for (const pre of command.preconditions) {
|
|
424
|
+
const parts = pre.split(':');
|
|
425
|
+
const type = parts[0].toLowerCase();
|
|
426
|
+
const val = parts.slice(1).join(':');
|
|
427
|
+
|
|
428
|
+
if (type === 'owneronly' || type === 'developeronly') {
|
|
429
|
+
if (!this._developers.includes(userId)) {
|
|
430
|
+
await sendErr(this.config.responses?.developerOnly !== undefined ? this.config.responses.developerOnly : 'Only developers can use this command.');
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
} else if (type === 'guildonly') {
|
|
434
|
+
if (!isGuild) {
|
|
435
|
+
await sendErr(this.config.responses?.guildOnly !== undefined ? this.config.responses.guildOnly : 'This command can only be used in a server.');
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
} else if (type === 'requireperms') {
|
|
439
|
+
const perms = val.split(',').map(p => p.trim());
|
|
440
|
+
if (member?.permissions) {
|
|
441
|
+
const missing = member.permissions.missing(perms);
|
|
442
|
+
if (missing.length > 0) {
|
|
443
|
+
await sendErr(this.config.responses?.missingPerms !== undefined ? (this.config.responses.missingPerms === null ? null : this.config.responses.missingPerms.replace('{perms}', missing.join(', '))) : `You are missing permissions: \`${missing.join(', ')}\``);
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
} else if (type === 'cooldown') {
|
|
448
|
+
const match = val.match(/(\d+)(s|m|h)/);
|
|
449
|
+
if (match) {
|
|
450
|
+
let amount = parseInt(match[1]) * 1000;
|
|
451
|
+
if (match[2] === 'm') amount *= 60;
|
|
452
|
+
if (match[2] === 'h') amount *= 3600;
|
|
453
|
+
totalCooldown = Math.max(totalCooldown, amount);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (totalCooldown > 0) {
|
|
460
|
+
const now = Date.now();
|
|
461
|
+
|
|
462
|
+
if (this.config.cooldownAdapter) {
|
|
463
|
+
const lastUsed = await this.config.cooldownAdapter.get(commandKey, userId);
|
|
464
|
+
if (lastUsed) {
|
|
465
|
+
const expirationTime = lastUsed + totalCooldown;
|
|
466
|
+
if (now < expirationTime) {
|
|
467
|
+
const timeStr = `<t:${Math.round(expirationTime / 1000)}:R>`;
|
|
468
|
+
await sendErr(this.config.responses?.cooldown !== undefined ? (this.config.responses.cooldown === null ? null : this.config.responses.cooldown.replace('{time}', timeStr)) : `Please wait, you are on a cooldown. You can use it again ${timeStr}.`);
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
await this.config.cooldownAdapter.set(commandKey, userId, now);
|
|
473
|
+
} else {
|
|
474
|
+
if (!this.cooldowns.has(commandKey)) this.cooldowns.set(commandKey, new Collection());
|
|
475
|
+
const timestamps = this.cooldowns.get(commandKey)!;
|
|
476
|
+
|
|
477
|
+
if (timestamps.has(userId)) {
|
|
478
|
+
const expirationTime = timestamps.get(userId)! + totalCooldown;
|
|
479
|
+
if (now < expirationTime) {
|
|
480
|
+
const timeStr = `<t:${Math.round(expirationTime / 1000)}:R>`;
|
|
481
|
+
await sendErr(this.config.responses?.cooldown !== undefined ? (this.config.responses.cooldown === null ? null : this.config.responses.cooldown.replace('{time}', timeStr)) : `Please wait, you are on a cooldown. You can use it again ${timeStr}.`);
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
timestamps.set(userId, now);
|
|
486
|
+
setTimeout(() => timestamps.delete(userId), totalCooldown);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
385
492
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
1
2
|
export * from './client.js';
|
|
2
|
-
export { DJSNextClientOptions, FileCommand, FileComponent, FileTask, Event as DJSNextEvent, DJSNextConfig } from './types.js';
|
|
3
|
+
export { DJSNextClientOptions, FileCommand, FileComponent, FileTask, Event as DJSNextEvent, DJSNextConfig, CooldownAdapter } from './types.js';
|
|
3
4
|
export * from './utils/paginate.js';
|
|
5
|
+
export * from './utils/prompts.js';
|
|
4
6
|
export * from './utils/configLoader.js';
|
|
5
7
|
export * from './utils/i18n.js';
|
|
8
|
+
export * from './utils/PaginationBuilder.js';
|
|
6
9
|
// Re-export everything from discord.js so users don't need to install it explicitly
|
|
7
10
|
export * from 'discord.js';
|