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.

@@ -1,4 +1,4 @@
1
- import { Message, EmbedBuilder } from 'discord.js';
1
+ import { Message, EmbedBuilder, version as djsVersion } from 'discord.js';
2
2
  import { exec } from 'child_process';
3
3
  import util from 'util';
4
4
  import os from 'os';
@@ -11,30 +11,88 @@ import { loadLocales } from '../utils/i18n.js';
11
11
 
12
12
  const execAsync = util.promisify(exec);
13
13
 
14
- export async function handleDNXT(message: Message, client: DJSNextClient, prefix: 'dnxt' | 'nxt' = 'dnxt') {
14
+ export function buildDisplayMessage(content: string, color = 0x525AF1) {
15
+ const djs = require('discord.js');
16
+ const container = new djs.ContainerBuilder()
17
+ .setAccentColor(color)
18
+ .addTextDisplayComponents(
19
+ new djs.TextDisplayBuilder().setContent(content)
20
+ );
21
+ return { components: [container], flags: 32768 };
22
+ }
23
+
24
+ export async function handleDNXT(message: Message, client: Client, devPrefix: 'dnxt' | 'nxt'): Promise<void> {
15
25
  if (message.author.bot) return;
16
- // Make sure only developers can use this
17
- if (!client['_developers'].includes(message.author.id)) return;
26
+ if (!(client as any)._developers.includes(message.author.id)) return;
27
+ if ((client as any).config?.devGuildId && message.guildId !== (client as any).config.devGuildId) return;
28
+
29
+ const originalReply = message.reply.bind(message);
30
+ message.reply = (async (content: any) => {
31
+ if (typeof content === 'string') {
32
+ return await originalReply({ ...buildDisplayMessage(content), allowedMentions: { repliedUser: false } });
33
+ }
34
+ if (!content.allowedMentions) content.allowedMentions = { repliedUser: false };
35
+ return await originalReply(content);
36
+ }) as any;
37
+
38
+ const content = message.content.trim();
39
+
40
+ // Build a list of valid dev command triggers: e.g. "dnxt", "!dnxt", "?dnxt", "nxt", "!nxt", etc.
41
+ const validTriggers = [devPrefix];
42
+ const clientPrefixes = (client as any)._prefixes || [];
43
+ for (const p of clientPrefixes) {
44
+ if (p !== '') validTriggers.push(`${p}${devPrefix}`);
45
+ }
18
46
 
19
- if (!message.content.startsWith(prefix)) return;
47
+ let matchedTrigger = validTriggers.find(t => content === t || content.startsWith(`${t} `));
48
+ if (!matchedTrigger) return;
20
49
 
21
- const args = message.content.slice(prefix.length).trim().split(/ +/);
50
+ const args = content.slice(matchedTrigger.length).trim().split(/ +/g);
22
51
  const command = args.shift()?.toLowerCase();
23
52
 
53
+ if (command === 'help') {
54
+ const djs = require('discord.js');
55
+
56
+ const container = new djs.ContainerBuilder()
57
+ .setAccentColor(0x525AF1)
58
+ .addTextDisplayComponents(
59
+ new djs.TextDisplayBuilder().setContent(`# 📖 DNXT Toolkit Reference\n> Current prefix trigger: \`${matchedTrigger}\``)
60
+ )
61
+ .addSeparatorComponents(new djs.SeparatorBuilder())
62
+ .addTextDisplayComponents(
63
+ new djs.TextDisplayBuilder().setContent(`### 📊 Core Framework\n- \`${matchedTrigger}\` — Developer system dashboard\n- \`${matchedTrigger} help\` — Shows this reference menu`),
64
+ new djs.TextDisplayBuilder().setContent(`### 💻 Execution & Diagnostics\n- \`${matchedTrigger} js <code>\` — Evaluates raw JS code\n- \`${matchedTrigger} sh <cmd>\` — Runs terminal shell script\n- \`${matchedTrigger} debug <code>\` — Evaluates JS with precise memory deltas`),
65
+ new djs.TextDisplayBuilder().setContent(`### 📂 File System & Network\n- \`${matchedTrigger} cat <file>\` — Reads file contents\n- \`${matchedTrigger} curl <url>\` — Fetches remote URL data\n- \`${matchedTrigger} git <cmd>\` — Executes git repository commands`),
66
+ new djs.TextDisplayBuilder().setContent(`### 🧰 Utilities\n- \`${matchedTrigger} <load|unload|reload> <target>\` — Manages system modules\n- \`${matchedTrigger} sync\` — Forces global slash command sync\n- \`${matchedTrigger} in <channel> <cmd>\` — Executes command in target channel\n- \`${matchedTrigger} restart\` — Restarts the bot completely\n- \`${matchedTrigger} shutdown\` — Stops the bot completely`)
67
+ );
68
+
69
+ await message.reply({ components: [container], flags: 32768, allowedMentions: { repliedUser: false } });
70
+ return;
71
+ }
72
+
24
73
  // Root dnxt command (Stats)
25
74
  if (!command) {
26
75
  const mem = process.memoryUsage();
27
- const discordJsVersion = require('discord.js/package.json').version;
28
76
  const botPing = client.ws.ping;
77
+ const djs = require('discord.js');
29
78
 
30
- const text =
31
- `Module was loaded <t:${Math.floor((Date.now() - client.uptime!) / 1000)}:R>.\n` +
32
- `DNXT framework plugin, discord.js \`${discordJsVersion}\`, \`Node.js ${process.version}\` on \`${os.type()}\`.\n` +
33
- `Latencies: \`${botPing}ms\` websocket ping.\n` +
34
- `Memory: \`${(mem.rss / 1024 / 1024).toFixed(2)} MB\` physical, \`${(mem.heapUsed / 1024 / 1024).toFixed(2)} MB\` heap.\n` +
35
- `System: \`${os.cpus().length}\` thread(s), \`${(os.uptime() / 60 / 60).toFixed(2)}\` hrs system uptime.`;
36
-
37
- await message.reply(text);
79
+ const container = new djs.ContainerBuilder()
80
+ .setAccentColor(0x525AF1)
81
+ .addTextDisplayComponents(
82
+ new djs.TextDisplayBuilder().setContent(`# 🛠️ Developer System Dashboard\n> **DNXT Framework Engine**`)
83
+ )
84
+ .addSeparatorComponents(new djs.SeparatorBuilder())
85
+ .addTextDisplayComponents(
86
+ new djs.TextDisplayBuilder().setContent(`### 📡 Network Status\n- **System Uptime:** <t:${Math.floor((Date.now() - client.uptime!) / 1000)}:R>\n- **WebSocket Latency:** \`${botPing}ms\``),
87
+ new djs.TextDisplayBuilder().setContent(`### 📦 Host Environment\n- **Discord.js Library:** \`v${djsVersion}\`\n- **Node.js Runtime:** \`${process.version}\`\n- **Operating System:** \`${os.type()}\` (\`${os.cpus().length}\` thread cores, \`${(os.uptime() / 60 / 60).toFixed(2)}\` hrs uptime)`),
88
+ new djs.TextDisplayBuilder().setContent(`### 🧠 Resource Utilization\n- **Physical Memory:** \`${(mem.rss / 1024 / 1024).toFixed(2)} MB\`\n- **Heap Allocated:** \`${(mem.heapUsed / 1024 / 1024).toFixed(2)} MB\``)
89
+ );
90
+
91
+ await message.reply({
92
+ components: [container],
93
+ flags: 32768, // MessageFlags.IsComponentsV2
94
+ allowedMentions: { repliedUser: false }
95
+ });
38
96
  return;
39
97
  }
40
98
 
@@ -55,11 +113,11 @@ export async function handleDNXT(message: Message, client: DJSNextClient, prefix
55
113
  const end = process.hrtime.bigint();
56
114
  const timeMs = Number(end - start) / 1e6;
57
115
 
58
- if (typeof evaled !== 'string') evaled = util.inspect(evaled, { depth: 1 });
116
+ if (typeof evaled !== 'string') evaled = util.inspect(evaled, { depth: 1, colors: true });
59
117
 
60
- await sendPaginatedText(message, `✅ **Evaluated in ${timeMs.toFixed(3)}ms**\n`, evaled, 'js');
118
+ await sendPaginatedText(message, `✅ **Evaluated in ${timeMs.toFixed(3)}ms**\n`, evaled, 'ansi');
61
119
  } catch (err: any) {
62
- await sendPaginatedText(message, `❌ **Evaluation Error**\n`, err.message, 'js');
120
+ await sendPaginatedText(message, `❌ **Evaluation Error**\n`, err.message, 'ansi');
63
121
  }
64
122
  return;
65
123
  }
@@ -71,53 +129,72 @@ export async function handleDNXT(message: Message, client: DJSNextClient, prefix
71
129
 
72
130
  try {
73
131
  const start = process.hrtime.bigint();
74
- const { stdout, stderr } = await execAsync(cmd);
132
+ let stdout = '', stderr = '', code = 0;
133
+ try {
134
+ const result = await execAsync(cmd);
135
+ stdout = result.stdout;
136
+ stderr = result.stderr;
137
+ } catch (err: any) {
138
+ stdout = err.stdout || '';
139
+ stderr = err.stderr || err.message || '';
140
+ code = err.code || 1;
141
+ }
75
142
  const end = process.hrtime.bigint();
76
143
  const timeMs = Number(end - start) / 1e6;
77
144
 
78
- const result = stdout || stderr || 'No output.';
79
- await sendPaginatedText(message, `✅ **Executed in ${timeMs.toFixed(3)}ms**\n`, result, 'sh');
145
+ let resultText = `$ ${cmd}\n`;
146
+ if (stdout) resultText += `${stdout}\n`;
147
+ if (stderr) resultText += `${stderr}\n`;
148
+ resultText += `\n[status] Return code ${code}`;
149
+
150
+ await sendPaginatedText(message, `✅ **Executed in ${timeMs.toFixed(3)}ms**\n`, resultText, 'ansi');
80
151
  } catch (err: any) {
81
- await sendPaginatedText(message, `❌ **Shell Error**\n`, err.message, 'sh');
152
+ await sendPaginatedText(message, `❌ **Shell Error**\n`, err.message, 'ansi');
82
153
  }
83
154
  return;
84
155
  }
85
156
 
86
157
  // Load / Unload / Reload
87
- if (command === 'reload' || command === 'load') {
158
+ if (command === 'load' || command === 'unload' || command === 'reload') {
88
159
  const target = args[0]?.toLowerCase();
89
160
  try {
90
161
  if (target === 'commands' && client['_commandsDir']) {
91
- client.commands.clear();
92
- client.commands = await loadAndDeployCommands(client['_commandsDir'], client.token!, client['_clientId']!, client['_guildId']);
93
- await message.reply('✅ Reloaded commands.');
162
+ if (command === 'unload' || command === 'reload') client.commands.clear();
163
+ if (command === 'load' || command === 'reload') client.commands = await loadAndDeployCommands(client['_commandsDir'], client.token!, client['_clientId']!, client['_guildId']);
164
+ await message.reply(`✅ Successfully ${command}ed commands.`);
94
165
  } else if (target === 'events' && client['_eventsDir']) {
95
- client.removeAllListeners();
96
- client['attachCoreListeners']();
97
- await loadEvents(client, client['_eventsDir']);
98
- await message.reply('✅ Reloaded events.');
166
+ if (command === 'unload' || command === 'reload') {
167
+ client.removeAllListeners();
168
+ client['attachCoreListeners']();
169
+ }
170
+ if (command === 'load' || command === 'reload') await loadEvents(client, client['_eventsDir']);
171
+ await message.reply(`✅ Successfully ${command}ed events.`);
99
172
  } else if (target === 'components' && client['_componentsDir']) {
100
- client.components.clear();
101
- client.components = await loadComponents(client['_componentsDir']);
102
- await message.reply('✅ Reloaded components.');
173
+ if (command === 'unload' || command === 'reload') client.components.clear();
174
+ if (command === 'load' || command === 'reload') client.components = await loadComponents(client['_componentsDir']);
175
+ await message.reply(`✅ Successfully ${command}ed components.`);
103
176
  } else if (target === 'locales' && client['_localesDir']) {
104
- loadLocales(client['_localesDir'], client.config.defaultLocale);
105
- await message.reply('✅ Reloaded locales.');
177
+ if (command === 'load' || command === 'reload') loadLocales(client['_localesDir'], client.config.defaultLocale);
178
+ await message.reply(`✅ Successfully ${command}ed locales.`);
106
179
  } else if (target === 'all' || !target) {
107
- client.removeAllListeners();
108
- client['attachCoreListeners']();
109
- client.commands.clear();
110
- client.components.clear();
111
- if (client['_eventsDir']) await loadEvents(client, client['_eventsDir']);
112
- if (client['_componentsDir']) client.components = await loadComponents(client['_componentsDir']);
113
- if (client['_localesDir']) loadLocales(client['_localesDir'], client.config.defaultLocale);
114
- if (client['_commandsDir']) client.commands = await loadAndDeployCommands(client['_commandsDir'], client.token!, client['_clientId']!, client['_guildId']);
115
- await message.reply(' Reloaded all framework modules.');
180
+ if (command === 'unload' || command === 'reload') {
181
+ client.removeAllListeners();
182
+ client['attachCoreListeners']();
183
+ client.commands.clear();
184
+ client.components.clear();
185
+ }
186
+ if (command === 'load' || command === 'reload') {
187
+ if (client['_eventsDir']) await loadEvents(client, client['_eventsDir']);
188
+ if (client['_componentsDir']) client.components = await loadComponents(client['_componentsDir']);
189
+ if (client['_localesDir']) loadLocales(client['_localesDir'], client.config.defaultLocale);
190
+ if (client['_commandsDir']) client.commands = await loadAndDeployCommands(client['_commandsDir'], client.token!, client['_clientId']!, client['_guildId']);
191
+ }
192
+ await message.reply(`✅ Successfully ${command}ed all framework modules.`);
116
193
  } else {
117
194
  await message.reply('❌ Unknown target. Valid targets: `commands, events, components, locales, all`');
118
195
  }
119
196
  } catch (err: any) {
120
- await message.reply(`❌ **Reload Error:** ${err.message}`);
197
+ await message.reply(`❌ **${command.toUpperCase()} Error:** ${err.message}`);
121
198
  }
122
199
  return;
123
200
  }
@@ -138,48 +215,9 @@ export async function handleDNXT(message: Message, client: DJSNextClient, prefix
138
215
  return;
139
216
  }
140
217
 
141
- // Sudo (Invoke command as another user)
142
- if (command === 'su' || command === 'sudo') {
143
- const targetUserId = args.shift();
144
- const cmd = args.join(' ');
145
- if (!targetUserId || !cmd) return void await message.reply(`❌ Usage: ${prefix} su <user_id> <command>`);
146
-
147
- try {
148
- const targetUser = await client.users.fetch(targetUserId.replace(/<@!?|>/g, ''));
149
- if (!targetUser) throw new Error('User not found.');
150
-
151
- // Create a mock message
152
- const mockMessage = Object.assign(Object.create(Object.getPrototypeOf(message)), message);
153
- mockMessage.author = targetUser;
154
- if (message.guild) {
155
- mockMessage.member = await message.guild.members.fetch(targetUser.id).catch(() => null);
156
- }
157
- mockMessage.content = cmd;
158
-
159
- client.emit('messageCreate', mockMessage as Message);
160
- await message.reply(`✅ Invoked \`${cmd}\` as **${targetUser.tag}**.`);
161
- } catch (e: any) {
162
- await message.reply(`❌ **Sudo Error:** ${e.message}`);
163
- }
164
- return;
165
- }
166
218
 
167
- // Source Command (dnxt source <command>)
168
- if (command === 'source' || command === 'src') {
169
- const target = args.join(' ');
170
- if (!target) return void await message.reply('❌ Please provide a command name.');
171
- const cmdData = client.commands.get(target);
172
- if (!cmdData || !cmdData.filepath) return void await message.reply('❌ Command not found or has no associated filepath.');
173
-
174
- try {
175
- const fs = await import('fs');
176
- const content = fs.readFileSync(cmdData.filepath, 'utf8');
177
- await sendPaginatedText(message, `📄 **Source of \`${target}\`**\n`, content, cmdData.filepath.split('.').pop() || '');
178
- } catch (e: any) {
179
- await message.reply(`❌ **Error reading source:** ${e.message}`);
180
- }
181
- return;
182
- }
219
+
220
+
183
221
 
184
222
  // Curl (dnxt curl <url>)
185
223
  if (command === 'curl') {
@@ -217,11 +255,11 @@ export async function handleDNXT(message: Message, client: DJSNextClient, prefix
217
255
  const timeMs = Number(end - start) / 1e6;
218
256
  const memDiff = (endMem - startMem) / 1024 / 1024;
219
257
 
220
- if (typeof evaled !== 'string') evaled = util.inspect(evaled, { depth: 1 });
258
+ if (typeof evaled !== 'string') evaled = util.inspect(evaled, { depth: 1, colors: true });
221
259
 
222
- await sendPaginatedText(message, `⏱️ **Debug Execution**\nTime: \`${timeMs.toFixed(3)}ms\` | Heap Delta: \`${memDiff.toFixed(3)}MB\`\n`, evaled, 'js');
260
+ await sendPaginatedText(message, `⏱️ **Debug Execution**\nTime: \`${timeMs.toFixed(3)}ms\` | Heap Delta: \`${memDiff.toFixed(3)}MB\`\n`, evaled, 'ansi');
223
261
  } catch (err: any) {
224
- await sendPaginatedText(message, `❌ **Debug Error**\n`, err.message, 'js');
262
+ await sendPaginatedText(message, `❌ **Debug Error**\n`, err.message, 'ansi');
225
263
  }
226
264
  return;
227
265
  }
@@ -230,59 +268,27 @@ export async function handleDNXT(message: Message, client: DJSNextClient, prefix
230
268
  if (command === 'in') {
231
269
  const channelId = args.shift()?.replace(/<#|>/g, '');
232
270
  const cmd = args.join(' ');
233
- if (!channelId || !cmd) return void await message.reply(`❌ Usage: ${prefix} in <channel> <command>`);
271
+ if (!channelId || !cmd) return void await message.reply(`❌ Usage: ${matchedTrigger} in <#channel|id> <command>`);
234
272
  try {
235
273
  const targetChannel = await client.channels.fetch(channelId);
236
274
  if (!targetChannel || !targetChannel.isTextBased()) throw new Error('Invalid Text Channel.');
237
275
 
238
276
  const mockMessage = Object.assign(Object.create(Object.getPrototypeOf(message)), message);
239
- mockMessage.channel = targetChannel;
240
- mockMessage.channelId = targetChannel.id;
241
- mockMessage.content = cmd;
242
-
277
+ Object.defineProperty(mockMessage, 'client', { value: client, configurable: true });
278
+ if (message.guild) {
279
+ Object.defineProperty(mockMessage, 'guild', { value: message.guild, configurable: true });
280
+ Object.defineProperty(mockMessage, 'guildId', { value: message.guildId, configurable: true });
281
+ }
282
+ Object.defineProperty(mockMessage, 'channel', { value: targetChannel, configurable: true });
283
+ Object.defineProperty(mockMessage, 'channelId', { value: targetChannel.id, configurable: true });
284
+ Object.defineProperty(mockMessage, 'content', { value: cmd, configurable: true });
243
285
  client.emit('messageCreate', mockMessage as Message);
244
- await message.reply(`✅ Redirected execution to <#${targetChannel.id}>.`);
245
286
  } catch (e: any) {
246
287
  await message.reply(`❌ **In Error:** ${e.message}`);
247
288
  }
248
289
  return;
249
290
  }
250
291
 
251
- // Tasks Command (dnxt tasks)
252
- if (command === 'tasks') {
253
- const tasks = (client as any)._activeTasks as Map<string, NodeJS.Timeout>;
254
- if (!tasks || tasks.size === 0) return void await message.reply('No active background tasks running.');
255
-
256
- let text = `⚙️ **Active Background Tasks (${tasks.size})**\n`;
257
- for (const [name] of tasks.entries()) {
258
- text += `- \`${name.split('/').pop()}\`\n`;
259
- }
260
- await sendPaginatedText(message, '', text, '');
261
- return;
262
- }
263
-
264
- // Cancel Command (dnxt cancel <task>)
265
- if (command === 'cancel') {
266
- const target = args.join(' ');
267
- if (!target) return void await message.reply(`❌ Usage: ${prefix} cancel <task_name>`);
268
-
269
- const tasks = (client as any)._activeTasks as Map<string, NodeJS.Timeout>;
270
- if (!tasks) return void await message.reply('No active tasks to cancel.');
271
-
272
- let found = false;
273
- for (const [name, intervalId] of tasks.entries()) {
274
- if (name.includes(target)) {
275
- clearInterval(intervalId);
276
- tasks.delete(name);
277
- found = true;
278
- await message.reply(`✅ Cancelled background task: \`${name.split('/').pop()}\``);
279
- break;
280
- }
281
- }
282
- if (!found) await message.reply('❌ Task not found.');
283
- return;
284
- }
285
-
286
292
  // Sync Command (dnxt sync)
287
293
  if (command === 'sync') {
288
294
  try {
@@ -295,49 +301,37 @@ export async function handleDNXT(message: Message, client: DJSNextClient, prefix
295
301
  return;
296
302
  }
297
303
 
298
- // SQL Command (dnxt sql <query>)
299
- if (command === 'sql') {
300
- const query = args.join(' ');
301
- if (!query) return void await message.reply(`❌ Usage: ${prefix} sql <query>`);
304
+ // Restart Command (dnxt restart)
305
+ if (command === 'restart') {
306
+ await message.reply('🔄 Restarting framework...');
307
+ client.destroy();
302
308
 
303
- try {
304
- let res: any;
305
- if (client.db && typeof client.db.$queryRawUnsafe === 'function') {
306
- res = await client.db.$queryRawUnsafe(query); // Prisma fallback
307
- } else if (client.db && typeof client.db.query === 'function') {
308
- res = await client.db.query(query); // PG/MySQL fallback
309
- } else {
310
- return void await message.reply('❌ Your configured `client.db` does not expose a recognized raw SQL execution method (`$queryRawUnsafe` or `query`).');
311
- }
312
- const inspect = util.inspect(res, { depth: 2 });
313
- await sendPaginatedText(message, `🗄️ **SQL Query**\n`, inspect, 'js');
314
- } catch (e: any) {
315
- await message.reply(`❌ **SQL Error:** ${e.message}`);
316
- }
309
+ const { spawn } = require('child_process');
310
+ const child = spawn(process.argv[0], process.argv.slice(1), {
311
+ detached: true,
312
+ stdio: 'ignore',
313
+ cwd: process.cwd()
314
+ });
315
+ child.unref();
316
+ process.exit(0);
317
317
  return;
318
318
  }
319
319
 
320
- // Voice Command (dnxt vc)
321
- if (command === 'vc' || command === 'voice') {
322
- if (!message.guild) return void await message.reply(' This command must be used in a server.');
323
- const me = message.guild.members.me;
324
- if (!me?.voice?.channel) return void await message.reply('❌ Bot is not currently in a voice channel.');
325
-
326
- const text = `🎙️ **Voice Debugger**\n` +
327
- `Channel: <#${me.voice.channel.id}> (\`${me.voice.channel.id}\`)\n` +
328
- `Muted: ${me.voice.mute} | Deafened: ${me.voice.deaf}\n` +
329
- `Session ID: \`${me.voice.sessionId || 'None'}\``;
330
- await message.reply(text);
320
+ // Shutdown Command (dnxt shutdown | stop)
321
+ if (command === 'shutdown' || command === 'stop') {
322
+ await message.reply('🛑 Shutting down framework...');
323
+ client.destroy();
324
+ process.exit(0);
331
325
  return;
332
326
  }
333
327
 
334
- await message.reply(`❓ Unknown ${prefix} command. Available: \`js, sh, git, cat, curl, su, in, source, debug, reload, tasks, cancel, sync, sql, vc\``);
328
+ await message.reply(`❓ Unknown ${matchedTrigger} command. Available: \`js, sh, git, cat, curl, in, debug, reload, sync, restart, shutdown\``);
335
329
  }
336
330
 
337
331
  async function sendPaginatedText(message: Message, header: string, content: string, language: string = '') {
338
- const maxLength = 1900;
332
+ const maxLength = 800;
339
333
  if (content.length <= maxLength) {
340
- await message.reply(`${header}\`\`\`${language}\n${content}\n\`\`\``);
334
+ await message.reply(buildDisplayMessage(`### ${header.replace(/\\*\\*/g, '')}\n\`\`\`${language}\n${content}\n\`\`\``));
341
335
  return;
342
336
  }
343
337
 
@@ -347,33 +341,78 @@ async function sendPaginatedText(message: Message, header: string, content: stri
347
341
  }
348
342
 
349
343
  let index = 0;
350
- const reply = await message.reply(`${header}\`\`\`${language}\n${chunks[index]}\n\`\`\`\n*Page 1 of ${chunks.length}*`);
344
+ const djs = require('discord.js');
345
+
346
+ function buildPage(idx: number) {
347
+ const text = `### ${header.replace(/\\*\\*/g, '')}\n\`\`\`${language}\n${chunks[idx]}\n\`\`\`\n> *Page ${idx + 1} of ${chunks.length}*`;
348
+ const container = new djs.ContainerBuilder()
349
+ .setAccentColor(0x525AF1)
350
+ .addTextDisplayComponents(
351
+ new djs.TextDisplayBuilder().setContent(text)
352
+ );
353
+
354
+ const row1 = new djs.ActionRowBuilder().addComponents(
355
+ new djs.ButtonBuilder().setCustomId('first').setLabel('≪').setStyle(2),
356
+ new djs.ButtonBuilder().setCustomId('prev').setLabel('<').setStyle(2),
357
+ new djs.ButtonBuilder().setCustomId('goto').setLabel('⎘').setStyle(1),
358
+ new djs.ButtonBuilder().setCustomId('next').setLabel('>').setStyle(2),
359
+ new djs.ButtonBuilder().setCustomId('last').setLabel('≫').setStyle(2)
360
+ );
361
+
362
+ const row2 = new djs.ActionRowBuilder().addComponents(
363
+ new djs.ButtonBuilder().setCustomId('stop').setLabel('✖ Close').setStyle(4)
364
+ );
365
+
366
+ container.addActionRowComponents(row1, row2);
367
+ return { components: [container], flags: 32768 };
368
+ }
351
369
 
352
- await reply.react('◀️');
353
- await reply.react('▶️');
354
- await reply.react('⏹️');
370
+ const reply = await message.reply(buildPage(index));
355
371
 
356
- const collector = reply.createReactionCollector({
357
- filter: (reaction, user) => ['◀️', '▶️', '⏹️'].includes(reaction.emoji.name!) && user.id === message.author.id,
372
+ const collector = reply.createMessageComponentCollector({
373
+ filter: (i) => ['first', 'prev', 'goto', 'stop', 'next', 'last'].includes(i.customId) && i.user.id === message.author.id,
358
374
  time: 120000
359
375
  });
360
376
 
361
- collector.on('collect', async (reaction, user) => {
362
- await reaction.users.remove(user.id).catch(() => null);
363
-
364
- if (reaction.emoji.name === '◀️') {
377
+ collector.on('collect', async (i) => {
378
+ if (i.customId === 'first') {
379
+ index = 0;
380
+ } else if (i.customId === 'prev') {
365
381
  index = index > 0 ? index - 1 : index;
366
- } else if (reaction.emoji.name === '▶️') {
382
+ } else if (i.customId === 'next') {
367
383
  index = index < chunks.length - 1 ? index + 1 : index;
368
- } else if (reaction.emoji.name === '⏹️') {
384
+ } else if (i.customId === 'last') {
385
+ index = chunks.length - 1;
386
+ } else if (i.customId === 'goto') {
387
+ const modal = new djs.ModalBuilder()
388
+ .setCustomId('goto_modal')
389
+ .setTitle('Go to Page');
390
+ const input = new djs.TextInputBuilder()
391
+ .setCustomId('page_num')
392
+ .setLabel(`Page Number (1-${chunks.length})`)
393
+ .setStyle(1)
394
+ .setRequired(true);
395
+ modal.addComponents(new djs.ActionRowBuilder().addComponents(input));
396
+ await i.showModal(modal);
397
+ try {
398
+ const modalSubmit = await i.awaitModalSubmit({ filter: (mi) => mi.user.id === message.author.id && mi.customId === 'goto_modal', time: 60000 });
399
+ const targetPage = parseInt(modalSubmit.fields.getTextInputValue('page_num'), 10);
400
+ if (!isNaN(targetPage) && targetPage >= 1 && targetPage <= chunks.length) {
401
+ index = targetPage - 1;
402
+ }
403
+ await modalSubmit.update(buildPage(index));
404
+ } catch {}
405
+ return;
406
+ } else if (i.customId === 'stop') {
407
+ await reply.delete().catch(() => null);
369
408
  collector.stop();
370
409
  return;
371
410
  }
372
411
 
373
- await reply.edit(`${header}\`\`\`${language}\n${chunks[index]}\n\`\`\`\n*Page ${index + 1} of ${chunks.length}*`);
412
+ await i.update(buildPage(index));
374
413
  });
375
414
 
376
415
  collector.on('end', () => {
377
- reply.reactions.removeAll().catch(() => null);
416
+ reply.edit({ components: [] }).catch(() => null);
378
417
  });
379
418
  }
@@ -14,12 +14,17 @@ const client = new DJSNextClient({
14
14
  client.start(process.env.DISCORD_TOKEN);
15
15
  `,
16
16
 
17
- ping: `module.exports = {
18
- command: {
19
- description: 'Replies with Pong!',
20
- execute: async (interaction) => {
21
- await interaction.reply('Pong!');
22
- }
17
+ ping: `/** @type {import('djs-next').FileCommand} */
18
+ module.exports = {
19
+ description: 'Replies with the actual bot latency!',
20
+ execute: async (interaction, client) => {
21
+ const sent = await interaction.reply({ content: 'Pinging...', withResponse: true });
22
+ const msg = sent.resource?.message || await interaction.fetchReply();
23
+ await interaction.editReply(\`Pong! 🏓\\nWebsocket Latency: \\\`\${client.ws.ping}ms\\\`\\nAPI Latency: \\\`\${msg.createdTimestamp - interaction.createdTimestamp}ms\\\`\`);
24
+ },
25
+ executeText: async (message, args, client) => {
26
+ const sent = await message.reply('Pinging...');
27
+ await sent.edit(\`Pong! 🏓\\nWebsocket Latency: \\\`\${client.ws.ping}ms\\\`\\nAPI Latency: \\\`\${sent.createdTimestamp - message.createdTimestamp}ms\\\`\`);
23
28
  }
24
29
  };
25
30
  `,
@@ -14,10 +14,17 @@ const client = new DJSNextClient({
14
14
  client.start(process.env.DISCORD_TOKEN);
15
15
  `,
16
16
 
17
- ping: `export const command = {
18
- description: 'Replies with Pong!',
19
- execute: async (interaction) => {
20
- await interaction.reply('Pong!');
17
+ ping: `/** @type {import('djs-next').FileCommand} */
18
+ export default {
19
+ description: 'Replies with the actual bot latency!',
20
+ execute: async (interaction, client) => {
21
+ const sent = await interaction.reply({ content: 'Pinging...', withResponse: true });
22
+ const msg = sent.resource?.message || await interaction.fetchReply();
23
+ await interaction.editReply(\`Pong! 🏓\\nWebsocket Latency: \\\`\${client.ws.ping}ms\\\`\\nAPI Latency: \\\`\${msg.createdTimestamp - interaction.createdTimestamp}ms\\\`\`);
24
+ },
25
+ executeText: async (message, args, client) => {
26
+ const sent = await message.reply('Pinging...');
27
+ await sent.edit(\`Pong! 🏓\\nWebsocket Latency: \\\`\${client.ws.ping}ms\\\`\\nAPI Latency: \\\`\${sent.createdTimestamp - message.createdTimestamp}ms\\\`\`);
21
28
  }
22
29
  };
23
30
  `,
@@ -16,12 +16,18 @@ client.start(process.env.DISCORD_TOKEN!);
16
16
 
17
17
  ping: `import { FileCommand } from 'djs-next';
18
18
 
19
- export const command: FileCommand = {
20
- description: 'Replies with Pong!',
21
- execute: async (interaction) => {
22
- await interaction.reply('Pong!');
19
+ export default {
20
+ description: 'Replies with the actual bot latency!',
21
+ execute: async (interaction, client) => {
22
+ const sent = await interaction.reply({ content: 'Pinging...', withResponse: true });
23
+ const msg = sent.resource?.message || await interaction.fetchReply();
24
+ await interaction.editReply(\`Pong! 🏓\\nWebsocket Latency: \\\`\${client.ws.ping}ms\\\`\\nAPI Latency: \\\`\${msg.createdTimestamp - interaction.createdTimestamp}ms\\\`\`);
25
+ },
26
+ executeText: async (message, args, client) => {
27
+ const sent = await message.reply('Pinging...');
28
+ await sent.edit(\`Pong! 🏓\\nWebsocket Latency: \\\`\${client.ws.ping}ms\\\`\\nAPI Latency: \\\`\${sent.createdTimestamp - message.createdTimestamp}ms\\\`\`);
23
29
  }
24
- };
30
+ } as FileCommand;
25
31
  `,
26
32
 
27
33
  ready: `import { DJSNextEvent, Events } from 'djs-next';
package/src/types.ts CHANGED
@@ -12,8 +12,21 @@ import {
12
12
  Message
13
13
  } from 'discord.js';
14
14
 
15
+ export interface CooldownAdapter {
16
+ get(commandId: string, userId: string): Promise<number | null> | number | null;
17
+ set(commandId: string, userId: string, expirationTime: number): Promise<void> | void;
18
+ }
19
+
15
20
  export interface DJSNextConfig {
16
21
  devGuildId?: string;
22
+ errorLogChannelId?: string;
23
+ responses?: {
24
+ developerOnly?: string | null;
25
+ guildOnly?: string | null;
26
+ cooldown?: string | null;
27
+ missingPerms?: string | null;
28
+ errorBoundary?: string | null;
29
+ };
17
30
  locales?: string[];
18
31
  defaultLocale?: string;
19
32
  directories?: {
@@ -23,6 +36,7 @@ export interface DJSNextConfig {
23
36
  tasks?: string;
24
37
  locales?: string;
25
38
  };
39
+ cooldownAdapter?: CooldownAdapter;
26
40
  }
27
41
 
28
42
  export interface DJSNextClientOptions extends ClientOptions {
@@ -34,9 +48,13 @@ export interface DJSNextClientOptions extends ClientOptions {
34
48
  guildId?: string;
35
49
  developers?: string[];
36
50
  prefixes?: string[] | string;
37
- enableMentionPrefix?: boolean;
51
+ enableSlashCommands?: boolean;
52
+ enableTextCommands?: boolean;
53
+ enableMentionPrefix?: boolean | string[];
54
+ enableNoPrefix?: boolean | string[];
38
55
  middleware?: (interaction: Interaction | Message, client: Client) => Promise<boolean> | boolean;
39
56
  config?: DJSNextConfig;
57
+ db?: any;
40
58
  }
41
59
 
42
60
  export interface FileTask<DB = any> {
@@ -55,6 +73,7 @@ export interface FileCommand<DB = any> {
55
73
  developerOnly?: boolean;
56
74
  guildOnly?: boolean;
57
75
  aliases?: string[];
76
+ preconditions?: string[];
58
77
  execute?: (interaction: ChatInputCommandInteraction, client: Client & { db: DB; t: Function; config: DJSNextConfig }) => Promise<void> | void;
59
78
  executeText?: (message: Message, args: string[], client: Client & { db: DB; t: Function; config: DJSNextConfig }) => Promise<void> | void;
60
79
  autocomplete?: (interaction: AutocompleteInteraction, client: Client & { db: DB; t: Function; config: DJSNextConfig }) => Promise<void> | void;
@@ -63,6 +82,7 @@ export interface FileCommand<DB = any> {
63
82
  export interface FileComponent<DB = any> {
64
83
  filepath?: string;
65
84
  customId?: string;
85
+ preconditions?: string[];
66
86
  execute: (interaction: MessageComponentInteraction | ModalSubmitInteraction, client: Client & { db: DB; t: Function; config: DJSNextConfig }, params?: Record<string, string>) => Promise<void> | void;
67
87
  }
68
88