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.

216 lines
6.5 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. addRecognizedCommand(command, methodName) {
  64. this._recognizedCommands.push(command);
  65. this._recognizedCommandMap.set(command, methodName);
  66. }
  67. getRecognizedCommands() {
  68. return this._recognizedCommandMap.keys();
  69. }
  70. getConfigFilePath() {
  71. return process.env.NODE_PATH + '/data/' + this.name.toLowerCase().replace(' ', '_') + '-config.json';
  72. }
  73. getConfigSensitiveFields() {
  74. return [];
  75. }
  76. getGlobal(key, defaultValue = null) {
  77. if (this._global_config !== null && typeof this._global_config[key] !== 'undefined') {
  78. return this._global_config[key];
  79. }
  80. return defaultValue
  81. }
  82. get(key, defaultValue = null) {
  83. if (this._config !== null && typeof this._config[key] !== 'undefined') {
  84. return this._config[key];
  85. }
  86. return defaultValue
  87. }
  88. /**
  89. * Default functionality for receiving and processing a message.
  90. *
  91. * Override this if the module needs to do more complicated message processing.
  92. */
  93. handleMessage(event, room, callback) {
  94. logger.debug("[%s] [%s] [%s]", this.name, room.name, event.event.content.body);
  95. let messageBody = event.event.content.body;
  96. let bodyParts = messageBody.split(' ');
  97. let trigger = bodyParts[0];
  98. let command = bodyParts[1];
  99. var args = [];
  100. if (bodyParts.length > 2) {
  101. args = bodyParts.slice(2);
  102. }
  103. logger.debug("Attempting to call %s with %s", command, args);
  104. let responseMessage = this.processMessage(command, ...args);
  105. callback(
  106. room,
  107. responseMessage
  108. );
  109. }
  110. /*
  111. Call the command method with the args
  112. */
  113. processMessage(command, ...args) {
  114. if (this._recognizedCommands.includes(command)) {
  115. logger.debug("Calling %s with %s", this._recognizedCommandMap.get(command), args);
  116. return this[this._recognizedCommandMap.get(command)](...args);
  117. } else {
  118. if (this.defaultCommand != null) {
  119. logger.debug("Attempting to use default command %s", this.defaultCommand);
  120. try {
  121. let newArgs = [command].concat(...args);
  122. logger.debug("Calling %s with %s", this._recognizedCommandMap.get(this.defaultCommand), newArgs);
  123. return this[this._recognizedCommandMap.get(this.defaultCommand)](...newArgs);
  124. } catch (e) {
  125. logger.error("Error while calling default command %s %s", this.defaultCommand, e);
  126. return this.cmd_help();
  127. }
  128. } else {
  129. logger.debug("Unrecognized command %s", command);
  130. return this.cmd_help();
  131. }
  132. }
  133. }
  134. /* Basic cmd methods */
  135. /*
  136. return basic help information,.
  137. */
  138. cmd_help(...args) {
  139. return message.createBasic(this.helpAndUsage);
  140. }
  141. /**
  142. * Return the basic config file
  143. *
  144. * @param {...any} args
  145. */
  146. cmd_config(...args) {
  147. if (this._config != null) {
  148. let configBody = JSON.stringify(sanitizeConfig(this._config, this.getConfigSensitiveFields()), null, 2)
  149. return message.createMarkdown(configBody, "```" + configBody + "```");
  150. }
  151. return null;
  152. }
  153. }
  154. let abstractModulePrototype = Object.getPrototypeOf(new AbstractModule('', '', ''));
  155. /*
  156. Initialization of a module.
  157. */
  158. function init(mod, globalConfig) {
  159. logger.debug("Initializing module %s", mod.name);
  160. if (mod.needConfig) {
  161. logger.debug("Loading config file %s", mod.getConfigFilePath());
  162. try {
  163. mod._config = getConfig(mod.getConfigFilePath(), mod.getConfigSensitiveFields());
  164. } catch (e) {
  165. logger.error("Module %s needs a valid config file at %s", mod.name, mod.getConfigFilePath());
  166. process.exit(1);
  167. }
  168. }
  169. if (mod.needGlobalConfig) {
  170. logger.debug("Bound global config to module %s", mod.name);
  171. mod._global_config = globalConfig;
  172. }
  173. logger.debug("Detecting command methods.");
  174. let commandMethods = getObjectKeysToPrototype(mod, abstractModulePrototype, (key) => {
  175. return key.startsWith('cmd_') && isFunction(mod[key]);
  176. })
  177. // let commandMethods = objectKeys.filter();
  178. logger.debug("Identified command methods: %s", commandMethods);
  179. commandMethods.forEach((commandMethodName) => {
  180. let command = commandMethodName.substring(4);
  181. mod.addRecognizedCommand(command, commandMethodName);
  182. })
  183. logger.debug("Bound command methods for %s as %s", mod.name, mod.getRecognizedCommands());
  184. }
  185. exports.AbstractModule = AbstractModule
  186. exports.initModule = init;