djs-next 0.0.1 ā 1.0.0-dev.2
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 +29 -51
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +344 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +321 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +4 -46
- package/dist/index.d.ts +4 -46
- package/dist/index.js +290 -578
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +280 -578
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +203 -0
- package/src/client.ts +106 -213
- package/src/index.ts +1 -4
- package/src/plugins/dnxt.ts +176 -215
- package/src/templates/cjs.ts +6 -11
- package/src/templates/esm.ts +4 -11
- package/src/templates/ts.ts +5 -11
- package/src/types.ts +1 -21
- package/src/utils/paginate.ts +13 -32
- package/tsup.config.ts +1 -1
- package/bin/create-djs-next.js +0 -78
- package/src/utils/PaginationBuilder.ts +0 -94
- package/src/utils/prompts.ts +0 -76
- package/test_payload.js +0 -3
- package/test_reply.js +0 -4
package/src/cli.ts
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { input, select, password } from '@inquirer/prompts';
|
|
5
|
+
import { spawnSync } from 'child_process';
|
|
6
|
+
import { tsTemplates } from './templates/ts.js';
|
|
7
|
+
import { esmTemplates } from './templates/esm.js';
|
|
8
|
+
import { cjsTemplates } from './templates/cjs.js';
|
|
9
|
+
import pc from 'picocolors';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
|
|
12
|
+
async function main() {
|
|
13
|
+
console.log(pc.bold(pc.blue('\nš Welcome to djs-next!\n')));
|
|
14
|
+
|
|
15
|
+
const userAgent = process.env.npm_config_user_agent || '';
|
|
16
|
+
let pm = 'npm';
|
|
17
|
+
if (userAgent.startsWith('yarn')) pm = 'yarn';
|
|
18
|
+
else if (userAgent.startsWith('pnpm')) pm = 'pnpm';
|
|
19
|
+
else if (userAgent.startsWith('bun')) pm = 'bun';
|
|
20
|
+
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
let projectName = args[0] || '';
|
|
23
|
+
|
|
24
|
+
if (!projectName || projectName === 'init') {
|
|
25
|
+
projectName = await input({
|
|
26
|
+
message: 'What is your project named?',
|
|
27
|
+
default: 'my-bot',
|
|
28
|
+
validate: (value) => value.trim().length > 0 || 'Project name is required'
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const language = await select({
|
|
33
|
+
message: 'Would you like to use TypeScript or JavaScript?',
|
|
34
|
+
choices: [
|
|
35
|
+
{ name: 'TypeScript', value: 'ts' },
|
|
36
|
+
{ name: 'JavaScript', value: 'js' }
|
|
37
|
+
],
|
|
38
|
+
default: 'ts'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const moduleSystem = await select({
|
|
42
|
+
message: 'Would you like to use ES Modules or CommonJS?',
|
|
43
|
+
choices: [
|
|
44
|
+
{ name: 'ES Modules (import/export)', value: 'esm' },
|
|
45
|
+
{ name: 'CommonJS (require/module.exports)', value: 'cjs' }
|
|
46
|
+
],
|
|
47
|
+
default: 'esm'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const botToken = await password({
|
|
51
|
+
message: 'What is your Discord Bot Token? (Leave empty to skip)',
|
|
52
|
+
mask: true
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const clientId = await input({
|
|
56
|
+
message: 'What is your Discord Client ID? (Leave empty to skip)'
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const targetPath = path.resolve(process.cwd(), projectName);
|
|
60
|
+
|
|
61
|
+
if (fs.existsSync(targetPath)) {
|
|
62
|
+
console.error(pc.red(`\nDirectory "${projectName}" already exists.`));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(pc.cyan(`\nScaffolding project in ${targetPath}...`));
|
|
67
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
68
|
+
|
|
69
|
+
const isTS = language === 'ts';
|
|
70
|
+
const isESM = moduleSystem === 'esm';
|
|
71
|
+
const ext = isTS ? 'ts' : 'js';
|
|
72
|
+
|
|
73
|
+
let devScript = isTS ? "tsx watch src/index.ts" : "node --watch src/index.js";
|
|
74
|
+
let startScript = isTS ? "node dist/index.js" : "node src/index.js";
|
|
75
|
+
|
|
76
|
+
if (pm === 'bun') {
|
|
77
|
+
devScript = `bun run --watch src/index.${ext}`;
|
|
78
|
+
startScript = `bun src/index.${ext}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const packageJson: any = {
|
|
82
|
+
name: projectName.toLowerCase().replace(/\\s+/g, '-'),
|
|
83
|
+
version: "1.0.0",
|
|
84
|
+
main: isTS ? "dist/index.js" : "src/index.js",
|
|
85
|
+
type: isESM ? "module" : "commonjs",
|
|
86
|
+
scripts: {
|
|
87
|
+
"dev": devScript,
|
|
88
|
+
"start": startScript
|
|
89
|
+
},
|
|
90
|
+
dependencies: {
|
|
91
|
+
"discord.js": "latest",
|
|
92
|
+
"djs-next": "latest",
|
|
93
|
+
"dotenv": "latest"
|
|
94
|
+
},
|
|
95
|
+
devDependencies: {}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (isTS) {
|
|
99
|
+
packageJson.scripts.build = "tsc";
|
|
100
|
+
packageJson.devDependencies = {
|
|
101
|
+
"typescript": "latest",
|
|
102
|
+
"@types/node": "latest",
|
|
103
|
+
"tsx": "latest"
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fs.writeFileSync(path.join(targetPath, 'package.json'), JSON.stringify(packageJson, null, 2));
|
|
108
|
+
|
|
109
|
+
if (isTS) {
|
|
110
|
+
const tsconfig = {
|
|
111
|
+
compilerOptions: {
|
|
112
|
+
target: "ES2022",
|
|
113
|
+
module: isESM ? "NodeNext" : "CommonJS",
|
|
114
|
+
moduleResolution: isESM ? "NodeNext" : "Node",
|
|
115
|
+
strict: true,
|
|
116
|
+
esModuleInterop: true,
|
|
117
|
+
outDir: "./dist",
|
|
118
|
+
types: ["node"],
|
|
119
|
+
ignoreDeprecations: "5.0"
|
|
120
|
+
},
|
|
121
|
+
include: ["src/**/*"]
|
|
122
|
+
};
|
|
123
|
+
fs.writeFileSync(path.join(targetPath, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fs.writeFileSync(path.join(targetPath, '.env'), `DISCORD_TOKEN=${botToken || 'your_token_here'}
|
|
127
|
+
CLIENT_ID=${clientId || 'your_client_id_here'}
|
|
128
|
+
`);
|
|
129
|
+
fs.writeFileSync(path.join(targetPath, '.gitignore'), `node_modules/
|
|
130
|
+
dist/
|
|
131
|
+
.env
|
|
132
|
+
.DS_Store
|
|
133
|
+
`);
|
|
134
|
+
|
|
135
|
+
const srcDir = path.join(targetPath, 'src');
|
|
136
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
137
|
+
fs.mkdirSync(path.join(srcDir, 'commands'), { recursive: true });
|
|
138
|
+
fs.mkdirSync(path.join(srcDir, 'events'), { recursive: true });
|
|
139
|
+
fs.mkdirSync(path.join(srcDir, 'components', 'buttons'), { recursive: true });
|
|
140
|
+
fs.mkdirSync(path.join(srcDir, 'tasks'), { recursive: true });
|
|
141
|
+
|
|
142
|
+
// Index File
|
|
143
|
+
if (isTS) {
|
|
144
|
+
fs.writeFileSync(path.join(srcDir, `index.${ext}`), tsTemplates.index);
|
|
145
|
+
} else if (isESM) {
|
|
146
|
+
fs.writeFileSync(path.join(srcDir, `index.${ext}`), esmTemplates.index);
|
|
147
|
+
} else {
|
|
148
|
+
fs.writeFileSync(path.join(srcDir, 'index.js'), cjsTemplates.index);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Ping Command
|
|
152
|
+
if (isTS) {
|
|
153
|
+
fs.writeFileSync(path.join(srcDir, 'commands', 'ping.ts'), tsTemplates.ping);
|
|
154
|
+
} else if (isESM) {
|
|
155
|
+
fs.writeFileSync(path.join(srcDir, 'commands', 'ping.js'), esmTemplates.ping);
|
|
156
|
+
} else {
|
|
157
|
+
fs.writeFileSync(path.join(srcDir, 'commands', 'ping.js'), cjsTemplates.ping);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Ready Event
|
|
161
|
+
if (isTS) {
|
|
162
|
+
fs.writeFileSync(path.join(srcDir, 'events', 'ready.ts'), tsTemplates.ready);
|
|
163
|
+
} else if (isESM) {
|
|
164
|
+
fs.writeFileSync(path.join(srcDir, 'events', 'ready.js'), esmTemplates.ready);
|
|
165
|
+
} else {
|
|
166
|
+
fs.writeFileSync(path.join(srcDir, 'events', 'ready.js'), cjsTemplates.ready);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Healthcheck Task
|
|
170
|
+
if (isTS) {
|
|
171
|
+
fs.writeFileSync(path.join(srcDir, 'tasks', 'healthcheck.ts'), tsTemplates.healthcheck);
|
|
172
|
+
} else if (isESM) {
|
|
173
|
+
fs.writeFileSync(path.join(srcDir, 'tasks', 'healthcheck.js'), esmTemplates.healthcheck);
|
|
174
|
+
} else {
|
|
175
|
+
fs.writeFileSync(path.join(srcDir, 'tasks', 'healthcheck.js'), cjsTemplates.healthcheck);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log(pc.cyan(`\nInstalling dependencies with ${pm}...`));
|
|
179
|
+
try {
|
|
180
|
+
execSync(`${pm} install`, { cwd: targetPath, stdio: 'inherit' });
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error(pc.red(`\nFailed to install dependencies. Please run '${pm} install' manually.`));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
console.log(pc.green(pc.bold(`\nSuccess! Created ${projectName} at ${targetPath}`)));
|
|
186
|
+
console.log(`\nInside that directory, you can run several commands:`);
|
|
187
|
+
console.log(pc.cyan(`\n ${pm} run dev`));
|
|
188
|
+
console.log(` Starts the development server with hot-reloading.`);
|
|
189
|
+
if (isTS) {
|
|
190
|
+
console.log(pc.cyan(`\n ${pm} run build`));
|
|
191
|
+
console.log(` Compiles the TypeScript app into JavaScript.`);
|
|
192
|
+
}
|
|
193
|
+
console.log(pc.cyan(`\n ${pm} start`));
|
|
194
|
+
console.log(` Starts the production server.`);
|
|
195
|
+
console.log(`\nWe suggest that you begin by typing:\n`);
|
|
196
|
+
console.log(pc.cyan(` cd ${projectName}`));
|
|
197
|
+
console.log(pc.cyan(` ${pm} run dev\n`));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
main().catch((err) => {
|
|
201
|
+
console.error(pc.red('An unexpected error occurred:'), err);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
});
|
package/src/client.ts
CHANGED
|
@@ -30,10 +30,7 @@ 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
|
|
34
|
-
private _enableTextCommands: boolean;
|
|
35
|
-
private _enableMentionPrefix: boolean | string[];
|
|
36
|
-
private _enableNoPrefix: boolean | string[];
|
|
33
|
+
private _enableMentionPrefix: boolean;
|
|
37
34
|
|
|
38
35
|
constructor(options: DJSNextClientOptions) {
|
|
39
36
|
super(options);
|
|
@@ -52,13 +49,7 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
52
49
|
|
|
53
50
|
const prefs = options.prefixes || [];
|
|
54
51
|
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;
|
|
59
52
|
this._enableMentionPrefix = options.enableMentionPrefix ?? true;
|
|
60
|
-
this._enableNoPrefix = options.enableNoPrefix ?? false;
|
|
61
|
-
if (options.db) this.db = options.db as any;
|
|
62
53
|
|
|
63
54
|
this.attachCoreListeners();
|
|
64
55
|
}
|
|
@@ -78,8 +69,6 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
78
69
|
|
|
79
70
|
// 2. Chat Input Commands Execution
|
|
80
71
|
if (interaction.isChatInputCommand()) {
|
|
81
|
-
if (!this._enableSlashCommands) return;
|
|
82
|
-
|
|
83
72
|
let commandKey = interaction.commandName;
|
|
84
73
|
const group = interaction.options.getSubcommandGroup(false);
|
|
85
74
|
const sub = interaction.options.getSubcommand(false);
|
|
@@ -90,12 +79,56 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
90
79
|
const command = this.commands.get(commandKey);
|
|
91
80
|
if (!command || !command.execute) return;
|
|
92
81
|
|
|
93
|
-
|
|
82
|
+
// --- Permissions & Validation Checks ---
|
|
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
|
+
}
|
|
94
124
|
|
|
95
125
|
try {
|
|
96
126
|
await command.execute(interaction, this);
|
|
97
127
|
} catch (error) {
|
|
98
|
-
|
|
128
|
+
console.error(`[djs-next] Error executing command: "${commandKey}"`, error);
|
|
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);
|
|
99
132
|
}
|
|
100
133
|
}
|
|
101
134
|
|
|
@@ -142,12 +175,13 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
142
175
|
}
|
|
143
176
|
}
|
|
144
177
|
if (component) {
|
|
145
|
-
if (!(await this.handlePreconditions(component, interaction, interaction.customId))) return;
|
|
146
|
-
|
|
147
178
|
try {
|
|
148
179
|
await component.execute(interaction, this, params);
|
|
149
180
|
} catch (error) {
|
|
150
|
-
|
|
181
|
+
console.error(`[djs-next] Error executing component: "${interaction.customId}"`, error);
|
|
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);
|
|
151
185
|
}
|
|
152
186
|
}
|
|
153
187
|
}
|
|
@@ -155,7 +189,6 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
155
189
|
|
|
156
190
|
this.on('messageCreate', async (message: Message) => {
|
|
157
191
|
if (message.author.bot) return;
|
|
158
|
-
if (!this._enableTextCommands) return;
|
|
159
192
|
|
|
160
193
|
// Global Middleware execution for Messages
|
|
161
194
|
if (this._middleware) {
|
|
@@ -174,29 +207,26 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
174
207
|
|
|
175
208
|
// 1. Check Mention Prefix
|
|
176
209
|
const mentionRegex = new RegExp(`^<@!?${this.user?.id}>\\s*`);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (canUseMention && mentionRegex.test(content)) {
|
|
210
|
+
if (this._enableMentionPrefix && mentionRegex.test(content)) {
|
|
180
211
|
matchedPrefix = content.match(mentionRegex)![0];
|
|
181
212
|
isCommand = true;
|
|
182
213
|
} else {
|
|
183
|
-
// 2. Check Standard Prefixes
|
|
214
|
+
// 2. Check Standard/No Prefixes
|
|
215
|
+
// Sort by length descending so longer prefixes match first
|
|
184
216
|
const sortedPrefs = [...this._prefixes].sort((a, b) => b.length - a.length);
|
|
185
217
|
|
|
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
|
+
|
|
186
223
|
for (const p of sortedPrefs) {
|
|
187
|
-
if (content.startsWith(p)) {
|
|
224
|
+
if (p !== '' && content.startsWith(p)) {
|
|
188
225
|
matchedPrefix = p;
|
|
189
226
|
isCommand = true;
|
|
190
227
|
break;
|
|
191
228
|
}
|
|
192
229
|
}
|
|
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
|
-
}
|
|
200
230
|
}
|
|
201
231
|
|
|
202
232
|
if (!isCommand) return;
|
|
@@ -214,13 +244,42 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
214
244
|
|
|
215
245
|
if (!command || !command.executeText) return;
|
|
216
246
|
|
|
217
|
-
|
|
247
|
+
// Validation Checks (Developers/GuildOnly)
|
|
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
|
+
}
|
|
218
276
|
|
|
219
277
|
// Execute Text Command
|
|
220
278
|
try {
|
|
221
279
|
await command.executeText(message, args, this);
|
|
222
280
|
} catch (error) {
|
|
223
|
-
|
|
281
|
+
console.error(`[djs-next] Error executing text command: "${commandName}"`, error);
|
|
282
|
+
await message.reply('We ran into an internal error executing this command.').catch(() => null);
|
|
224
283
|
}
|
|
225
284
|
});
|
|
226
285
|
}
|
|
@@ -230,17 +289,15 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
230
289
|
|
|
231
290
|
this.config = await loadConfig();
|
|
232
291
|
|
|
233
|
-
// Fallback options to config
|
|
292
|
+
// Fallback options to config
|
|
234
293
|
this._guildId = this._guildId || this.config.devGuildId;
|
|
235
|
-
|
|
236
|
-
if (!this.
|
|
237
|
-
if (!this.
|
|
238
|
-
if (!this.
|
|
239
|
-
if (!this.
|
|
240
|
-
if (!this._localesDir) this._localesDir = path.resolve(process.cwd(), this.config.directories?.locales || 'src/locales');
|
|
294
|
+
if (!this._commandsDir && this.config.directories?.commands) this._commandsDir = path.resolve(process.cwd(), this.config.directories.commands);
|
|
295
|
+
if (!this._eventsDir && this.config.directories?.events) this._eventsDir = path.resolve(process.cwd(), this.config.directories.events);
|
|
296
|
+
if (!this._componentsDir && this.config.directories?.components) this._componentsDir = path.resolve(process.cwd(), this.config.directories.components);
|
|
297
|
+
if (!this._tasksDir && this.config.directories?.tasks) this._tasksDir = path.resolve(process.cwd(), this.config.directories.tasks);
|
|
298
|
+
if (!this._localesDir && this.config.directories?.locales) this._localesDir = path.resolve(process.cwd(), this.config.directories.locales);
|
|
241
299
|
|
|
242
|
-
|
|
243
|
-
if (fs.existsSync(this._localesDir)) loadLocales(this._localesDir, this.config.defaultLocale);
|
|
300
|
+
if (this._localesDir) loadLocales(this._localesDir, this.config.defaultLocale);
|
|
244
301
|
|
|
245
302
|
// Load middleware from root
|
|
246
303
|
if (!this._middleware) {
|
|
@@ -263,18 +320,17 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
263
320
|
}
|
|
264
321
|
}
|
|
265
322
|
|
|
266
|
-
if (
|
|
267
|
-
if (
|
|
268
|
-
if (
|
|
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);
|
|
269
326
|
|
|
270
|
-
|
|
271
|
-
|
|
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;
|
|
327
|
+
if (this._commandsDir) {
|
|
328
|
+
if (!this._clientId) throw new Error("[djs-next] You must provide a clientId to deploy commands.");
|
|
276
329
|
this.commands = await loadAndDeployCommands(this._commandsDir, token, this._clientId, this._guildId);
|
|
277
330
|
}
|
|
331
|
+
|
|
332
|
+
await this.login(token);
|
|
333
|
+
console.log(`[djs-next] Bot is ready and logged in as ${this.user?.tag}!`);
|
|
278
334
|
}
|
|
279
335
|
|
|
280
336
|
public enableDevTools(prefix: 'dnxt' | 'nxt' = 'dnxt'): void {
|
|
@@ -326,167 +382,4 @@ export class DJSNextClient<DB = any> extends Client {
|
|
|
326
382
|
console.warn(`[djs-next] chokidar not installed. HMR is disabled. Please run "npm install chokidar" to use this feature.`);
|
|
327
383
|
}
|
|
328
384
|
}
|
|
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
|
-
}
|
|
492
385
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
2
1
|
export * from './client.js';
|
|
3
|
-
export { DJSNextClientOptions, FileCommand, FileComponent, FileTask, Event as DJSNextEvent, DJSNextConfig
|
|
2
|
+
export { DJSNextClientOptions, FileCommand, FileComponent, FileTask, Event as DJSNextEvent, DJSNextConfig } from './types.js';
|
|
4
3
|
export * from './utils/paginate.js';
|
|
5
|
-
export * from './utils/prompts.js';
|
|
6
4
|
export * from './utils/configLoader.js';
|
|
7
5
|
export * from './utils/i18n.js';
|
|
8
|
-
export * from './utils/PaginationBuilder.js';
|
|
9
6
|
// Re-export everything from discord.js so users don't need to install it explicitly
|
|
10
7
|
export * from 'discord.js';
|