djs-next 1.0.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of djs-next might be problematic. Click here for more details.

@@ -0,0 +1,379 @@
1
+ import { Message, EmbedBuilder } from 'discord.js';
2
+ import { exec } from 'child_process';
3
+ import util from 'util';
4
+ import os from 'os';
5
+ import { DJSNextClient } from '../client.js';
6
+ import { loadEvents } from '../handlers/eventHandler.js';
7
+ import { loadComponents } from '../handlers/componentHandler.js';
8
+ import { loadAndDeployCommands } from '../handlers/commandHandler.js';
9
+ import { loadTasks } from '../handlers/taskHandler.js';
10
+ import { loadLocales } from '../utils/i18n.js';
11
+
12
+ const execAsync = util.promisify(exec);
13
+
14
+ export async function handleDNXT(message: Message, client: DJSNextClient, prefix: 'dnxt' | 'nxt' = 'dnxt') {
15
+ if (message.author.bot) return;
16
+ // Make sure only developers can use this
17
+ if (!client['_developers'].includes(message.author.id)) return;
18
+
19
+ if (!message.content.startsWith(prefix)) return;
20
+
21
+ const args = message.content.slice(prefix.length).trim().split(/ +/);
22
+ const command = args.shift()?.toLowerCase();
23
+
24
+ // Root dnxt command (Stats)
25
+ if (!command) {
26
+ const mem = process.memoryUsage();
27
+ const discordJsVersion = require('discord.js/package.json').version;
28
+ const botPing = client.ws.ping;
29
+
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);
38
+ return;
39
+ }
40
+
41
+ // Evaluation (js, eval, py)
42
+ if (command === 'js' || command === 'eval' || command === 'py') {
43
+ let code = args.join(' ');
44
+ if (code.startsWith('```js') || code.startsWith('```py')) code = code.replace(/^```[a-z]*|```$/g, '');
45
+ else if (code.startsWith('```')) code = code.replace(/^```|```$/g, '');
46
+
47
+ if (!code) return void await message.reply('❌ Please provide code to evaluate.');
48
+
49
+ try {
50
+ const start = process.hrtime.bigint();
51
+ const { commands, components, config } = client;
52
+
53
+ // eslint-disable-next-line no-eval
54
+ let evaled = await eval(`(async () => { ${code} })()`);
55
+ const end = process.hrtime.bigint();
56
+ const timeMs = Number(end - start) / 1e6;
57
+
58
+ if (typeof evaled !== 'string') evaled = util.inspect(evaled, { depth: 1 });
59
+
60
+ await sendPaginatedText(message, `✅ **Evaluated in ${timeMs.toFixed(3)}ms**\n`, evaled, 'js');
61
+ } catch (err: any) {
62
+ await sendPaginatedText(message, `❌ **Evaluation Error**\n`, err.message, 'js');
63
+ }
64
+ return;
65
+ }
66
+
67
+ // Execute Shell (sh, shell, git)
68
+ if (command === 'sh' || command === 'shell' || command === 'git') {
69
+ const cmd = command === 'git' ? 'git ' + args.join(' ') : args.join(' ');
70
+ if (!cmd) return void await message.reply('❌ Please provide a command to execute.');
71
+
72
+ try {
73
+ const start = process.hrtime.bigint();
74
+ const { stdout, stderr } = await execAsync(cmd);
75
+ const end = process.hrtime.bigint();
76
+ const timeMs = Number(end - start) / 1e6;
77
+
78
+ const result = stdout || stderr || 'No output.';
79
+ await sendPaginatedText(message, `✅ **Executed in ${timeMs.toFixed(3)}ms**\n`, result, 'sh');
80
+ } catch (err: any) {
81
+ await sendPaginatedText(message, `❌ **Shell Error**\n`, err.message, 'sh');
82
+ }
83
+ return;
84
+ }
85
+
86
+ // Load / Unload / Reload
87
+ if (command === 'reload' || command === 'load') {
88
+ const target = args[0]?.toLowerCase();
89
+ try {
90
+ 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.');
94
+ } else if (target === 'events' && client['_eventsDir']) {
95
+ client.removeAllListeners();
96
+ client['attachCoreListeners']();
97
+ await loadEvents(client, client['_eventsDir']);
98
+ await message.reply('✅ Reloaded events.');
99
+ } else if (target === 'components' && client['_componentsDir']) {
100
+ client.components.clear();
101
+ client.components = await loadComponents(client['_componentsDir']);
102
+ await message.reply('✅ Reloaded components.');
103
+ } else if (target === 'locales' && client['_localesDir']) {
104
+ loadLocales(client['_localesDir'], client.config.defaultLocale);
105
+ await message.reply('✅ Reloaded locales.');
106
+ } 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.');
116
+ } else {
117
+ await message.reply('❌ Unknown target. Valid targets: `commands, events, components, locales, all`');
118
+ }
119
+ } catch (err: any) {
120
+ await message.reply(`❌ **Reload Error:** ${err.message}`);
121
+ }
122
+ return;
123
+ }
124
+
125
+ // Cat (Read File)
126
+ if (command === 'cat') {
127
+ const fs = await import('fs');
128
+ const path = await import('path');
129
+ const file = args.join(' ');
130
+ if (!file) return void await message.reply('❌ Please provide a file to read.');
131
+
132
+ try {
133
+ const content = fs.readFileSync(path.resolve(process.cwd(), file), 'utf8');
134
+ await sendPaginatedText(message, `📄 **${file}**\n`, content, file.split('.').pop() || '');
135
+ } catch (e: any) {
136
+ await message.reply(`❌ **Error reading file:** ${e.message}`);
137
+ }
138
+ return;
139
+ }
140
+
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
+
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
+ }
183
+
184
+ // Curl (dnxt curl <url>)
185
+ if (command === 'curl') {
186
+ const url = args[0];
187
+ if (!url) return void await message.reply('❌ Please provide a URL.');
188
+ try {
189
+ const res = await fetch(url);
190
+ const text = await res.text();
191
+ await sendPaginatedText(message, `🌐 **Fetched from \`${url}\`**\n`, text, 'html');
192
+ } catch (e: any) {
193
+ await message.reply(`❌ **Curl Error:** ${e.message}`);
194
+ }
195
+ return;
196
+ }
197
+
198
+ // Debug Command (dnxt debug <command>)
199
+ // In discord.py DNXT debug measures execution of a command.
200
+ // In our case we will run a js eval and time it explicitly with heap usage.
201
+ if (command === 'debug') {
202
+ let code = args.join(' ');
203
+ if (code.startsWith('```js') || code.startsWith('```py')) code = code.replace(/^```[a-z]*|```$/g, '');
204
+ else if (code.startsWith('```')) code = code.replace(/^```|```$/g, '');
205
+ if (!code) return void await message.reply('❌ Please provide code to debug.');
206
+
207
+ try {
208
+ const startMem = process.memoryUsage().heapUsed;
209
+ const start = process.hrtime.bigint();
210
+ const { commands, components, config } = client;
211
+
212
+ // eslint-disable-next-line no-eval
213
+ let evaled = await eval(`(async () => { ${code} })()`);
214
+
215
+ const end = process.hrtime.bigint();
216
+ const endMem = process.memoryUsage().heapUsed;
217
+ const timeMs = Number(end - start) / 1e6;
218
+ const memDiff = (endMem - startMem) / 1024 / 1024;
219
+
220
+ if (typeof evaled !== 'string') evaled = util.inspect(evaled, { depth: 1 });
221
+
222
+ await sendPaginatedText(message, `⏱️ **Debug Execution**\nTime: \`${timeMs.toFixed(3)}ms\` | Heap Delta: \`${memDiff.toFixed(3)}MB\`\n`, evaled, 'js');
223
+ } catch (err: any) {
224
+ await sendPaginatedText(message, `❌ **Debug Error**\n`, err.message, 'js');
225
+ }
226
+ return;
227
+ }
228
+
229
+ // In Command (dnxt in <channel_id> <command>)
230
+ if (command === 'in') {
231
+ const channelId = args.shift()?.replace(/<#|>/g, '');
232
+ const cmd = args.join(' ');
233
+ if (!channelId || !cmd) return void await message.reply(`❌ Usage: ${prefix} in <channel> <command>`);
234
+ try {
235
+ const targetChannel = await client.channels.fetch(channelId);
236
+ if (!targetChannel || !targetChannel.isTextBased()) throw new Error('Invalid Text Channel.');
237
+
238
+ const mockMessage = Object.assign(Object.create(Object.getPrototypeOf(message)), message);
239
+ mockMessage.channel = targetChannel;
240
+ mockMessage.channelId = targetChannel.id;
241
+ mockMessage.content = cmd;
242
+
243
+ client.emit('messageCreate', mockMessage as Message);
244
+ await message.reply(`✅ Redirected execution to <#${targetChannel.id}>.`);
245
+ } catch (e: any) {
246
+ await message.reply(`❌ **In Error:** ${e.message}`);
247
+ }
248
+ return;
249
+ }
250
+
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
+ // Sync Command (dnxt sync)
287
+ if (command === 'sync') {
288
+ try {
289
+ await message.reply('🔄 Force syncing slash commands...');
290
+ await loadAndDeployCommands((client as any)._commandsDir, client.token!, (client as any)._clientId, (client as any)._guildId);
291
+ await message.reply('✅ Slash commands synchronized globally/locally.');
292
+ } catch (e: any) {
293
+ await message.reply(`❌ **Sync Error:** ${e.message}`);
294
+ }
295
+ return;
296
+ }
297
+
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>`);
302
+
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
+ }
317
+ return;
318
+ }
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);
331
+ return;
332
+ }
333
+
334
+ await message.reply(`❓ Unknown ${prefix} command. Available: \`js, sh, git, cat, curl, su, in, source, debug, reload, tasks, cancel, sync, sql, vc\``);
335
+ }
336
+
337
+ async function sendPaginatedText(message: Message, header: string, content: string, language: string = '') {
338
+ const maxLength = 1900;
339
+ if (content.length <= maxLength) {
340
+ await message.reply(`${header}\`\`\`${language}\n${content}\n\`\`\``);
341
+ return;
342
+ }
343
+
344
+ const chunks: string[] = [];
345
+ for (let i = 0; i < content.length; i += maxLength) {
346
+ chunks.push(content.substring(i, i + maxLength));
347
+ }
348
+
349
+ let index = 0;
350
+ const reply = await message.reply(`${header}\`\`\`${language}\n${chunks[index]}\n\`\`\`\n*Page 1 of ${chunks.length}*`);
351
+
352
+ await reply.react('◀️');
353
+ await reply.react('▶️');
354
+ await reply.react('⏹️');
355
+
356
+ const collector = reply.createReactionCollector({
357
+ filter: (reaction, user) => ['◀️', '▶️', '⏹️'].includes(reaction.emoji.name!) && user.id === message.author.id,
358
+ time: 120000
359
+ });
360
+
361
+ collector.on('collect', async (reaction, user) => {
362
+ await reaction.users.remove(user.id).catch(() => null);
363
+
364
+ if (reaction.emoji.name === '◀️') {
365
+ index = index > 0 ? index - 1 : index;
366
+ } else if (reaction.emoji.name === '▶️') {
367
+ index = index < chunks.length - 1 ? index + 1 : index;
368
+ } else if (reaction.emoji.name === '⏹️') {
369
+ collector.stop();
370
+ return;
371
+ }
372
+
373
+ await reply.edit(`${header}\`\`\`${language}\n${chunks[index]}\n\`\`\`\n*Page ${index + 1} of ${chunks.length}*`);
374
+ });
375
+
376
+ collector.on('end', () => {
377
+ reply.reactions.removeAll().catch(() => null);
378
+ });
379
+ }
@@ -0,0 +1,49 @@
1
+ export const cjsTemplates = {
2
+ index: `const { DJSNextClient, GatewayIntentBits } = require('djs-next');
3
+ require('dotenv/config');
4
+
5
+ const client = new DJSNextClient({
6
+ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
7
+ commandsDir: './src/commands',
8
+ eventsDir: './src/events',
9
+ componentsDir: './src/components',
10
+ tasksDir: './src/tasks',
11
+ clientId: process.env.CLIENT_ID
12
+ });
13
+
14
+ client.start(process.env.DISCORD_TOKEN);
15
+ `,
16
+
17
+ ping: `module.exports = {
18
+ command: {
19
+ description: 'Replies with Pong!',
20
+ execute: async (interaction) => {
21
+ await interaction.reply('Pong!');
22
+ }
23
+ }
24
+ };
25
+ `,
26
+
27
+ ready: `const { Events } = require('djs-next');
28
+
29
+ module.exports = {
30
+ event: {
31
+ name: Events.ClientReady,
32
+ once: true,
33
+ execute: (client) => {
34
+ console.log('Ready! Logged in as ' + client.user?.tag);
35
+ }
36
+ }
37
+ };
38
+ `,
39
+
40
+ healthcheck: `module.exports = {
41
+ task: {
42
+ interval: 60000,
43
+ execute: async (client) => {
44
+ console.log('[Task] Healthcheck completed.');
45
+ }
46
+ }
47
+ };
48
+ `
49
+ };
@@ -0,0 +1,43 @@
1
+ export const esmTemplates = {
2
+ index: `import { DJSNextClient, GatewayIntentBits } from 'djs-next';
3
+ import 'dotenv/config';
4
+
5
+ const client = new DJSNextClient({
6
+ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
7
+ commandsDir: './src/commands',
8
+ eventsDir: './src/events',
9
+ componentsDir: './src/components',
10
+ tasksDir: './src/tasks',
11
+ clientId: process.env.CLIENT_ID
12
+ });
13
+
14
+ client.start(process.env.DISCORD_TOKEN);
15
+ `,
16
+
17
+ ping: `export const command = {
18
+ description: 'Replies with Pong!',
19
+ execute: async (interaction) => {
20
+ await interaction.reply('Pong!');
21
+ }
22
+ };
23
+ `,
24
+
25
+ ready: `import { Events } from 'djs-next';
26
+
27
+ export const event = {
28
+ name: Events.ClientReady,
29
+ once: true,
30
+ execute: (client) => {
31
+ console.log('Ready! Logged in as ' + client.user?.tag);
32
+ }
33
+ };
34
+ `,
35
+
36
+ healthcheck: `export const task = {
37
+ interval: 60000,
38
+ execute: async (client) => {
39
+ console.log('[Task] Healthcheck completed.');
40
+ }
41
+ };
42
+ `
43
+ };
@@ -0,0 +1,47 @@
1
+ export const tsTemplates = {
2
+ index: `import { DJSNextClient, GatewayIntentBits } from 'djs-next';
3
+ import 'dotenv/config';
4
+
5
+ const client = new DJSNextClient({
6
+ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
7
+ commandsDir: './src/commands',
8
+ eventsDir: './src/events',
9
+ componentsDir: './src/components',
10
+ tasksDir: './src/tasks',
11
+ clientId: process.env.CLIENT_ID
12
+ });
13
+
14
+ client.start(process.env.DISCORD_TOKEN!);
15
+ `,
16
+
17
+ ping: `import { FileCommand } from 'djs-next';
18
+
19
+ export const command: FileCommand = {
20
+ description: 'Replies with Pong!',
21
+ execute: async (interaction) => {
22
+ await interaction.reply('Pong!');
23
+ }
24
+ };
25
+ `,
26
+
27
+ ready: `import { DJSNextEvent, Events } from 'djs-next';
28
+
29
+ export const event: DJSNextEvent<Events.ClientReady> = {
30
+ name: Events.ClientReady,
31
+ once: true,
32
+ execute: (client) => {
33
+ console.log('Ready! Logged in as ' + client.user?.tag);
34
+ }
35
+ };
36
+ `,
37
+
38
+ healthcheck: `import { FileTask } from 'djs-next';
39
+
40
+ export const task: FileTask = {
41
+ interval: 60000,
42
+ execute: async (client) => {
43
+ console.log('[Task] Healthcheck completed.');
44
+ }
45
+ };
46
+ `
47
+ };
@@ -0,0 +1,8 @@
1
+ import { DJSNextClient } from '../client.js';
2
+
3
+ describe('DJSNextClient', () => {
4
+ it('should instantiate without throwing', () => {
5
+ const client = new DJSNextClient({ intents: [] });
6
+ expect(client).toBeDefined();
7
+ });
8
+ });
package/src/types.ts ADDED
@@ -0,0 +1,69 @@
1
+ import {
2
+ ClientOptions,
3
+ ClientEvents,
4
+ ChatInputCommandInteraction,
5
+ Client,
6
+ ApplicationCommandOptionData,
7
+ PermissionResolvable,
8
+ AutocompleteInteraction,
9
+ MessageComponentInteraction,
10
+ ModalSubmitInteraction,
11
+ Interaction
12
+ } from 'discord.js';
13
+
14
+ export interface DJSNextConfig {
15
+ devGuildId?: string;
16
+ locales?: string[];
17
+ defaultLocale?: string;
18
+ directories?: {
19
+ commands?: string;
20
+ events?: string;
21
+ components?: string;
22
+ tasks?: string;
23
+ locales?: string;
24
+ };
25
+ }
26
+
27
+ export interface DJSNextClientOptions extends ClientOptions {
28
+ commandsDir?: string;
29
+ eventsDir?: string;
30
+ componentsDir?: string;
31
+ tasksDir?: string;
32
+ clientId?: string;
33
+ guildId?: string;
34
+ developers?: string[];
35
+ middleware?: (interaction: Interaction, client: Client) => Promise<boolean> | boolean;
36
+ config?: DJSNextConfig;
37
+ }
38
+
39
+ export interface FileTask<DB = any> {
40
+ filepath?: string;
41
+ interval: number;
42
+ execute: (client: Client & { db: DB; t: Function; config: DJSNextConfig }) => Promise<void> | void;
43
+ }
44
+
45
+ export interface FileCommand<DB = any> {
46
+ filepath?: string;
47
+ description: string;
48
+ options?: ApplicationCommandOptionData[];
49
+ cooldown?: number;
50
+ userPermissions?: PermissionResolvable[];
51
+ botPermissions?: PermissionResolvable[];
52
+ developerOnly?: boolean;
53
+ guildOnly?: boolean;
54
+ execute?: (interaction: ChatInputCommandInteraction, client: Client & { db: DB; t: Function; config: DJSNextConfig }) => Promise<void> | void;
55
+ autocomplete?: (interaction: AutocompleteInteraction, client: Client & { db: DB; t: Function; config: DJSNextConfig }) => Promise<void> | void;
56
+ }
57
+
58
+ export interface FileComponent<DB = any> {
59
+ filepath?: string;
60
+ customId?: string;
61
+ execute: (interaction: MessageComponentInteraction | ModalSubmitInteraction, client: Client & { db: DB; t: Function; config: DJSNextConfig }, params?: Record<string, string>) => Promise<void> | void;
62
+ }
63
+
64
+ export interface Event<K extends keyof ClientEvents = keyof ClientEvents, DB = any> {
65
+ filepath?: string;
66
+ name: K;
67
+ once?: boolean;
68
+ execute: (client: Client & { db: DB; t: Function; config: DJSNextConfig }, ...args: ClientEvents[K]) => Promise<void> | void;
69
+ }
@@ -0,0 +1,27 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import { pathToFileURL } from 'url';
4
+ import { DJSNextConfig } from '../types.js';
5
+
6
+ export async function loadConfig(): Promise<DJSNextConfig> {
7
+ const exts = ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'];
8
+ const cwd = process.cwd();
9
+
10
+ for (const ext of exts) {
11
+ const configPath = path.join(cwd, `djs-next.config${ext}`);
12
+ if (fs.existsSync(configPath)) {
13
+ try {
14
+ const configModule = await import(pathToFileURL(configPath).href);
15
+ return configModule.default || configModule;
16
+ } catch (err) {
17
+ console.error(`[djs-next] Error loading config file ${configPath}:`, err);
18
+ return {};
19
+ }
20
+ }
21
+ }
22
+ return {};
23
+ }
24
+
25
+ export function defineConfig(config: DJSNextConfig): DJSNextConfig {
26
+ return config;
27
+ }
@@ -0,0 +1,57 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ let localesCache: Record<string, Record<string, any>> = {};
5
+ let defaultLoc = 'en';
6
+
7
+ export function loadLocales(localesDir: string, defaultLocale?: string) {
8
+ if (defaultLocale) defaultLoc = defaultLocale;
9
+
10
+ if (!fs.existsSync(localesDir)) return;
11
+
12
+ const files = fs.readdirSync(localesDir);
13
+ for (const file of files) {
14
+ if (file.endsWith('.json')) {
15
+ const lang = file.replace('.json', '');
16
+ const content = fs.readFileSync(path.join(localesDir, file), 'utf8');
17
+ try {
18
+ localesCache[lang] = JSON.parse(content);
19
+ } catch (e) {
20
+ console.error(`[djs-next] Failed to parse locale file: ${file}`, e);
21
+ }
22
+ }
23
+ }
24
+ }
25
+
26
+ export function translate(key: string, locale: string = defaultLoc, variables?: Record<string, string | number>): string {
27
+ // Fallback to default locale if the requested one is not found
28
+ const dict = localesCache[locale] || localesCache[defaultLoc] || {};
29
+
30
+ const keys = key.split('.');
31
+ let value: any = dict;
32
+
33
+ for (const k of keys) {
34
+ if (value && typeof value === 'object') {
35
+ value = value[k];
36
+ } else {
37
+ value = undefined;
38
+ break;
39
+ }
40
+ }
41
+
42
+ if (typeof value !== 'string') {
43
+ return key; // return the key itself if string not found
44
+ }
45
+
46
+ if (variables) {
47
+ for (const [varKey, varValue] of Object.entries(variables)) {
48
+ value = value.replace(new RegExp(`{{s*${varKey}s*}}`, 'g'), String(varValue));
49
+ }
50
+ }
51
+
52
+ return value;
53
+ }
54
+
55
+ export function getLocalesCache() {
56
+ return localesCache;
57
+ }