Advanced: Command Objects

The Command Object method is probably one of the most used method of advanced command handling. Depends on the framework, the way to do this varies, but all of the do the same technique: putting commands into separate files, and require them.

Pros:

  • Allowing reloading all, or individual commands
  • Easy to manage and maintain
  • Help command auto updates

Cons:

  • A little bit too advanced
  • For small bots it might not be needed at all

A normal Command object:

The command object's most important properties is, obviously, the function itself. And then, depends on the command, there might be help text, detailed help text, or alias.

let ping = {
    help: "Send a Pong!",
    func: (message, args) => {
        message.channel.sendMessage('Pong!');
    }
}

To be able to put this in a different file, and usable in the index file, you use exports. There are export.[properties] and module.exports but I won't be explaining the detailed differences of both (it's not like I don't understand it either, ok?). I'll be using the latter in this example, and it will be something simple:

module.exports = ping;
// Alternatively, just do it like this
module.exports = {
    help: "Send a Pong!",
    func: (message, args) => {
        message.channel.sendMessage('Pong!');
    }
}

This way when we require the js file, the whole function (as well as help text) will be available to be called in the index file.

ping = require('./commands/ping.js');
ping.func(message, args); //Pong!
console.log(ping.help); //Send a Pong!

However, sometimes you will need several other things that is stored in the main file, things like prefix for example, or other commands. To do this, all you have to do is to attach these things into the client object and add the client as one of the arguments to the func.

// Commands/ping.js
let ping = {
    help: "Send a Pong!",
    func: (client, message, args) => {
        message.channel.sendMessage('Pong!');
        console.log(client.prefix);
    }
}
// Index.js
ping = require('./commands/ping.js');
client.prefix = "$";
ping.func(client, message, args); //$
console.log(ping.help); //Send a Pong!

The problem with this is it MIGHT be conflicting with other properties or method of Discord client. So I suggest creating the an upper level which will store the actual client itself, as well as other configs. So it will look like this:

Client = {
    config: require('./config.json'),
    bot: Discord.Client()
}

This way, we'll keep the config data, and the bot's properties and methods untouched no matter how much discord.js change their codes. Handy! Next we'll take a look at how to expand into several commands

Multiple Command Objects

So after we use the technique above and have several command files, but then, if we keep requiring the command files manually then everything will be stupid, won't it? That's why we'll automate the requires too, with the help of fs.readdirSync.

let commandsList = fs.readdirSync('./commands/'); // return an array of all the files and folders inside the commands folder
Client.commands = {}; // initiate value for the command list of client
for (i = 0; i < commandsList.length; i++) {
    let item = commandsList[i];
    if (item.match(/\.js$/)) { // only take js files
        delete require.cache[require.resolve(`./commands/${item}.js`)]; // delete the cache of the require, useful in case you wanna reload the command again
        Client.commands[item.slice(0, -3)] = require(`./commands/${item}.js`); // and put the require inside the client.commands object
    }
}

It's also very silly if we also do if-else or switch down in the onMessage event, we already have an object with all the commands stored, so we don't we use it instead?

Client.bot.on('message', (msg) => {
    // Skipping the whole bot check and selfbot check cuz you should know how to do it right now
    if (msg.content.startsWith(Client.config.prefix)) {
        args = msg.content.slice(Client.config.prefix.length).split(' ');
        if (args[0] in Client.commands) {
            Client.commands[args[0]].func(Client, msg, args);
        }
    }
});

That should be the basic code of the command objects handler. However we still have a few things more to cover before this part of the tutorial can be finished!

Special Commands

As discussed above, one of the pros for this method is the ability to reload commands, as well as auto-update help files. But how can we do that? Let's find out

To begin, we'll have to do some modification of the require automation code above. It's simply putting the whole code into a function and attach it to the Client. You can put it on a separate file for easy management but it's optional.

Client.load = (command) => {
    let commandsList = fs.readdirSync('./commands/');
    if (command) {
        if (commandsList.indexOf(`${command}.js`) >= 0) {
            delete require.cache[require.resolve(`./commands/${command}.js`)];
            Client.commands[command] = require(`./commands/${command}.js`);
        }
    } else {
        Client.commands = {};
        for (i = 0; i < commandsList.length; i++) {
            let item = commandsList[i];
            if (item.match(/\.js$/)) {
                delete require.cache[require.resolve(`./commands/${item}.js`)];
                Client.commands[item.slice(0, -3)] = require(`./commands/${item}.js`);
            }
        }
    }
}
Client.load();

And then, we'll simply have the following command files as followed:

reload.js

module.exports = {
    help: 'Reload the command',
    func: (Client, msg, args) => {
        if (args.length > 1) Client.load(args[1]);
        else Client.load();
    }
}

help.js

module.exports = {
    help: 'Plz send help!!',
    func: (Client, msg, args) => {
        if (args.length > 1) {
            if (args[1] in Client.commands && Client.commands[args[1]].help)
                msg.channel.sendCode('asciidoc', `${Client.config.prefix + args[1]} :: ${Client.commands[args[1]].help}`;
        else {
            let help = "";
            for (var command in Client.commands) {
                help += `${Client.config.prefix + command} :: ${Client.commands[command].help}\n`
            }
            msg.channel.sendCode('asciidoc', help);
        }
    }
}

And that's how you do the most basic kind of command object handler. You might want to adjust the codes a little bit to accommodate several other features such as aliases or categorizing the commands, or use callback / promise for the reload command to tell you that the process is done, etc etc, but in essense, this is the most basic stuffs of the handler. Whether you want to use exactly this, or expand / modify to fit your bot, it's all up to you from this point :P

I hope this tutorial is bug-free but in case there are bugs in any of my codes, or if I have forgotten some steps in the tutorial, or want me to explain more about the codes, please tell me and I'll look into it. Please note though that this method requires a decent amount of js knowledge so please, don't try to use it if you're new to js or coding as a whole.

results matching ""

    No results matching ""