Learn to Code via Tutorials on Repl.it!

← Back to all posts
Discord.JS Tutorial + Template (Handlers)
h
TereDeJugo (6)

Discord.JS Bot Tutorial + Template

Info

This is an advanced tutorial, I recommend that you already have experience with Javascript and Discord.js, I will not get too confused when explaining things, I will only explain the code

Step by Step

Once you have created your bot on the Discord developer portal, we will start with a new Node.js project

Start

We will install the necessary packages i.e Discord.Js, and you can do it with the command npm i discord.js in your shell, or you can use the package table of replit

Now, we are going to import the package, we only need the Client and Collection classes

const { Client, Collection } = require("discord.js");

We will create our variable with the new client, and we will also add a property with a collection for our commands

const client = new Client();
client.commands = new Collection();

Ready, we already have our client and its collection of commands, now we will start creating the command handler

Command handler

The logic of a command handler is to go through the files that we tell it, and to require the modules that we will later define in our command, and then save it in our collection. We will start by requiring the readdirSync function, from the fs module, which comes in Node by default.

const { Client, Collection } = require("discord.js");
const { readdirSync } = require("fs");

We will create a command folder, and we will start using the readdirSync function in it, to read the directories

for(dir of readdirSync("./commands")) {
    
};

What this will do is that every time the for is executed, the value of dir will be the folder that it is browsing, and so until it goes through the entire directory, I will create subfolders for the command categories, so we will rescan each directory that is in the command folder

for(const dir of readdirSync("./commands")) {
    for(file of readdirSync("./commands/" + dir)) {

    };
};

Now, we will check if the file ends in .js, and then we will make a require on it, and use its "name" property as the key, for our command collection.

for(const dir of readdirSync("./commands")) {
    for(const file of readdirSync("./commands/" + dir)) {
        if(file.endsWith(".js")) {
            const content = require(`./commands/${dir}/${file}`);
            const key = content.name;

            client.commands.set(key, content);
        };
    };
};

Alias

If you want to have aliases, you can create a new collection, specify aliases, so what? So that the commands do not interfere with aliases, since we will basically create a copy of our command, but with another key, so it is better to have it in another collection

client.commands = new Collection();
client.aliases = new Collection();

Now we will simply verify if the alias property exists in the content of our command, the one that we define in the command handler, and if that property exists, we will go through the array that it contains, with the aliases, and we will save them as the key to our collection of aliases.

for(const dir of readdirSync("./commands")) {
    for(const file of readdirSync("./commands/" + dir)) {
        if(file.endsWith(".js")) {
            const content = require(`./commands/${dir}/${file}`);
            const key = content.name;

            client.commands.set(key, content);

            if(content.alias) {
                for(const key of content.alias) {
                    client.aliases.set(key, content);
                };
            };
        };
    };
};

Event Handler

Ready, we already have our command handler, now let's go to the events, for that, we will create an events folder, and we will do the same, we will make a for and if the file ends in .js, we will require the content

for(const file of readdirSync("./events")) {
    if(file.endsWith(".js")) {
        const content = require(`./events/${file}`)
    }
}

Now, for the key, instead of taking a property, we are going to use the name of the file, and we will remove the last 3 characters, so that it is only the name, this will mean that the name of the file must be exactly the name of the event.

for(const file of readdirSync("./events")) {
    if(file.endsWith(".js")) {
        const content = require(`./events/${file}`)
        const key = file.substring(0, file.length - 3);
    }
}

Now we will call the event using the bind function, which what it will do is create a new function with the content of our event file

for(const file of readdirSync("./events")) {
    if(file.endsWith(".js")) {
        const content = require(`./events/${file}`)
        const key = file.substring(0, file.length - 3);

        client.on(key, content.bind(null, client));
    }
}

Ready, we already have both the command handler and the event handler, now, we will create a file for the readdy event, obviously, it will be called ready.js (remember that it must be the name of the event)

First event

Now, we define the module.exports with an arrow function, which will receive the client as a parameter, and will show a message by console when the bot connects.

module.exports = async (client) => {
    console.log("The bot is online");
};

Now, before starting our bot, of course, we need to log in with our token, so we will put it at the end of our index.js.

client.login("Your token");

Message event

Great!, but now we must process the commands, for that, first of all we will create an event, the file must be called message.js, and we start the arrow function with the parameter client and message

module.exports = (client, message) => {
    
};

The first thing I want to verify is that the message begins with the prefix of my bot, and that the user is not a bot, if not, it would be chaos. So we will create a variable with our prefix, and we will check if the text of the message begins with that prefix, and also, if the author of the message does not have the bot property

module.exports = (client, message) => {
    const prefix = "!";

    if(message.content.startsWith(prefix) && !message.author.bot) {
        
    }
};

Now we will create two variables, first, an args call, which will remove the prefix of the original message content (the received one) and then separate the content of the remaining message, cut the string every time it finds one or more empty spaces, that way, we can operate with commands with options.

if(message.content.startsWith(prefix) && !message.author.bot) {
    const args = message.content.slice(prefix.length).split(/ +/);
};

Now our second variable will be to remove the first element from the args array, which would be the message, and put it in lowercase using toLowerCase, that way, both the keys of our command collection and those that the user puts , they will coincide, even if you write them in capital letters (in the Discord message)

if(message.content.startsWith(prefix) && !message.author.bot) {
    const args = message.content.slice(prefix.length).split(/ +/);
    const command = args.shift().toLowerCase();
};

Ready, we have that, now it will only be necessary to look for the command in our two collections of commands and aliases, and if it exists, call its function run, and pass it as a parameter the client, the message, and the args

if(message.content.startsWith(prefix) && !message.author.bot) {
    const args = message.content.slice(prefix.length).split(/ +/);
    const command = args.shift().toLowerCase();

    const cmd = client.commands.get(command) || client.aliases.get(command);

    if(cmd) cmd.run(client, message, args);
};

First Command

Now, do you want to create a command? Let's do the typical Ping command, for that, I'll create the file in a subdirectory (remember that our command handler works with subfolders)

We define the module.exports as an object, with the name and alias properties, and the run arrow function, which will receive the client, message and args parameters

module.exports = {
    name: "ping",
    alias: ["p"],
    run: (client, message, args) => {
        
    }
};

(TIP: If you don't want to have aliases in your command, just leave the array empty)

Now, in the run function we will simply make it respond with the function of the message reply parameter, it will respond with the classic "pong".

run: (client, message, args) => {
    return message.channel.send("Pong!");
}

Now, we run our bot, and we send it the command, see what happens

Ready, you already have your modular bot working, now it is only a matter of starting to add commands, you can use the args to see things that were put after the command...

run: (client, message, args) => {
    return message.channel.send("Pong! " + args[0]);
}

And as you can see, the command works the same, even if it is in capital letters, I hope this tutorial has served you, thanks for reading!