# Commands

A full reference and API documentation can be found on the Chariot.Command documentation page.

# Template

This is an empty command template with every instantiation property available. This template can be used and filled in as required for creating new commands. If a certain property e.g. this.userPermissions isn't required it should be left out instead of leaving it empty (which might cause unexpected behavior). An explanation can be found under the template.

const Chariot = require('chariot.js');

class CommandName extends Chariot.Command {
    constructor() {
        super(); 

        this.name = '';
        this.aliases = [''];
        this.subcommands = [''];
        this.permissions = [''];
        this.userPermissions = [''];
        this.nsfw = false;
        this.owner = false;
        this.allowDMs = false;
        this.cooldown = 3;
        this.help = {
            message: '',
            usage: '',
            example: [''],
            inline: true,
            visible: true
        }
    }

    /**
     * Precondition testing method. This method will run BEFORE the main command logic.
     * Once every test passed, next() MUST be called, in order to run the main command logic!
     * @param {object} message An Eris.Message object emitted from Eris
     * @param {string[]} args An array containing all provided arguments
     * @param {object} chariot The main bot client.
     * @param {Function} next Marking testing as done, invoking the main command executor
     */
    async runPreconditions(message, args, chariot, next) {
        next();
    }

    /**
     * Main method running after passing preconditions
     * @param {object} message An Eris.Message object emitted from Eris
     * @param {string[]} args An array containing all provided arguments
     * @param {object} chariot The main bot client. 
     */
    async execute(message, args, chariot) {
        /* Main command logic goes here */
    }
}

module.exports = new CommandName();

PRO TIP

Don't forget to export a newly instantiated object of the command class as shown above instead of just the class!

# Explanation

A valid Chariot.js command file that's inherited the abstract Chariot.Command class can be split into four distinct sections:

  •   Instantiation Properties
  •   Pre-condition testing
  •   Sub-command executors
  •   Main command executor

# Instantiation Properties

Instantiation properties are used to model a Chariot.js command and make it fit to one's needs. Generally speaking, every property can be omitted but the name property, since Chariot.js uses it to register, load and initiate the command.

Properties that aren't needed, e.g. to check for user permissions (userPermissions) or for defining sub-commands (subcommands), should be removed entirely instead of leaving them empty or with invalid children.

A command's cooldown is specified in seconds and can be specified as a floating point number as well, e.g. 2.75.

The help property can also be omitted if the default help system isn't required or used. If however the help system is being used and the help property within a command is provided the entire help object should be filled in. Chariot.js will default to Not Provided if a property within the help object property is missing, incomplete, invalid or empty.

If however the default help system of Chariot.js is used, several different properties of the help object can be filled in. The visible property of the help object toggles whether or not the command should be listed on the default help command list. This is useful if commands are wished to be hidden from the default help command list even if they are not owner-level commands. Visibility of commands defaults to true (even if the property within the help object is omitted).

Chariot.js also supports enabling certain commands for DM usage. This behavior can be enabled on a per-command basis with the allowDMs property, which defaults to false, meaning commands are not useable in DMs by default!

A detailed property specification can be found on the Command documentation page or under Typedefs for Help Properties.

# Pre-Condition Testing

Trying to be as unopinionated and modular as possible, Chariot.js completely leaves the choice up to the developer what should be used. This also holds true for the runPreconditions method of the command class.

As stated in the full API reference for the Chariot.Command class, the runPreconditions method is fully optional and can be fully left out if not needed. But what does it do? The method is technically a fully asynchronous pre-execution hook and gets executed before the actual command logic (in this case the execute method). The runPreconditions method provides a callback function and API similar to middleware functions in HTTP frameworks like Express or Polka. Unlike other pre-execution hooks, Chariot.js handles pre-condition testing slightly differently and won't execute the main command logic before the provided next method was called. This allows for pinpoint precise execution chains with the developer being the orchestrator telling Chariot.js exactly when to start processing the main command logic.

PRO TIP

Omitting the runPreconditions method allows for smaller and cleaner command files if testing isn't needed!

WARNING

A common pitfall is forgetting to call the provided next method which causes the command to never leave pre-condition testing thus never entering the main execution phase and telling Chariot.js to invoke the main executors!

# Sub-Command Executors

A Chariot.js command supports fully autonomous, self-contained and argument aware sub-command executors allowing for high level code abstractions and extremely clean and readable code. If sub-commands are wished to be used, each sub-command needed must be specified by name as a separate element ínside of the subcommands array property within the constructor. For each added subcommand within that array an identically named sub-command executor must be present within the command's class. If for instance the command gets instantiated with the following sub-command property: this.subcommands = ['foo', 'bar'];, the class must contain two methods named foo and bar respectively:

async foo(message, args, chariot) { /* ... */ }
async bar(message, args, chariot) { /* ... */ }

PRO TIP

If this setup process is done incorrectly, Chariot.js will throw an appropriate error with tips on how to fix the issue.

The arguments passed to sub-commands are level aware, which means the arguments array will only contain arguments after the invoking sub-command argument. If for example a command has been invoked like !testcommand bar baz qux (given the configured prefix is !) the arguments array for the bar sub-command will consist of ['baz', 'qux'], cleverly omitting the argument that's been used to invoke the sub-command in the first place.

If however an argument is passed that doesn't match with any sub-command identifier, e.g. !testcommand beep boop, Chariot.js will fall back to the main executor execute and pass every argument collected inside of the arguments array.

A full command example utilizing sub-commands can be found here.

# Main Command Executor

The main command executor is the main method of each Chariot.js command and is the last fallback. If no pre-condition tests were specified or no sub-commands were used, the main commad executor execute will run. Down below are some examples for a few commonly used commands to demonstrate how to add new commands to Chariot.js.

# Examples

Here are a few command examples to demonstrate how to structure a Chariot.js command file

# Ping Command

const Chariot = require('chariot.js');

/**
 * This example is an extremely basic command example of how a command could look like and work with Chariot.js
 */
class Ping extends Chariot.Command {
    constructor() {
        super();

        this.name = 'ping';
        this.cooldown = 3;
        this.allowDMs = true;
        this.help = {
            message: 'A super simple ping command which is also usable in DMs',
            usage: 'ping',
            example: ['ping'],
            inline: true
        }
    }

    /**
     *  This is the main method getting executed by the MessageHandler upon a valid command request
     * 
     * @param {Object} message The emitted message object that triggered this command  
     * @param {String[]} args The arguments fetched from using the command
     * @param {Object} chariot The bot client itself
     */
    async execute(message, args, chariot) {
        message.channel.createMessage("Pong!");
    }
}

module.exports = new Ping();

# Sub-Command

const Chariot = require('chariot.js');

/**
 * This example is an extremely basic command example of how to make use of sub-commands with Chariot.js
 */
class Ping extends Chariot.Command {
    constructor() {
        super();

        this.name = 'ping';
        this.subcommands = ['pong', 'peng'];
        this.cooldown = 3;
        this.help = {
            message: 'A super simple ping command with extra sub commands',
            usage: 'ping <[pong|peng]>',
            example: ['ping', 'ping pong', 'ping peng'],
            inline: false
        }
    }

    /**
     * Sub-command handler for sub-command "pong".
     * In this example this sub-command handler gets invoked whenever a user types "<prefix>ping pong
     */
    async pong(message, args, chariot) {
        message.channel.createMessage("Pong!");
    }

    /**
     * Sub-command handler for sub-command "pong".
     * In this example this sub-command handler gets invoked whenever a user types "<prefix>ping peng
     */
    async peng(message, args, chariot) {
        message.channel.createMessage("Peng!");
    }

    /**
     *  This is the main method getting executed by the MessageHandler upon a valid command request.
     *  This method is also a fallback for when sub-commands were specified but user arguments didn't match.
     * 
     * @param {Object} message The emitted message object that triggered this command  
     * @param {String[]} args The arguments fetched from using the command
     * @param {Object} chariot The bot client itself
     */
    async execute(message, args, chariot) {
        message.channel.createMessage("Ping!");
    }
}

module.exports = new Ping();

# Eval Command

const Chariot = require('chariot.js');

/**
 * This example shows how to make a command only accessible to the defined bot owner
 * by setting this.owner = true in the class constructor and using precondition testing.
 */
class Eval extends Chariot.Command {
    constructor() {
        super();

        this.name = 'eval';
        this.owner = true;
        this.cooldown = 5;
        this.userPermissions = ['administrator'];
        this.help = {
            message: 'A simple demonstration of how a secure Eval command could be implemented including precondition testing!',
            usage: 'eval <code>',
            example: ['eval 2+2', 'eval process.exit(0);'],
            inline: false,
            visible: false
        }
    }

    /**
     * Precondition testing method. This method will run BEFORE the main command logic.
     * Once ever test passed, next() MUST be called, in order to run the main command logic!
     * @param {object} message An Eris.Message object emitted from Eris
     * @param {string[]} args An array containing all provided arguments
     * @param {object} chariot The main bot client.
     * @param {Function} next Marking testing as done, invoking the main command executor
     */
    async runPreconditions(message, args, chariot, next) {
        if (args.join().length > 1000) {
            return message.channel.createMessage('Too many arguments!');
        }

        next();
    }

    /**
     * Sanitize a text
     * @param {string} text The evaled texted to be sanitized before embedding into a code block 
     */
    sanitize(text) {
        if (typeof(text) === "string") {
            return text.replace(/`/g, "`" + String.fromCharCode(8203)).replace(/@/g, "@" + String.fromCharCode(8203));
        } else {
            return text;
        }
    }

    /**
     *  This is the main method getting executed by the MessageHandler upon a valid command request
     * 
     * @param {Object} message The emitted message object that triggered this command  
     * @param {String[]} args The arguments fetched from using the command
     * @param {Object} chariot The bot client itself
     */
    async execute(message, args, chariot) {
        try {
            let evaled = await eval(args.join(' '));

            if (typeof evaled !== "string") {
                evaled = require('util').inspect(evaled);
            }

            message.channel.createCode(this.sanitize(evaled), "js");
        } catch (evalError) {
            message.channel.createCode(this.sanitize(evalError), "js");
        }
    }
}

module.exports = new Eval();

# Await Message Command

const Chariot = require('chariot.js');

/**
 * This example file shows how to add multiple command aliases and how to utilize the extremely easy 
 * and pretty straight forward message collector.
 */
class MessageCollector extends Chariot.Command {
    constructor() {
        super();

        this.name = 'messagecollector';
        this.cooldown = 10;
        this.aliases = [
            'msgscollect',
            'msgsclt'
        ];
        this.help = {
            message: 'A simple message collector demo showing how easy it is to collect messages with Chariot.js!',
            usage: 'messagecollector',
            example: ['messagecollector'],
            inline: true
        }
    }

    /**
     *  This is the main method getting executed by the MessageHandler upon a valid command request
     * 
     * @param {Object} message The emitted message object that triggered this command  
     * @param {String[]} args The arguments fetched from using the command
     * @param {Object} chariot The bot client itself
     */
    async execute(message, args, chariot) {
        let question = await message.channel.createMessage("What's on your mind? Tell me!");
        let answers = await message.channel.awaitMessages(msg => msg.author.id === message.author.id, { time: 10000, maxMatches: 3 });

        if (answers.length) {
            const results = [];

            answers.forEach(answer =>  results.push(answer.content));

            message.channel.createMessage(`You answered ${answers.length} time(s) and said:\n${results.join('\n')}!`);
        } else {
            message.channel.createMessage(`You didn't answer at all!`);
        }
    }
}

module.exports = new MessageCollector();