Baphomet is the dedicated bot for nulloctet matrix
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

251 lines
7.3 KiB

  1. /**
  2. * Base module that all modules extend
  3. */
  4. let { logger } = require('../logging');
  5. let message = require('../message');
  6. let { isFunction, getObjectKeysToPrototype } = require('../utility');
  7. let { getConfig, sanitizeConfig } = require('../config');
  8. class AbstractModule {
  9. /*
  10. Name of the module used in help documentation and logging.
  11. */
  12. name = "AbstractModule";
  13. /*
  14. Short description of the module functionality.
  15. */
  16. description = "Base Module That All Other Modules Extend";
  17. /*
  18. A helpful multiline string that defines the module usage
  19. */
  20. helpAndUsage = `Example: !abstract_module <command>
  21. !abstract_module <boo> : scares people`
  22. /*
  23. The exported command used to invoke the module directly.
  24. */
  25. command = "abstract_module";
  26. /*
  27. The default method to call when a command word is not recognized.
  28. */
  29. defaultCommand = null;
  30. /*
  31. The module should be hidden from help and command dialogs.
  32. */
  33. hidden = false;
  34. /*
  35. This module should receive all messages, regardless of whether
  36. the module was directly referenced with a command.
  37. */
  38. canHandleIndirectMessages = false;
  39. /*
  40. Indicates if the modules needs access to the global config
  41. */
  42. needGlobalConfig = false;
  43. /*
  44. Indicates if the module requires a readable config file.
  45. */
  46. needConfig = false;
  47. /* internal */
  48. /*
  49. The global config passed in
  50. */
  51. _global_config = null;
  52. /*
  53. The loaded config file, if it exists.
  54. */
  55. _config = null;
  56. constructor(name, description, command) {
  57. this.name = name;
  58. this.description = description;
  59. this.command = command;
  60. this._recognizedCommands = [];
  61. this._recognizedCommandMap = new Map();
  62. }
  63. /**
  64. * Called after the module is initialized.
  65. */
  66. postInit() {
  67. return this;
  68. }
  69. /**
  70. * Adds a recognized command and method to the module.
  71. *
  72. * @param {*} command
  73. * @param {*} methodName
  74. */
  75. addRecognizedCommand(command, methodName) {
  76. this._recognizedCommands.push(command);
  77. this._recognizedCommandMap.set(command, methodName);
  78. }
  79. /**
  80. * Returns the list of recognized commands.
  81. */
  82. getRecognizedCommands() {
  83. return this._recognizedCommandMap.keys();
  84. }
  85. /**
  86. * The file path that the module configuration file is expected at.
  87. */
  88. getConfigFilePath() {
  89. return process.env.NODE_PATH + '/data/' + this.name.toLowerCase().replace(' ', '_') + '-config.json';
  90. }
  91. /**
  92. * Fields that should be sanitized before printing the config information for the user.
  93. */
  94. getConfigSensitiveFields() {
  95. return [];
  96. }
  97. /**
  98. * Return a global config value or a default.
  99. *
  100. * @param {*} key
  101. * @param {*} defaultValue
  102. */
  103. getGlobal(key, defaultValue = null) {
  104. if (this._global_config !== null && typeof this._global_config[key] !== 'undefined') {
  105. return this._global_config[key];
  106. }
  107. return defaultValue
  108. }
  109. /**
  110. * Return a module config value or a default.
  111. *
  112. * @param {*} key
  113. * @param {*} defaultValue
  114. */
  115. get(key, defaultValue = null) {
  116. if (this._config !== null && typeof this._config[key] !== 'undefined') {
  117. return this._config[key];
  118. }
  119. return defaultValue
  120. }
  121. /**
  122. * Default functionality for receiving and processing a message.
  123. *
  124. * Override this if the module needs to do more complicated message processing.
  125. */
  126. handleMessage(event, room, callback) {
  127. logger.debug("[%s] [%s] [%s]", this.name, room.name, event.event.content.body);
  128. let messageBody = event.event.content.body;
  129. let bodyParts = messageBody.split(' ');
  130. let trigger = bodyParts[0];
  131. let command = bodyParts[1];
  132. var args = [];
  133. if (bodyParts.length > 2) {
  134. args = bodyParts.slice(2);
  135. }
  136. logger.debug("[%s] Attempting to call %s with %s", trigger, command, args);
  137. let responseMessage = this.processMessage(event, command, ...args);
  138. callback(
  139. room,
  140. responseMessage
  141. );
  142. }
  143. /*
  144. Call the command method with the args
  145. */
  146. processMessage(event, command, ...args) {
  147. if (this._recognizedCommands.includes(command)) {
  148. logger.debug("Calling %s with %s", this._recognizedCommandMap.get(command), args);
  149. return this[this._recognizedCommandMap.get(command)](event, ...args);
  150. } else {
  151. if (this.defaultCommand != null) {
  152. logger.debug("Attempting to use default command %s", this.defaultCommand);
  153. try {
  154. let newArgs = [command].concat(...args);
  155. logger.debug("Calling %s with %s", this._recognizedCommandMap.get(this.defaultCommand), newArgs);
  156. return this[this._recognizedCommandMap.get(this.defaultCommand)](event, ...newArgs);
  157. } catch (e) {
  158. logger.error("Error while calling default command %s %s", this.defaultCommand, e);
  159. return this.cmd_help();
  160. }
  161. } else {
  162. logger.debug("Unrecognized command %s", command);
  163. return this.cmd_help();
  164. }
  165. }
  166. }
  167. /* Basic cmd methods */
  168. /*
  169. return basic help information,.
  170. */
  171. cmd_help(event, ...args) {
  172. return message.createBasic(this.helpAndUsage);
  173. }
  174. /**
  175. * Return the basic config file
  176. *
  177. * @param {...any} args
  178. */
  179. cmd_config(event, ...args) {
  180. if (this._config != null) {
  181. let configBody = JSON.stringify(sanitizeConfig(this._config, this.getConfigSensitiveFields()), null, 2)
  182. return message.createMarkdown(configBody, "```" + configBody + "```");
  183. }
  184. return null;
  185. }
  186. }
  187. let abstractModulePrototype = Object.getPrototypeOf(new AbstractModule('', '', ''));
  188. /*
  189. Initialization of a module.
  190. */
  191. function init(mod, globalConfig) {
  192. logger.debug("Initializing module %s", mod.name);
  193. if (mod.needConfig) {
  194. logger.debug("Loading config file %s", mod.getConfigFilePath());
  195. try {
  196. mod._config = getConfig(mod.getConfigFilePath(), mod.getConfigSensitiveFields());
  197. } catch (e) {
  198. logger.error("Module %s needs a valid config file at %s", mod.name, mod.getConfigFilePath());
  199. process.exit(1);
  200. }
  201. }
  202. if (mod.needGlobalConfig) {
  203. logger.debug("Bound global config to module %s", mod.name);
  204. mod._global_config = globalConfig;
  205. }
  206. logger.debug("Detecting command methods.");
  207. let commandMethods = getObjectKeysToPrototype(mod, abstractModulePrototype, (key) => {
  208. return key.startsWith('cmd_') && isFunction(mod[key]);
  209. })
  210. // let commandMethods = objectKeys.filter();
  211. logger.debug("Identified command methods: %s", commandMethods);
  212. commandMethods.forEach((commandMethodName) => {
  213. let command = commandMethodName.substring(4);
  214. mod.addRecognizedCommand(command, commandMethodName);
  215. })
  216. logger.debug("Bound command methods for %s as %s", mod.name, mod.getRecognizedCommands());
  217. mod.postInit();
  218. }
  219. exports.AbstractModule = AbstractModule
  220. exports.initModule = init;