let { logger } = require('./logging'); let { modules } = require('./module/index'); let trie = require('trie-prefix-tree'); let { getShortestPrefix } = require('./utility'); let help = require('./module/help'); let message = require('./message'); let utility = require('./utility'); let { initModule } = require('./module/abstract'); let sentinelValue = '!'; class Engine { constructor(config, bot, modules) { this.config = config; this.bot = bot; this.modules = modules; this.moduleMap = new Map(); this.commands = []; this.commandMap = new Map(); this.commandRadixTree = trie([]); } initModules() { this.modules.forEach((mod) => { logger.info("Loading module: %s", mod.name); initModule(mod); logger.info("Recognized commands: %s", mod.getRecognizedCommands()) this.moduleMap.set(mod.command, mod); this.commandMap.set(mod.command, mod); this.commandRadixTree.addWord(mod.command); }); this.modules.forEach((mod) => { let shortCharCommand = getShortestPrefix(this.commandRadixTree, mod.command, 1); let short3CharCommand = getShortestPrefix(this.commandRadixTree, mod.command, 3); let shortCommandAliases = [shortCharCommand, short3CharCommand]; logger.info("Adding short command %s for module: %s", shortCommandAliases, mod.name); shortCommandAliases.forEach((commandAlias) => { this.commandMap.set(commandAlias, mod); }) }); this.helpModule = help.create(this.moduleMap) initModule(this.helpModule); this.moduleMap.set(this.helpModule.command, this.helpModule); this.commandMap.set('help', this.helpModule); this.commands = Array.from(this.commandMap.keys()).sort() logger.info("Bound modules to keywords: %o", this.moduleMap); } init() { logger.info("Initializing modules"); this.initModules(); /* Bind Message Parsing */ let engine = this; let handleMessages = (event, room, toStartOfTimeline) => { /* Don't process messages from self */ if (event.sender.userId !== this.config.userId) { /* don't process messages that aren't of type m.room.message */ if (event.getType() !== "m.room.message") { logger.debug("Recieved message of type: %s", event.getType()); return; } else { let messageBody = event.event.content.body; logger.debug("[%s] %s", room.name, messageBody); if (messageBody.indexOf(sentinelValue) === 0) { let command = messageBody.split(' ')[0].substring(1); if (engine.commandMap.has(command)) { engine.commandMap.get(command).handleMessage(event, room, sendResponseMessageCallback(engine.bot)); } else { let responseMessage = "The following commands are recognized" responseMessage += "\n" + engine.commands.join(", ") responseMessage += "\nAdditional information can be discovered with !help " sendResponseMessage(engine.bot, room, responseMessage); } } } } } this.bot.init(handleMessages); /* Capture Exit Conditions */ ["SIGINT", "SIGTERM"].forEach((signature) => { process.on(signature, async () => { await this.bot.sendStatusShutdown() .then(() => { logger.info("Gracefully stopping Matrix SDK Client") this.bot.client.stopClient(); }); process.exit(0); }); }); process.on('exit', () => { logger.info("Shutting Down"); }); return this; } run() { this.bot.connect(); return this; } } /** * Handle the callback sending messages via the bot * * @param {*} bot * @param {*} room * @param {*} responseMessage */ function sendResponseMessage(bot, room, responseMessage) { logger.debug("Responding to room: %s with %o", room.roomId, responseMessage); Promise.resolve(responseMessage).then((promisedMessage) => { logger.debug("Sending message: %s", promisedMessage); if (promisedMessage instanceof Object) { bot.client.sendMessage(room.roomId, promisedMessage); } else if (utility.isString(promisedMessage)) { bot.client.sendMessage(room.roomId, message.createBasic(promisedMessage)); } else { logger.error("Unable to process response message: %s", promisedMessage); } }) } /** * Wrapper to produce a callback function that can be passed to the modules * * @param {*} bot */ function sendResponseMessageCallback(bot) { return (room, responseMessage) => { sendResponseMessage(bot, room, responseMessage); } } function create(config, bot) { return new Engine(config, bot, modules) } exports.create = create;