Basic Command Handler

Basic Command Handler

A Command Handler is essentially a way to separate your commands into different files, instead of having a bunch of if/else conditions inside your code (or a switch/case if you’re being fancy).

In this case, the code shows you how to separate each command into its own file. This means that each command can be edited separately, and also reloaded without the need to restart your bot.

Main File Changes

Since we’re creating a separate file (module) for each event and each command, our main file (index.js) will change drastically from a list of commands to a simple file that loads other files.

Two main loops are needed to execute this function. First off, the one that will load all the events files. Each event will need to have a file in that folder, named exactly like the event itself. So for message we want ./events/message.js, for guildBanAdd we want ./events/guildBanAdd.js , etc.

const fs = require('fs');
// This loop reads the /events/ folder and attaches each event file to the appropriate event.
fs.readdir("./events/", (err, files) => {
  if (err) return console.error(err);
  files.forEach(file => {
    // If the file is not a JS file, ignore it (thanks, Apple)
    if (!file.endsWith(".js")) return;
    // Load the event file itself
    const event = require(`./events/${file}`);
    // Get just the event name from the file name
    let eventName = file.split(".")[0];
    // super-secret recipe to call events with all their proper arguments *after* the `client` var.
    // without going into too many details, this means each event will be called with the client argument,
    // followed by its "normal" arguments, like message, member, etc etc.
    // This line is awesome by the way. Just sayin'.
    client.on(eventName, event.bind(null, client));
    delete require.cache[require.resolve(`./events/${file}`)];
  });
});

The second loop is going to be for the commands themselves. For a couple of reasons, we want to put the commands inside of a structure that we can refer to later - we like to use Collection, which is exported with discord.js

client.commands = new Discord.Collection();

fs.readdir("./commands/", (err, files) => {
  if (err) return console.error(err);
  files.forEach(file => {
    if (!file.endsWith(".js")) return;
    // Load the command file itself
    let props = require(`./commands/${file}`);
    // Get just the command name from the file name
    let commandName = file.split(".")[0];
    console.log(`Attempting to load command ${commandName}`);
    // Here we simply store the whole thing in the Collection. We're not running it right now.
    client.commands.set(commandName, props);
  });
});

Ok so with that being said, our main file now looks like this (how clean is that, really?):

const Discord = require("discord.js");
const fs = require("fs");
const client = new Discord.Client();

fs.readdir("./events/", (err, files) => {
  if (err) return console.error(err);
  files.forEach(file => {
    const event = require(`./events/${file}`);
    let eventName = file.split(".")[0];
    client.on(eventName, event.bind(null, client));
  });
});

client.commands = new Discord.Collection();

fs.readdir("./commands/", (err, files) => {
  if (err) return console.error(err);
  files.forEach(file => {
    if (!file.endsWith(".js")) return;
    let props = require(`./commands/${file}`);
    let commandName = file.split(".")[0];
    console.log(`Attempting to load command ${commandName}`);
    client.commands.set(commandName, props);
  });
});

client.login('Client Token');

Our first Event: Message

The message event is obviously the most important one, as it will receive all messages sent to the bot. Create the ./events/message.js file (make sure it’s spelled exactly like that) and look at this bit of code:

module.exports = (client, message) => {
  // Ignore all bots
  if (message.author.bot) return;

  // Ignore messages not starting with the prefix (in config.json)
  if (message.content.indexOf(client.config.prefix) !== 0) return;

  // Our standard argument/command name definition.
  const args = message.content.slice(client.config.prefix.length).trim().split(/ +/g);
  const command = args.shift().toLowerCase();

  // Grab the command data from the client.commands - Collection
  const cmd = client.commands.get(command);

  // If that command doesn't exist, silently exit and do nothing
  if (!cmd) return;

  // Run the command
  cmd.run(client, message, args);
};

There are more things we could do here, like get per-guild settings or check permissions before running the command, etc. Out of a desire to keep this page simple, I’ve avoided all that extra code.

Example commands

This would be the content of the ./commands/ping.js file, which is called with !ping (assuming ! as a prefix)

exports.run = (client, message, args) => {
    message.channel.send("pong!").catch(console.error);
}

Another example would be the more complex ./commands/kick.js command, called using !kick @user

exports.run = (client, message, [mention, ...reason]) => {
  const modRole = message.guild.roles.find(role => role.name === "Mods");
  if (!modRole)
    return console.log("The Mods role does not exist");

  if (!message.member.roles.has(modRole.id))
    return message.reply("You can't use this command.");

  if (message.mentions.members.size === 0)
    return message.reply("Please mention a user to kick");

  if (!message.guild.me.hasPermission("KICK_MEMBERS"))
    return message.reply("");

  const kickMember = message.mentions.members.first();

  kickMember.kick(reason.join(" ")).then(member => {
    message.reply(`${member.user.username} was succesfully kicked.`);
  });
};

Notice the structure on the first line. exports.run is the “function name” that is exported, with 3 arguments: client (the client), message (the message variable from the handler) and args. Here, args is replaced by fancy destructuring that captures the reason (the rest of the message after the mention) in an array.

Other Events

Events are handled almost exactly in the same way, except that the number of arguments depends on which event it is. For example, the ready event:

module.exports = (client) => {
  console.log(`Ready to serve in ${client.channels.size} channels on ${client.guilds.size} servers, for a total of ${client.users.size} users.`);
}

Note that the ready event normally doesn’t have any arguments, it’s just (). But because we’re in separate modules, it’s necessary to “pass” the client variable to it or it would not be accessible. That’s what our fancy bind is for in the main file!

Here’s another example with the guildMemberAdd event:

module.exports = (client, member) => {
  const defaultChannel = member.guild.channels.find(channel => channel.permissionsFor(guild.me).has("SEND_MESSAGES"));
  defaultChannel.send(`Welcome ${member.user} to this server.`).catch(console.error);
}

Now we have client and also member which is the argument provided by the guildMemberAdd event.

Happy Glitching!