Source: subModule.js

// Copyright 2018-2019 Campbell Crowley. All rights reserved.
// Author: Campbell Crowley (dev@campbellcrowley.com)
/**
 * @description Base class for all Sub-Modules.
 */
class SubModule {
  /**
   * @description Create a subModule.
   */
  constructor() {
    /**
     * The help message to show the user in the main help message.
     *
     * @abstract
     * @type {undefined|string|Discord~EmbedBuilder}
     * @default
     */
    this.helpMessage = undefined;

    /**
     * The postfix for the global prefix for this subModule. Must be defined
     * before begin(), otherwise it is ignored.
     *
     * @abstract
     * @type {string}
     * @default
     */
    this.postPrefix = '';
    /**
     * The current Discord object instance of the bot. Defaults to require cache
     * value for editor autocompletion, updates to current reference at init.
     *
     * @public
     * @type {Discord}
     */
    this.Discord = require('discord.js');
    /**
     * The current bot client. Defaults to require cache value for editor
     * autocompletion, updates to current reference at init.
     *
     * @public
     * @type {Discord~Client}
     */
    this.client = this.Discord.Client;
    /**
     * The command object for registering command listeners. Defaults to require
     * cache value for editor autocompletion, updates to current reference at
     * init.
     *
     * @public
     * @type {Command}
     */
    this.command = require('./commands.js');
    /**
     * The common object. Defaults to require cache value for editor
     * autocompletion, updates to current reference at init.
     *
     * @public
     * @type {Common}
     */
    this.common = require('./common.js');

    /**
     * The parent SpikeyBot instance. Defaults to required cache value for
     * autocompletion, updates to current reference at init.
     *
     * @public
     * @type {?SpikeyBot}
     */
    this.bot = require('./SpikeyBot.js');

    /**
     * The commit at HEAD at the time this module was loaded. Essentially the
     * version of this submodule.
     *
     * @public
     * @constant
     * @type {string}
     */
    this.commit = '';
    this.commit = require('child_process')
        .execSync('git rev-parse --short HEAD').toString().trim();

    /**
     * The time at which this module was loaded for use in checking if the
     * module needs to be reloaded because the file has been modified since
     * loading.
     *
     * @public
     * @constant
     * @type {number}
     */
    this.loadTime = Date.now();

    /**
     * The name of this submodule. Used for differentiating in the log. Should
     * be defined before begin().
     *
     * @protected
     * @type {string}
     * @abstract
     */
    this.myName = 'SubModule';

    /**
     * Has this subModule been initialized yet (Has begin() been called).
     *
     * @protected
     * @type {boolean}
     * @default
     * @readonly
     */
    this.initialized = false;
  }
  /**
   * The function called at the end of begin() for further initialization
   * specific to the subModule. Must be defined before begin() is called.
   *
   * @protected
   * @abstract
   */
  initialize() {
  }

  /**
   * Initialize this submodule.
   *
   * @public
   * @param {Discord} Discord The Discord object for the API library.
   * @param {Discord~Client} client The client that represents this bot.
   * @param {Command} command The command instance in which to
   * register command listeners.
   * @param {Common} common Class storing common functions.
   * @param {SpikeyBot} bot The parent SpikeyBot instance.
   */
  begin(Discord, client, command, common, bot) {
    this.Discord = Discord;
    this.client = client;
    this.command = command;
    this.common = common;
    this.bot = bot;

    this.log = function(msg) {
      if (this.client.shard) {
        this.common.log(
            msg, `${this.client.shard.ids.join(' ')} ${this.myName}`, 1);
      } else {
        this.common.log(msg, this.myName, 1);
      }
    };
    this.debug = function(msg) {
      if (this.client.shard) {
        this.common.logDebug(
            msg, `${this.client.shard.ids.join(' ')} ${this.myName}`, 1);
      } else {
        this.common.logDebug(msg, this.myName, 1);
      }
    };
    this.warn = function(msg) {
      if (this.client.shard) {
        this.common.logWarning(
            msg, `${this.client.shard.ids.join(' ')} ${this.myName}`, 1);
      } else {
        this.common.logWarning(msg, this.myName, 1);
      }
    };
    this.error = function(msg) {
      if (this.client.shard) {
        this.common.error(
            msg, `${this.client.shard.ids.join(' ')} ${this.myName}`, 1);
      } else {
        this.common.error(msg, this.myName, 1);
      }
    };

    if (this.initialized) return;

    setTimeout(() => {
      if (this.initialized) return;
      this.debug(`${this.myName} Initialize...`);

      this.save = this.save.bind(this);
      this.initialize = this.initialize.bind(this);
      this.shutdown = this.shutdown.bind(this);
      this.log = this.log.bind(this);
      this.warn = this.warn.bind(this);
      this.debug = this.debug.bind(this);
      this.error = this.error.bind(this);

      this.initialize();
      this.log(`${this.myName} Initialized`);
      this.initialized = true;
    });
  }

  /**
   * Trigger subModule to shutdown and get ready for process terminating.
   *
   * @public
   */
  end() {
    if (!this.initialized) return;
    this.shutdown();
    this.initialized = false;
    this.log(`${this.myName} Shutdown`);
  }

  /**
   * Log using common.log, but automatically set name.
   *
   * @protected
   * @param {string} msg The message to log.
   */
  log(msg) {
    console.log(msg);
  }
  /**
   * Log using common.logDebug, but automatically set name.
   *
   * @protected
   * @param {string} msg The message to log.
   */
  debug(msg) {
    console.log(msg);
  }
  /**
   * Log using common.logWarning, but automatically set name.
   *
   * @protected
   * @param {string} msg The message to log.
   */
  warn(msg) {
    console.log(msg);
  }
  /**
   * Log using common.error, but automatically set name.
   *
   * @protected
   * @param {string} msg The message to log.
   */
  error(msg) {
    console.error(msg);
  }

  /**
   * Shutdown and disable this submodule. Removes all event listeners.
   *
   * @abstract
   * @protected
   */
  shutdown() {
  }

  /* eslint-disable @typescript-eslint/no-unused-vars */
  /**
   * Saves all data to files necessary for saving current state.
   *
   * @param {string} [opt='sync'] Can be 'async', otherwise defaults to
   * synchronous.
   * @abstract
   */
  save(opt = 'sync') {
  }
  /* eslint-enable @typescript-eslint/no-unused-vars */

  /**
   * @description Check if this module is in a state that is ready to be
   * unloaded. If false is returned, this module should not be unloaded and
   * doing such may risk putting the module into an uncontrollable state.
   * @see {@link SubModule~reloadable}
   *
   * @abstract
   * @public
   * @returns {boolean} True if can be unloaded, false if cannot.
   */
  unloadable() {
    return true;
  }

  /**
   * @description Check if this module is in a state that is ready to be
   * reloaded. If false is returned, this module should not be unloaded and
   * doing such may risk putting the module into an uncontrollable state. This
   * is different from unloadable, which checks if this module can be stopped
   * completely, this checks if the module can be stopped and restarted.
   * @see {@link SubModule~unloadable}
   *
   * @abstract
   * @public
   * @returns {boolean} True if can be reloaded, false if cannot.
   */
  reloadable() {
    return this.unloadable();
  }
}

/**
 * Extends SubModule as the base class of a child.
 *
 * @public
 * @static
 * @param {object} child The child class to extend.
 */
SubModule.extend = function(child) {
  child.prototype = new SubModule();
  child.prototype.constructor = child;
};

module.exports = SubModule;