status-manager/index.js

const {
  status: {
    isValidType,
    getActivity,
    equalStatuses
  },
  logger
} = require('../../util')

class StatusManager {
  /**
   * A class representing a StatusManager.
   * @param {DataClient}           bot             The DataClient to manage.
   * @param {DatabaseManager}      databaseManager The DatabaseManager used to fetch statuses.
   * @param {StatusManagerOptions} [options={}]    StatusManagerOptions.
   */
  constructor (bot, databaseManager, {
    mode = 'manual',
    interval = 43200000,
    defaultStatus
  } = {}) {
    /**
     * @type    {DataClient}
     * @private
     */
    this._bot = bot
    /**
     * @type    {DatabaseManager}
     * @private
     */
    this._dbm = databaseManager
    /**
     * @type    {string}
     * @private
     */
    this._mode = mode
    /**
     * @type    {number}
     * @private
     */
    this._interval = interval
    /**
     * @type {Status}
     */
    this.defaultStatus = defaultStatus
    /**
     * @type {Status}
     */
    this.current = null
    /**
     * @type    {Timeout}
     * @private
     */
    this._timer = null
  }

  /**
   * Initialize the statuses.
   * @returns {Promise<void>}
   */
  async initialize () {
    const [ defaultStatus ] = await this._dbm.newQuery('status')
      .equalTo('name', this.defaultStatus.name)
      .equalTo('type', this.defaultStatus.type)
      .find()
    if (!defaultStatus) {
      await this._dbm.newObject('status').save({
        name: this.defaultStatus.name,
        type: this.defaultStatus.type
      })
    }
    if (this._mode === 'random') {
      this.timerStart()
    }
    return this.setStatus(this.defaultStatus)
  }

  /**
   * Get the statuses for this bot.
   * @returns {Promise<Array<DatabaseObject>>} The search results.
   */
  getStatuses () {
    return this._dbm.newQuery('status').find()
  }

  /**
   * Search for statuses by name.
   * @param   {string}                         name The name to search by.
   * @returns {Promise<Array<DatabaseObject>>}      The search results.
   */
  findStatusByName (name) {
    return this._dbm.newQuery('status').equalTo('name', name).find()
  }

  /**
   * Add a status record.
   * @param   {Status}        status The status to add.
   * @returns {Promise<void>}
   */
  async addStatus (status) {
    const [
      oldStatuses,
      dbStatus
    ] = await Promise.all([
      this.getStatuses(), this._dbm.add('status', status)
    ])

    if (oldStatuses.length === 0) {
      this.setStatus(dbStatus.toJSON())
    } else if (
      oldStatuses.length === 1 && !this._timer && this._mode === 'random'
    ) {
      this.timerStart()
    }
  }

  /**
   * Delete a status record.
   * @param   {DatabaseObject} dbStatus The status to delete (as a DatabaseObject).
   * @returns {Promise<void>}
   */
  async deleteStatus (dbStatus) {
    if (equalStatuses(dbStatus.toJSON(), this.current)) {
      this.current = null
    }
    await dbStatus.delete()
    const statuses = await this.getStatuses()
    let nextStatus
    if (statuses.length === 0) {
      nextStatus = this.defaultStatus
    }
    this.setStatus(nextStatus)
  }

  /**
   * Set the status of the bot.
   * @param   {Status}        [status] Status to set to, if none is given and mode is random, it will randomly change.
   * @returns {Promise<void>}
   */
  async setStatus (status) {
    if (status && typeof status.name === 'string') {
      if (typeof status.type !== 'number' || !isValidType(status.type)) {
        status.type = 0
      }
    } else {
      status = null
      switch (this._mode) {
        case 'random':
          status = await this._randomStatus()
          break
        case 'manual':
        default: return
      }
    }

    if (status) {
      logger.info(getActivity(status.type), status.name)
      this.current = status
      this._bot.editStatus('online', status)
    }
  }

  /**
   * Start automatic status changing.
   * @returns {void}
   */
  timerStart () {
    if (!this._timer) {
      this._timer = setInterval(() => this.setStatus(), this._interval)
    }
  }

  /**
   * Stop changing status automatically.
   * @returns {void}
   */
  timerEnd () {
    if (this._timer) {
      this._timer = clearInterval(this._timer)
    }
  }

  async _randomStatus () {
    const statusQuery = this._dbm.newQuery('status')
    if (this.current) {
      statusQuery
        .notEqualTo('name', this.current.name)
        .notEqualTo('type', this.current.type)
    }
    const statuses = await statusQuery.find()
    if (statuses.length > 0) {
      this.timerStart()
      const dbStatus = statuses[Math.floor(Math.random() * statuses.length)]

      return {
        name: dbStatus.get('name'),
        type: dbStatus.get('type')
      }
    } else {
      this.timerEnd()
    }
  }
}

module.exports = StatusManager

/**
 * @typedef  StatusManagerOptions
 * @property {string} [mode='manual']      The mode of the StatusManager, either 'manual' or 'random'.
 * @property {number} [interval=43200000]  The amount of time to wait before randomly changing status (requires 'random' mode).
 * @property {Status} defaultStatus        The default status of the bot.
 */