// 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;