controllers/CommandController.js

const discordJs = require('discord.js')

/**
 * Controller for Discord bot {@link Command|Commands}
 *
 * @class
 */
class CommandController {
  /**
   * Initializes a new CommandController
   *
   * @param {Object} config configuration object
   * @param {Esdi} config.server Esdi server instance
   * @param {String} [config.botPrefix = 'esdi!'] prefix for {@link Command|Commands}
   * @memberof CommandController
   */
  init ({ server, botPrefix = 'esdi!' }) {
    this.server = server
    this.prefix = botPrefix
  }

  /**
   * Starts the CommandController
   *
   * @listens Esdi#start
   * @memberof CommandController
   */
  start () {
    console.log('[#] Starting CommandController...')

    this.cooldowns = new Map()
  }

  /**
   * Stops the CommandController
   *
   * @listens Esdi#stop
   * @memberof CommandController
   */
  stop () {
    console.log('[#] Stopping CommandController...')

    delete this.cooldowns
  }

  /**
   * Finds a matching Command
   *
   * @param {String} commandName name or alias of command
   * @returns {Command}
   * @memberof CommandController
   */
  findCommand (commandName) {
    return this.server.commands.get(commandName) || this.server.commands.find(cmd => cmd.aliases && cmd.aliases.includes(commandName))
  }

  /**
   * Determines the command prefix for a provided message
   *
   * @param {external:Message} message discord.js Message
   * @returns {String|Boolean} prefix string or false
   * @memberof CommandController
   */
  determinePrefix (message) {
    let prefix = false
    // first, check if using the primary prefix
    if (message.content.startsWith('esdi!')) {
      // if so, use the primary prefix
      prefix = 'esdi!'
      // then, check if this Guild has a custom prefix
    } else if (
      this.server.controllers.get('GuildController').get(message.guild.id).prefix &&
      message.content.startsWith(this.server.controllers.get('GuildController').get(message.guild.id).prefix)) {
      // if so, use the Guild's custom prefix
      prefix = this.server.controllers.get('GuildController').get(message.guild.id).prefix
      // finally, check the instance prefix
    } else if (
      message.content.startsWith(this.prefix)) {
      prefix = this.prefix
    }

    return prefix
  }

  /**
   * Executes a Command
   *
   * @param {Object} config configuration object
   * @param {Command} config.command Discord bot Command
   * @param {String[]} config.args array of argument strings
   * @param {Message} config.message Discord Message
   * @returns {Message}
   * @memberof CommandController
   */
  executeCommand ({ command, args, message }) {
    if (command.ownerOnly && (message.author.id !== message.guild.ownerID)) {
      return message.reply(`only the server owner can use the \`${command.name}\` command.`)
    }

    if (command.guildOnly && message.channel.type === 'dm') {
      return message.reply(`the \`${command.name}\` command does not work in a direct message.`)
    }

    if (command.args && !args.length) {
      let reply = `Invalid use of the \`${command.name}\` command, ${message.author}.`

      if (command.usage) {
        reply += `\n**Syntax:** \`${this.determinePrefix(message)}${command.name} ${command.usage}\``
      }

      return message.channel.send(reply)
    }

    if (!this.cooldowns.has(command.name)) {
      this.cooldowns.set(command.name, new discordJs.Collection())
    }

    const now = Date.now()
    const timestamps = this.cooldowns.get(command.name)
    const cooldownAmount = (command.cooldown) * 1000

    if (timestamps.has(message.author.id)) {
      const expirationTime = timestamps.get(message.author.id) + cooldownAmount

      if (now < expirationTime) {
        const timeLeft = (expirationTime - now) / 1000
        return message.reply(`you must wait \`${Math.ceil(timeLeft)} second${Math.ceil(timeLeft) !== 1 ? 's' : ''}\` before reusing the \`${command.name}\` command.`)
      }
    }

    timestamps.set(message.author.id, now)
    setTimeout(() => timestamps.delete(message.author.id), cooldownAmount)

    try {
      command.execute({ message, args, server: this.server })
    } catch (error) {
      console.error(error)
      message.reply(`an error occurred while attempting to execute the \`${command.name}\` command.`)
    }
  }
}

// factory
module.exports = new CommandController()