Command handling
Unless your bot project is a small one, it's not a very good idea to have a single file with a giant if/else if chain for commands. If you want to implement features into your bot and make your development process a lot less painful, you'll want to implement a command handler. Let's get started on that!
Here's the base code we'll be using:
const Discord = require('discord.js');
const { prefix, token } = require('./config.json');
const client = new Discord.Client();
client.once('ready', () => {
console.log('Ready!');
});
client.on('message', message => {
if (!message.content.startsWith(prefix) || message.author.bot) return;
const args = message.content.slice(prefix.length).trim().split(/ +/);
const command = args.shift().toLowerCase();
if (command === 'ping') {
message.channel.send('Pong.');
} else if (command === 'beep') {
message.channel.send('Boop.');
}
// ...
});
client.login(token);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
TIP
We'll be moving over the commands created in the previous page as well, but for the sake of keeping the base code short, the code block above omits those commands.
Individual command files
Before anything, you may want to create a backup of your current bot file. If you've followed along so far, your entire folder structure should look something like this:
In the same folder, create a new folder and name it commands
. This is where you'll store all of your commands, of course. Head over to your commands
folder, create a new file named ping.js
, and copy & paste in the following code:
module.exports = {
name: 'ping',
description: 'Ping!',
execute(message, args) {
message.channel.send('Pong.');
},
};
2
3
4
5
6
7
You can go ahead and do the same for the rest of your commands and put their respective blocks of code inside the execute()
function. If you've been using the same code as the guide thus far, you can copy & paste your commands into their own files now, following the format above. The description
property is optional but will be useful for the dynamic help command we'll be covering later.
TIP
module.exports
is how you export data in Node.js so that you can require()
it in other files. If you're unfamiliar with it and want to read more, you can look at the documentationopen in new window for more info.
TIP
If you need to access your client instance from inside one of your command files, you can access it via message.client
. If you need to access external files, modules, etc., you should re-require them at the top of the file.
Reading command files
Back in your main file, make these two additions:
const fs = require('fs');
const Discord = require('discord.js');
const { prefix, token } = require('./config.json');
const client = new Discord.Client();
client.commands = new Discord.Collection();
2
3
4
5
6
TIP
fs
is Node's native file system module. You can read the docs about it hereopen in new window.
TIP
If you aren't exactly sure what Collections are, they're a class that extend JavaScript's native Map class and include more extensive, useful functionality. You can read about Maps hereopen in new window, and see all the available Collection methods hereopen in new window.
This next step is how you'll dynamically retrieve all your newly created command files. The fs.readdirSync()
open in new window method will return an array of all the file names in a directory, e.g. ['ping.js', 'beep.js']
. To ensure only command files get returned, use Array.filter()
to leave out any non-JavaScript files from the array. With that array, you can loop over it and dynamically set your commands to the Collection you made above.
client.commands = new Discord.Collection();
const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const command = require(`./commands/${file}`);
// set a new item in the Collection
// with the key as the command name and the value as the exported module
client.commands.set(command.name, command);
}
2
3
4
5
6
7
8
9
10
Dynamically executing commands
With your client.commands
Collection setup, you can use it to retrieve and execute your commands! Inside your message
event, delete your if
/else if
chain of commands and replace it with this:
client.on('message', message => {
if (!message.content.startsWith(prefix) || message.author.bot) return;
const args = message.content.slice(prefix.length).trim().split(/ +/);
const command = args.shift().toLowerCase();
if (!client.commands.has(command)) return;
try {
client.commands.get(command).execute(message, args);
} catch (error) {
console.error(error);
message.reply('there was an error trying to execute that command!');
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
If there isn't a command with that name, you don't need to do anything further, so exit early with return
. If there is, .get()
the command, call its .execute()
method, and pass in your message
and args
variables as its arguments. In case something goes wrong, log the error and report back to the member to let them know.
And that's it! Whenever you want to add a new command, you make a new file in your commands
directory, name it what you want, and then do what you did for the other commands.
In the next chapter, we'll be going through how to implement some basic features into your brand new command handler. Currently, it's hardly a command "handler" at this point; it's a command loader and executor if you wish to see it that way. You'll learn how to implement some new features and the logic behind them, such as:
- Command aliases
- Cooldowns
- Guild only commands
- A dynamic help message
Resulting code
If you want to compare your code to the code we've constructed so far, you can review it over on the GitHub repository here open in new window.