brussell98/Mirai

View on GitHub
lib/ChatHandler.js

Summary

Maintainability
B
6 hrs
Test Coverage
/**
 * Handles messages received by the client
 * @prop {Bot} bot The bot
 * @prop {Object} awaits The active awaits
 */
class ChatHandler {
    /**
     * Creates a new ChatHandler
     * @param {Bot} [bot] The main client
     * @param {Object} [options] The configuration to use
     * @param {Boolean} [options.allowSelf=false] Allow messages from the bot to be handled
     * @param {Boolean} [options.allowBots=false] Allow messages from bots to be handled
     * @param {Object} [options.defaultHelpCommand] Enable the default help command
     * @param {String} [options.defaultHelpCommand.prefix="!"] The prefix to use for the help command
     * @param {String} [options.defaultHelpCommand.before] The text to put as a header
     * @param {String} [options.defaultHelpCommand.after] The text to put as a footer
     * @param {String} [options.defaultHelpCommand.title] The text to put as a title (bolded)
     * @param {String} [options.defaultHelpCommand.style="extended"] One of the following: basic, extended, embed
     */
    constructor(bot, options = { }) {
        this.bot = bot;

        this.allowSelf = !!options.allowSelf;
        this.allowBots = !!options.allowBots;
        this.awaits = { };
        this._nextAwaitId = 0;
        this.defaultHelpCommand = options.defaultHelpCommand !== undefined;

        if (this.defaultHelpCommand === true) {
            this.help = {
                prefix: options.defaultHelpCommand.prefix || '!',
                before: options.defaultHelpCommand.before,
                after: options.defaultHelpCommand.after,
                title: options.defaultHelpCommand.title,
                style: options.defaultHelpCommand.style
            };
        }

        this.messageHandler = message => {
            if ((message.author.id === this.bot.user.id && this.allowSelf === false) ||
                (message.author.bot && this.allowBots === false) ||
                (message.channel.guild && this.bot.blacklistedGuilds.includes(message.channel.guild.id)) ||
                this.bot.blacklistedUsers.includes(message.author.id))
                return;

            if (this.defaultHelpCommand && message.content === this.help.prefix + 'help')
                this.getHelp(message);

            Object.values(this.bot.commandPlugins).forEach(plugin => plugin.handle(message));

            for (let id in this.awaits) {
                if (this.awaits[id].trigger(message)) {
                    this.awaits[id].action(message);
                    delete this.awaits[id];
                }
            }
        };
    }

    /** Starts listening for messages */
    run() {
        this.bot.on('messageCreate', this.messageHandler);
    }

    /** Stops listening for messages */
    stop() {
        this.bot.removeListener('messageCreate', this.messageHandler);
    }

    /**
     * Generate and send a help message
     * @param {Message} message The message to respond to
     * @returns {Promise} The sent message
     */
    getHelp(message) {
        const help = Object.values(this.bot.commandPlugins).map(plugin => plugin.help).filter(help => help);
        switch (this.help.style) {
            case 'basic':
                return message.channel.createMessage(
                    `${this.help.title ? `**${this.help.title}**\n` : ''}${this.help.before ? this.help.before + '\n' : ''}\n${
                        help.filter(h => h).map(h => `__${h[0]}__: ${h[2].join(', ')}`).join('\n')
                    }${this.help.after ? '\n\n' + this.help.after : ''}`
                ).catch(error => this.bot.logger.error('Error sending help message:', error));
            case 'embed':
                return message.channel.createMessage({ embed: {
                    title: this.help.title || this.bot.user.username + ' Command List',
                    description: this.help.before || null,
                    fields: help.filter(h => h).map(h => h[2].map((cmd, i) => {
                        return { name: cmd, value: h[3][i] || '\u200B', inline: true };
                    })).reduce((a, b) => a.concat(b)),
                    footer: this.help.after ? { text: this.help.after } : null
                } }).catch(error => this.bot.logger.error('Error sending help message:', error));
            default: // style: extended
                return message.channel.createMessage(
                    `${this.help.title ? `**${this.help.title}**\n` : ''}${this.help.before ? this.help.before + '\n' : ''}\n${help.filter(h => h).map(h => {
                        return `**${h[0]}**: ${h[1]}\n\t${h[2].map((cmd, i) => cmd + ': ' + h[3][i]).join('\n\t')}`
                    }).join('\n\n')}${this.help.after ? '\n\n' + this.help.after : ''}`
                ).catch(error => this.bot.logger.error('Error sending help message:', error));
        }
    }

    /**
     * Add an await, which will trigger under certain conditions
     * @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) {
        if (typeof trigger !== 'function' || typeof action !== 'function')
            return new Error('Trigger and action must be functions');

        let id = this._nextAwaitId++;
        this.awaits[id] = { trigger, action };

        if (timeout)
            setTimeout(() => { delete this.awaits[id]; }, timeout);

        return id;
    }
}

module.exports = ChatHandler;