brussell98/Mirai

View on GitHub
lib/Base/AbstractCommand.js

Summary

Maintainability
A
1 hr
Test Coverage
/**
 * A command managed by a CommandPlugin
 * @abstract
 */
class AbstractCommand {
    /**
     * Creates a new AbstractCommand
     * @abstract
     */
    constructor() {
        if (this.constructor === AbstractCommand)
            throw new Error("Can't instantiate an abstract class!");
    }

    /**
     * The name of the command
     * @type {String}
     * @abstract
     */
    get name() {
        throw new Error('name must be overwritten');
    }

    /**
     * A description of the command
     * @type {String}
     * @abstract
     */
    get description() {
        return '';
    }

    /**
     * Loads the command
     * @param {AbstractCommandPlugin} parent The plugin managing this command
     * @returns {Promise} Resolves with `this`
     */
    load(parent) {
        this.parent = parent;
        return Promise.resolve(this);
    }

    /**
     * Destroys/unloads the command
     * @returns {Promise} Resolves with no value
     */
    destroy() {
        this.parent = undefined;
        return Promise.resolve();
    }

    /**
     * Send a message to discord
     * @param {Message} message The message to respond to
     * @param {String|Object} content The content to be passed to Eris.Client#createMessage
     * @param {Object} [options] Options for sending the message
     * @param {Boolean} [options.deleteTrigger=false] Delete the message that triggered the command
     * @param {Number} [options.deleteAfter=0] Delete the created message after this many milliseconds
     * @param {Boolean} [options.DM=false] Send in a Direct Message
     * @param {Object} [options.file] The file to be sent with the message. Format as shown in the Eris docs
     * @param {Boolean} [options.paginate] Split the message at 2000 character intervals. Used to send log messages that would normally fail
     * @returns {Promise<Message>} The sent message, or nothing if pagination is true
     * @see {@link http://eris.tachibana.erendale.abal.moe/Eris/docs/Client#function-createMessage|Eris.Client#createMessage}
     */
    sendMessage(message, content, options) {
        if (!message || (!content && !options))
            return Promise.resolve();

        options = options || { };

        if (options.deleteTrigger && message.channel.guild && message.channel.permissionsOf(this.parent.bot.user.id).has('manageMessages'))
            message.delete().catch(error => this.parent.logger.warn('Error deleting trigger:', error));

        if (typeof content !== 'object')
            content = { content };

        if (content.embed && !message.channel.permissionsOf(this.parent.bot.user.id).has('embedLinks'))
            return Promise.resolve(this._sendMessage(message.channel, 'Unable to send message. I need the Embed Links permission', { }));

        if (options.file && !message.channel.permissionsOf(this.parent.bot.user.id).has('attachFiles'))
            return Promise.resolve(this._sendMessage(message.channel, 'Unable to send message. I need the Attach Files permission', { }));

        return (options.DM ? message.author.getDMChannel() : Promise.resolve(message.channel)).then(channel => {
            if (!options.paginate)
                return this._sendMessage(channel, content, options);

            let i = 0;
            while (i < content.content.length) {
                let _content = JSON.parse(JSON.stringify(content)); // Clone
                _content.content = content.content.slice(i, i + 2000);
                this._sendMessage(channel, _content, options);
                i += 2000;
                options.file = undefined;
            }

            return Promise.resolve();
        }).catch(error => this.parent.logger.warn('Error getting DM channel:', error));
    }

    /**
     * @private
     * @param {Channel} channel The channel to create the message in
     * @param {String|Object} content The content to be passed to Eris.Client#createMessage
     * @param {Object} [options] Options for sending the message
     * @returns {Promise<Message>} The sent message
     */
    _sendMessage(channel, content, options) {
        return channel.createMessage(content, options.file).then(message => {
            if (options.deleteAfter > 0)
                setTimeout(() => {
                    message.delete().catch(error => this.parent.logger.warn('Error deleting message (deleteAfter): ', error));
                }, options.deleteAfter);

            return message;
        }).catch(error => this.parent.logger.warn('Error creating message:', error));
    }

    /**
     * Await a message, the trigger a function
     * @param {Function} trigger A function that takes a Message and returns a Boolean
     * @param {Function} action A function to be executed when `trigger` returns true
     * @param {Number} [timeout] Delete the await after this many milliseconds
     * @returns {Number} The unique id of the await
     */
    awaitMessage(trigger, action, timeout) {
        return this.parent.bot.chatHandler.awaitMessage(trigger, action, timeout);
    }

    /**
     * Escape any discord markdown present in a string
     * @param {String} text The text to escape
     * @returns {String} The escaped text
     */
    escapeMarkdown(text) {
        return text.replace(/([`*_~])/g, '\\$1');
    }

    /**
     * Check if a user is on cooldown
     * @param {String} userID The id of the user to check for
     * @param {Number} waitTime If not on cooldown, put them on cooldown for this many milliseconds
     * @returns {Boolean} If the user is still on cooldown
     */
    userOnCooldown(userID, waitTime) {
        if (!this.cooldownUsers)
            this.cooldownUsers = new Set();

        if (this.cooldownUsers.has(userID))
            return true;

        this.cooldownUsers.add(userID);
        setTimeout(() => this.cooldownUsers.delete(userID), waitTime);

        return false;
    }
}

module.exports = AbstractCommand;