From eb2f2a006bc19dab3c53659a208e7cf2d3d059f9 Mon Sep 17 00:00:00 2001 From: Drew Short Date: Sun, 29 Dec 2019 18:19:33 -0600 Subject: [PATCH] Adding help message support --- bot/engine.js | 74 ++++++++++++++++++++++++++++++++++-------- bot/module/abstract.js | 6 ++++ bot/module/giphy.js | 4 +-- bot/module/help.js | 37 +++++++++++++++++++++ bot/utility.js | 23 ++++++++++++- package-lock.json | 5 +++ package.json | 1 + 7 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 bot/module/help.js diff --git a/bot/engine.js b/bot/engine.js index 34c848d..d5f879f 100644 --- a/bot/engine.js +++ b/bot/engine.js @@ -1,11 +1,41 @@ let { logger } = require('./logging'); -let { modules } = require('./module/index') +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 sentinelValue = '!'; class Engine { constructor(config, bot, modules) { - this.config = config + this.config = config; this.bot = bot; this.modules = modules; + this.moduleMap = new Map(); + this.commandMap = new Map(); + this.commandRadixTree = trie([]); + } + + initModules() { + this.modules.forEach((module) => { + logger.info("Loading module: %s", module.name); + this.moduleMap.set(module.command, module); + this.commandMap.set(module.command, module); + this.commandRadixTree.addWord(module.command); + }); + + this.modules.forEach((module) => { + let shortCommand = getShortestPrefix(this.commandRadixTree, module.command, 3); + logger.info("Adding short command %s for module: %s", shortCommand, module.name); + this.commandMap.set(shortCommand, module); + }); + + this.helpModule = help.create(this.moduleMap) + this.commandMap.set('help', this.helpModule); + + logger.info("Bound modules to keywords: %o", this.moduleMap); } init() { @@ -13,12 +43,36 @@ class Engine { this.initModules(); /* Bind Message Parsing */ + let engine = this; let handleMessages = (event, room, toStartOfTimeline) => { - if (event.getType() !== "m.room.message") { - logger.debug("Recieved message of type: %s", event.getType()); - return; // only use messages - } else { - logger.debug("[%s] %s", room.name, event.event.content.body); + /* 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); + var responseMessage = null; + if (engine.commandMap.has(command)) { + responseMessage = engine.commandMap.get(command).handleMessage(event, room); + } else { + responseMessage = engine.helpModule.help(); + } + + logger.debug("Responding to room: %s", room.roomId); + if (responseMessage instanceof Object) { + engine.bot.client.sendMessage(room.roomId, responseMessage); + } else if (utility.isString(responseMessage)) { + engine.bot.client.sendMessage(room.roomId, message.createBasic(responseMessage)); + } else { + logger.error("Unable to process response message: %s", responseMessage); + } + } + } } } @@ -44,12 +98,6 @@ class Engine { return this; } - initModules() { - this.modules.forEach((module) => { - logger.info("Loading module: %s", module.name); - }); - } - run() { this.bot.connect(); return this; diff --git a/bot/module/abstract.js b/bot/module/abstract.js index 5a1441d..91233f7 100644 --- a/bot/module/abstract.js +++ b/bot/module/abstract.js @@ -3,6 +3,7 @@ */ let { logger } = require('../logging'); +let message = require('../message'); class AbstractModule { name = "AbstractModule" @@ -17,6 +18,11 @@ class AbstractModule { handleMessage(event, room) { logger.debug("[%s] [%s] [%s]", this.name, room.name, event.event.content.body); + return message.createBasic(this.name + " processed the message"); + } + + help(event, room) { + return message.createBasic(this.name + " HELP!"); } } diff --git a/bot/module/giphy.js b/bot/module/giphy.js index ab48fb6..b0a9698 100644 --- a/bot/module/giphy.js +++ b/bot/module/giphy.js @@ -7,8 +7,8 @@ let { AbstractModule } = require('./abstract'); class GiphyModule extends AbstractModule { constructor() { super( - "Giphy", - "Insert Giphy Links/Media", + "Giphy", + "Insert Giphy Links/Media", "giphy" ); } diff --git a/bot/module/help.js b/bot/module/help.js new file mode 100644 index 0000000..9816ff4 --- /dev/null +++ b/bot/module/help.js @@ -0,0 +1,37 @@ +/** + * Help module + */ + +let { AbstractModule } = require('./abstract'); +let { logger } = require('../logging'); + +class HelpModule extends AbstractModule { + constructor(commandMap) { + super( + "Help", + "Provide helpful information about other modules.", + "help" + ); + this.commandMap = commandMap; + } + + addCommand(command, module) { + logger.debug("Adding help for command %s against module %s", command, module.name); + this.commands.push(command); + } + + help() { + let help = `!help `; + for (let command of Array.from(this.commandMap.keys()).sort()) { + help += "\n!help " + command + " : " + this.commandMap.get(command).description; + } + return help; + } +} + +function create(commandMap) { + return new HelpModule(commandMap); +} + +exports.create = create; +exports.module = new HelpModule(); \ No newline at end of file diff --git a/bot/utility.js b/bot/utility.js index d1cb9a2..67ddeb9 100644 --- a/bot/utility.js +++ b/bot/utility.js @@ -1,6 +1,21 @@ let fs = require('fs'); let { logger } = require('./logging'); +function getShortestPrefix(radixTree, key, sliceSize) { + let shortKey = key.substring(0, sliceSize); + let keyCount = radixTree.countPrefix(shortKey); + if (keyCount < 1) { + return null; + } + if (key.length === sliceSize && radixTree.getPrefix(shortKey).includes(key)) { + return null; + } + if (keyCount === 1) { + return shortKey; + } + return getShortestPrefix(radixTree, key, sliceSize + 1); +} + function toISODateString(d) { function pad(n) { return n < 10 ? '0' + n : n } return d.getUTCFullYear() + '-' @@ -29,6 +44,12 @@ function sleep(ms) { }) } +function isString(s) { + return typeof(s) === 'string' || s instanceof String; +} + +exports.getShortestPrefix = getShortestPrefix; exports.toISODateString = toISODateString; exports.getBuildInfo = getBuildInfo; -exports.sleep = sleep; \ No newline at end of file +exports.sleep = sleep; +exports.isString = isString; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 16e15f0..3765bb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1177,6 +1177,11 @@ } } }, + "trie-prefix-tree": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/trie-prefix-tree/-/trie-prefix-tree-1.5.1.tgz", + "integrity": "sha512-Jjvj/dA97wXnabG/NLJUgo4IQMj6vucH+Qxm7of/omfWSmZlPqdRU6Ta4GmQqCZH+n3/iYZUwfvUoEhB0Hs83Q==" + }, "triple-beam": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", diff --git a/package.json b/package.json index d3d071f..1c09411 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "matrix-js-sdk": "2.4.5", + "trie-prefix-tree": "^1.5.1", "winston": "^3.2.1" }, "devDependencies": {