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.

171 lines
5.9 KiB

  1. import { AbstractModule } from "./module/abstract";
  2. let { logger } = require('./logging');
  3. let { modules } = require('./module/index');
  4. let trie = require('trie-prefix-tree');
  5. let { getShortestPrefix } = require('./utility');
  6. let help = require('./module/help');
  7. let message = require('./message');
  8. let utility = require('./utility');
  9. let { initModule } = require('./module/abstract');
  10. let { sanitizeConfig } = require('./config');
  11. let commandPrefix = '!';
  12. class Engine {
  13. config: any;
  14. bot: any;
  15. modules: Array<AbstractModule>;
  16. moduleMap: Map<string, AbstractModule>;
  17. commands: Array<string>;
  18. commandMap: Map<string, AbstractModule>;
  19. commandRadixTree: any;
  20. helpModule: any;
  21. constructor(config, bot, modules) {
  22. this.config = config;
  23. this.bot = bot;
  24. this.modules = modules;
  25. this.moduleMap = new Map();
  26. this.commands = [];
  27. this.commandMap = new Map();
  28. this.commandRadixTree = trie([]);
  29. }
  30. initModules() {
  31. let sanitizedGlobalConfig = sanitizeConfig(
  32. this.config,
  33. [
  34. 'userPassword',
  35. 'accessToken'
  36. ])
  37. this.modules.forEach((mod) => {
  38. logger.info("Loading module: %s", mod.name);
  39. initModule(mod, sanitizedGlobalConfig);
  40. logger.info("Recognized commands: %s", mod.getRecognizedCommands())
  41. this.moduleMap.set(mod.command, mod);
  42. this.commandMap.set(mod.command, mod);
  43. this.commandRadixTree.addWord(mod.command);
  44. });
  45. this.modules.forEach((mod) => {
  46. let shortCharCommand = getShortestPrefix(this.commandRadixTree, mod.command, 1);
  47. let short3CharCommand = getShortestPrefix(this.commandRadixTree, mod.command, 3);
  48. let shortCommandAliases = [shortCharCommand, short3CharCommand];
  49. logger.info("Adding short command %s for module: %s", shortCommandAliases, mod.name);
  50. shortCommandAliases.forEach((commandAlias) => {
  51. this.commandMap.set(commandAlias, mod);
  52. })
  53. });
  54. this.helpModule = help.create(this.moduleMap)
  55. initModule(this.helpModule);
  56. this.moduleMap.set(this.helpModule.command, this.helpModule);
  57. this.commandMap.set('help', this.helpModule);
  58. this.commands = Array.from(this.commandMap.keys()).sort()
  59. logger.info("Bound modules to keywords: %o", this.moduleMap);
  60. }
  61. init() {
  62. logger.info("Initializing modules");
  63. this.initModules();
  64. /* Bind Message Parsing */
  65. let engine = this;
  66. let handleEvent = (event, room, toStartOfTimeline) => {
  67. /* Don't process messages from self */
  68. if (event.sender.userId !== this.config.userId) {
  69. /* don't process messages that aren't of type m.room.message */
  70. if (event.getType() !== "m.room.message") {
  71. logger.debug("Recieved message of type: %s", event.getType());
  72. return;
  73. } else {
  74. let messageBody = event.event.content.body;
  75. logger.debug("[%s] %s", room.name, messageBody);
  76. if (messageBody.indexOf(commandPrefix) === 0) {
  77. let command = messageBody.split(' ')[0].substring(1);
  78. if (engine.commandMap.has(command)) {
  79. engine.commandMap.get(command).handleMessage(event, room, sendResponseMessageCallback(engine.bot));
  80. } else {
  81. let responseMessage = "The following commands are recognized"
  82. responseMessage += "\n" + engine.commands.join(", ")
  83. responseMessage += "\nAdditional information can be discovered with !help <command>"
  84. sendResponseMessage(engine.bot, room, responseMessage);
  85. }
  86. }
  87. }
  88. }
  89. }
  90. this.bot.init(handleEvent);
  91. /* Capture Exit Conditions */
  92. let signals: NodeJS.Signals[] = ["SIGINT", "SIGTERM"];
  93. signals.forEach((signature: NodeJS.Signals) => {
  94. process.on(signature, async () => {
  95. await this.bot.sendStatusShutdown()
  96. .then(() => {
  97. logger.info("Gracefully stopping Matrix SDK Client")
  98. this.bot.client.stopClient();
  99. });
  100. process.exit(0);
  101. });
  102. });
  103. process.on('exit', () => {
  104. logger.info("Shutting Down");
  105. });
  106. return this;
  107. }
  108. run() {
  109. this.bot.connect();
  110. return this;
  111. }
  112. }
  113. /**
  114. * Handle the callback sending messages via the bot
  115. *
  116. * @param {*} bot
  117. * @param {*} room
  118. * @param {*} responseMessage
  119. */
  120. function sendResponseMessage(bot, room, responseMessage) {
  121. logger.debug("Responding to room: %s with %o", room.roomId, responseMessage);
  122. Promise.resolve(responseMessage).then((promisedMessage) => {
  123. if (responseMessage !== null) {
  124. logger.debug("Sending message: %s", promisedMessage);
  125. if (promisedMessage instanceof Object) {
  126. bot.client.sendMessage(room.roomId, promisedMessage);
  127. } else if (utility.isString(promisedMessage)) {
  128. bot.client.sendMessage(room.roomId, message.createBasic(promisedMessage));
  129. } else {
  130. logger.error("Unable to process response message: %s", promisedMessage);
  131. }
  132. } else {
  133. logger.warn("No response message offered");
  134. }
  135. })
  136. }
  137. /**
  138. * Wrapper to produce a callback function that can be passed to the modules
  139. *
  140. * @param {*} bot
  141. */
  142. function sendResponseMessageCallback(bot) {
  143. return (room, responseMessage) => {
  144. sendResponseMessage(bot, room, responseMessage);
  145. }
  146. }
  147. function create(config, bot) {
  148. return new Engine(config, bot, modules)
  149. }
  150. export { create };