/** * Base module that all modules extend */ let { logger } = require('../logging'); let message = require('../message'); let { isFunction, getObjectKeysToPrototype } = require('../utility'); let { getConfig, sanitizeConfig } = require('../config'); class AbstractModule { /* Name of the module used in help documentation and logging. */ name = "AbstractModule"; /* Short description of the module functionality. */ description = "Base Module That All Other Modules Extend"; /* A helpful multiline string that defines the module usage */ helpAndUsage = `Example: !abstract_module !abstract_module : scares people` /* The exported command used to invoke the module directly. */ command = "abstract_module"; /* The default method to call when a command word is not recognized. */ defaultCommand = null; /* The module should be hidden from help and command dialogs. */ hidden = false; /* This module should receive all messages, regardless of whether the module was directly referenced with a command. */ canHandleIndirectMessages = false; /* Indicates if the modules needs access to the global config */ needGlobalConfig = false; /* Indicates if the module requires a readable config file. */ needConfig = false; /* internal */ /* The global config passed in */ _global_config = null; /* The loaded config file, if it exists. */ _config = null; constructor(name, description, command) { this.name = name; this.description = description; this.command = command; this._recognizedCommands = []; this._recognizedCommandMap = new Map(); } /** * Called after the module is initialized. */ postInit() { return this; } /** * Adds a recognized command and method to the module. * * @param {*} command * @param {*} methodName */ addRecognizedCommand(command, methodName) { this._recognizedCommands.push(command); this._recognizedCommandMap.set(command, methodName); } /** * Returns the list of recognized commands. */ getRecognizedCommands() { return this._recognizedCommandMap.keys(); } /** * The file path that the module configuration file is expected at. */ getConfigFilePath() { return process.env.NODE_PATH + '/data/' + this.name.toLowerCase().replace(' ', '_') + '-config.json'; } /** * Fields that should be sanitized before printing the config information for the user. */ getConfigSensitiveFields() { return []; } /** * Return a global config value or a default. * * @param {*} key * @param {*} defaultValue */ getGlobal(key, defaultValue = null) { if (this._global_config !== null && typeof this._global_config[key] !== 'undefined') { return this._global_config[key]; } return defaultValue } /** * Return a module config value or a default. * * @param {*} key * @param {*} defaultValue */ get(key, defaultValue = null) { if (this._config !== null && typeof this._config[key] !== 'undefined') { return this._config[key]; } return defaultValue } /** * Default functionality for receiving and processing a message. * * Override this if the module needs to do more complicated message processing. */ handleMessage(event, room, callback) { logger.debug("[%s] [%s] [%s]", this.name, room.name, event.event.content.body); let messageBody = event.event.content.body; let bodyParts = messageBody.split(' '); let trigger = bodyParts[0]; let command = bodyParts[1]; var args = []; if (bodyParts.length > 2) { args = bodyParts.slice(2); } logger.debug("[%s] Attempting to call %s with %s", trigger, command, args); let responseMessage = this.processMessage(event, command, ...args); callback( room, responseMessage ); } /* Call the command method with the args */ processMessage(event, command, ...args) { if (this._recognizedCommands.includes(command)) { logger.debug("Calling %s with %s", this._recognizedCommandMap.get(command), args); return this[this._recognizedCommandMap.get(command)](event, ...args); } else { if (this.defaultCommand != null) { logger.debug("Attempting to use default command %s", this.defaultCommand); try { let newArgs = [command].concat(...args); logger.debug("Calling %s with %s", this._recognizedCommandMap.get(this.defaultCommand), newArgs); return this[this._recognizedCommandMap.get(this.defaultCommand)](event, ...newArgs); } catch (e) { logger.error("Error while calling default command %s %s", this.defaultCommand, e); return this.cmd_help(); } } else { logger.debug("Unrecognized command %s", command); return this.cmd_help(); } } } /* Basic cmd methods */ /* return basic help information,. */ cmd_help(event, ...args) { return message.createBasic(this.helpAndUsage); } /** * Return the basic config file * * @param {...any} args */ cmd_config(event, ...args) { if (this._config != null) { let configBody = JSON.stringify(sanitizeConfig(this._config, this.getConfigSensitiveFields()), null, 2) return message.createMarkdown(configBody, "```" + configBody + "```"); } return null; } } let abstractModulePrototype = Object.getPrototypeOf(new AbstractModule('', '', '')); /* Initialization of a module. */ function init(mod, globalConfig) { logger.debug("Initializing module %s", mod.name); if (mod.needConfig) { logger.debug("Loading config file %s", mod.getConfigFilePath()); try { mod._config = getConfig(mod.getConfigFilePath(), mod.getConfigSensitiveFields()); } catch (e) { logger.error("Module %s needs a valid config file at %s", mod.name, mod.getConfigFilePath()); process.exit(1); } } if (mod.needGlobalConfig) { logger.debug("Bound global config to module %s", mod.name); mod._global_config = globalConfig; } logger.debug("Detecting command methods."); let commandMethods = getObjectKeysToPrototype(mod, abstractModulePrototype, (key) => { return key.startsWith('cmd_') && isFunction(mod[key]); }) // let commandMethods = objectKeys.filter(); logger.debug("Identified command methods: %s", commandMethods); commandMethods.forEach((commandMethodName) => { let command = commandMethodName.substring(4); mod.addRecognizedCommand(command, commandMethodName); }) logger.debug("Bound command methods for %s as %s", mod.name, mod.getRecognizedCommands()); mod.postInit(); } exports.AbstractModule = AbstractModule exports.initModule = init;