command/index.js

const {
  ExtendedMap
} = require('../../util')

class Command {
  /**
   * Class representing a command.
   * <T extends DataClient>
   * @param {CommandData<T>} data The CommandData.
   */
  constructor ({
    name,
    description,
    run,
    options: {
      aliases = [],
      parameters = [],
      middleware = [],
      deleteInvoking,
      deleteResponse,
      deleteResponseDelay,
      subCommands = [],
      permission,
      postHook,
      dmOnly = false,
      guildOnly = false
    } = {}
  }) {
    /**
     * @type {string}
     */
    this.name = name
    /**
     * @type {string}
     */
    this.description = description
    /**
     * @type {CommandAction<T>}
     */
    this.run = run
    /**
     * @type {string[]}
     */
    this.aliases = aliases
    /**
     * @type {string[]}
     */
    this.parameters = parameters
    /**
     * @type {CommandMiddleware[]}
     */
    this.middleware = middleware
    /**
     * @type {boolean}
     */
    this.deleteInvoking = deleteInvoking
    /**
     * @type {boolean}
     */
    this.deleteResponse = deleteResponse
    /**
     * @type {number}
     */
    this.deleteResponseDelay = deleteResponseDelay
    /**
     * @type {Permission}
     */
    this.permission = permission
    /**
     * @type {PostHook}
     */
    this.postHook = postHook
    /**
     * @type {boolean}
     */
    this.dmOnly = dmOnly
    /**
     * @type {boolean}
     */
    this.guildOnly = guildOnly
    /**
     * @type {ExtendedMap<string, Command<T>>}
     */
    this.subCommands = new ExtendedMap()

    for (const sub of subCommands) {
      if (!(sub instanceof Command)) {
        throw new TypeError('INVALID_COMMAND')
      }
      this.subCommands.set(sub.name, sub)
    }
  }

  /**
   * @type {string}
   */
  get info () {
    return `Name: ${this.name}\n` + (
      this.aliases[0]
        ? 'Aliases: ' + this.aliases.join(', ') + '\n'
        : ''
    ) + 'Description: ' + this.description + (
      this.parameters[0]
        ? '\nParameters: ' + this.parameters.join(', ')
        : ''
    ) + (
      this.subCommands.size > 0
        ? '\n\nSub Commands\n' +
          this.subCommands.map((command) => command.info).join('\n\n')
        : ''
    )
  }
}

class GuildCommand extends Command {
  /**
   * Class representing a command.
   * <T extends DataClient>
   * @param {CommandData<T>} data The CommandData.
   */
  constructor (...args) {
    super(...args)
    /**
     * @constant {boolean}
     */
    this.guildOnly = true
    /**
     * @constant {boolean}
     */
    this.dmOnly = false
  }
}

class PrivateCommand extends Command {
  /**
   * Class representing a private command.
   * <T extends DataClient>
   * @param {CommandData<T>} data The CommandData.
   */
  constructor (data) {
    super(data)
    this.run = async (...args) => {
      const res = await data.run(...args)

      return typeof res === 'string'
        ? { content: res, dm: true }
        : { ...res, dm: true }
    }
    /**
     * @constant {boolean}
     */
    this.guildOnly = false
    /**
     * @constant {boolean}
     */
    this.dmOnly = true
  }
}
class SettingCommand extends GuildCommand {
  /**
   * Class representing a settings command.
   * <T extends DataClient>
   * @param {SettingCommandData<T>} data The SettingCommandData.
   */
  constructor ({
    getValue,
    displayName,
    setting,
    ...rest
  }) {
    super(rest)
    /**
     * @type {string}
     */
    this.displayName = displayName
    /**
     * @type {string}
     */
    this.setting = setting
    /**
     * @type {SettingCommandGetValue<T>}
     */
    this.getValue = getValue
  }
}

class ToggleCommand extends SettingCommand {
  /**
   * Class representing a toggle command.
   * <T extends DataClient>
   * @param {SettingCommandData<T>} data The ToggleCommandData.
   */
  constructor (data) {
    super({
      ...data,
      getValue: async function (bot, { msg: { channel } }) {
        const dbGuild = await bot.dbm.newQuery('guild').get(channel.guild.id)
        if (!dbGuild) {
          throw Error('fuck')
        }

        return dbGuild.get(this.setting) ? 'Enabled' : 'Disabled'
      },
      run: async function (bot, { msg: { channel } }) {
        const dbGuild = await bot.dbm.newQuery('guild').get(channel.guild.id)
        if (!dbGuild) {
          throw Error('fuck')
        }
        await dbGuild.save({
          [this.setting]: !dbGuild.get(this.setting)
        })

        return dbGuild.get(this.setting) ? 'Enabled!' : 'Disabled!'
      }
    })
  }
}

module.exports = {
  Command,
  GuildCommand,
  PrivateCommand,
  SettingCommand,
  ToggleCommand
}

/**
 * @typedef  CommandData
 * @property {string}            name        The command name.
 * @property {string}            description The command description.
 * @property {CommandAction<T>}  run         The command function.
 * @property {CommandOptions<T>} [options]   The command options.
 */
/**
 * @typedef  {CommandData} SettingCommandData
 * @property {SettingCommandGetValue<T>} getValue    The getValue function.
 * @property {string}                    displayName The name to print when listing this setting.
 * @property {string|Promise<string>}    setting     The name of the setting to use with the dbm.
 */
/**
 * @typedef  CommandOptions
 * @property {Array<string>}     [aliases=[]]                List of alias names for the command.
 * @property {Array<string>}     [parameters=[]]             List of paremeters that the command takes.
 * @property {Permission}        [permission]                The permission threshold needed to execute this command.
 * @property {PostHook}          [postHook]                  A function to run after successful execution of a command.
 * @property {boolean}           [deleteInvoking=true]       Whether or not the bot should delete the message that invoked this command.
 * @property {boolean}           [deleteResponse=true]       Whether or not the bot should delete the message response from this command.
 * @property {number}            [deleteResponseDelay=10000] How many miliseconds to wait before deleting the bots response.
 * @property {Array<Command<T>>} [subCommands]               The sub commands.
 * @property {boolean}           [dmOnly=false]              Whether or not the command can only be used in a DM channel (overrides guildOnly).
 * @property {boolean}           [dmOnly=true]               Whether or not the command can only be used in a Guild channel.
 */

/**
 * @callback CommandAction
 * @param    {T}                 bot     The DataClient.
 * @param    {CommandContext<T>} context The CommandContext.
 * @returns  {CommandResults|Promise<CommandResults>}
 */

/**
 * @callback SettingCommandGetValue
 * @param    {CommandContext<T>} context The CommandContext.
 * @returns  {string}                    The value of the Setting.
 */

/**
 * @callback PostHook<T>
 * @param    {T}                 bot      The DataClient.
 * @param    {CommandContext<T>} context  The CommandContext.
 * @param    {Message}           response The message response from the bot {@link https://abal.moe/Eris/docs/Message|(link)}.
 * @returns  {void}
 */
/**
 * @typedef  CommandContext
 * @property {string[]}                     params  The parsed params that make up the invoking message.
 * @property {Message}                      msg     The message from Discord {@link https://abal.moe/Eris/docs/Message|(link)}.
 * @property {TextableChannel|GuildChannel} channel The channel the message was sent in {@link https://abal.moe/Eris/docs/TextableChannel|(link)} {@link https://abal.moe/Eris/docs/GuildChannel|(link)}.
 */

/**
 * @typedef {MessageData|string|Promise<CommandResults>} CommandResults
 */

/**
 * @typedef  MessageData
 * @property {string} content The message content.
 */