How to create a custom command handler for your Discord.JS bot.

Subscribe to my newsletter and never miss my upcoming articles

Please note that if you are a skilled programmer I recommend you use classes instead of plain scripts for commands

All of the code mentioned here is at this GitHub repo:

To follow this tutorial you must have a basic understanding of:

  • NodeJS v12+
  • Discord.JS 12+
  • Javascript

Creating the bot

If you haven't done so already, you will need to create a bot. If you don't know how, you can do this by:

  1. Going to the Discord developer dashboard and clicking "New Application"
  2. Giving it a name
  3. Creating a bot application and saving the token for later image.png
  4. Invite it to your server (paste the link into your browser) image.png

Getting the bot online

Before we start creating the command handler we need to write some code to build on top of.

Before that though, we need to get our project ready. Create the following file tree:

  • config.json
  • src/
    • bot.js
    • commands/
      • ping.js

Now run npm init or yarn init depending on which one you like to use. (Make sure you set your main file to src/bot.js!). Then run npm install discord.js or yarn add discord.js.

Confused? See all of the code at the GitHub repository i made for this post.

Now we need to write some code. Open up src/bot.js and write the following

const Discord = require('discord.js');

let bot = new Discord.Client();

This creates the bot.

Before we continue you will need to populate config.json.

{
  "token": "YOUR TOKEN HERE",
  "prefix": "!"
}

Make sure to replace YOUR TOKEN HERE with the token you grabbed from Discord earlier.

Go back to src/bot.js and add const config = require('../config.json') to the start of the file. Then at the end add

bot.login(config.token);

This is the most basic bot. Now we will move on to creating the command handler

Creating a command register

The best way to do this is to map all of your commands into a Map and then if a message starts with the prefix and there is a command in that map that has the same name as the command matched, run it.

It is good practice to separate this into multiple files.

First we need to map all of the commands on startup.

Add the following code to src/bot.js

const fs = require('fs');
const path = require('path');

let commands = new Map();

fs.readdirSync(path.join(__dirname, 'commands')).forEach((f) => {
    if(f.endsWith(".js")) {
        let file = require(path.join(__dirname, 'commands', f));
        commands.set(file.info.name, file);
        console.log(`Registered command: ${file.info.name}`);
    }
});

What this does is loop through all the files in src/commands and if they have the file extension .js then add that command to the map. You will understand why this works later.

Writing a command

Earlier you should have created the file src/commands/ping.js. Open it.

In this file, write

module.exports = {
    info: {
        name: "ping"
    },
    runner(msg, bot) {
        msg.channel.send("Pinging").then(sent => {
            sent.edit(sent.createdTimestamp - msg.createdTimestamp + "ms");
        });
    }
}

What this does is tell the command register that it's name is ping and when a user runs it, figure out the ping.

At this point if you start the bot (via node . on the command line) it will log to the console that it has registered a command called ping but you will not be able to run it on Discord.

Running commands

Maps make this very simple.

Go back to src/bot.js and write this code:

bot.on('message', (msg) => {
    if(!msg.author.bot && msg.content.startsWith(config.prefix)) { // checks if the sender isnt a bot and the message starts with the command prefix
        let command = msg.content.split(" ")[0].replace(config.prefix, "") //isolates the command name from the rest of the message. Does this by only getting the first word and removing the prefix.
        if(commands.has(command)) {
            let cmd = commands.get(command)
            if(typeof cmd.runner === "function") {
                cmd.runner(msg, bot);
            }
        }
    }
});

I have added comments to explain the more complicated lines. After those the bot just checks if that command exists and it has a runner then runs it if that is true.

Testing the bot

If you followed the tutorial correctly the bot should start and you should see the following in the console; image.png

Now if you go to the server you invited the bot to and run !ping this should happen: image.png

If it doesnt, make sure your code matches the github repository (minus the .idea directory my IDE made that automatically)

Thank you for reading!

Image by Morgan Richardson on Unsplash

No Comments Yet