From 4a3d209e5206511a6c59f1f2d2e2148a317e9a06 Mon Sep 17 00:00:00 2001 From: Drew Short Date: Tue, 7 Jan 2020 16:18:13 -0600 Subject: [PATCH] Started work on admin module * Added config command as default * Added markdown message type with auto conversion * Added example module * Added sanitized global config loading for modules --- bot/config.js | 3 +- bot/engine.js | 26 +++++++--- bot/message.js | 18 ++++++- bot/module/abstract.js | 45 +++++++++++++++- bot/module/admin.js | 37 +++++++++++++- bot/module/example.js | 37 ++++++++++++++ bot/module/giphy.js | 8 ++- bot/module/help.js | 2 + package-lock.json | 113 +++++++++++++++++++++++++++-------------- package.json | 1 + 10 files changed, 236 insertions(+), 54 deletions(-) create mode 100644 bot/module/example.js diff --git a/bot/config.js b/bot/config.js index 3808273..2002dc5 100644 --- a/bot/config.js +++ b/bot/config.js @@ -25,4 +25,5 @@ function getConfig(configFile, sanitizedFields = [], reload = false) { } } -exports.getConfig = getConfig \ No newline at end of file +exports.getConfig = getConfig; +exports.sanitizeConfig = sanitizeConfig; \ No newline at end of file diff --git a/bot/engine.js b/bot/engine.js index 9fdf885..cbcdb83 100644 --- a/bot/engine.js +++ b/bot/engine.js @@ -6,6 +6,7 @@ let help = require('./module/help'); let message = require('./message'); let utility = require('./utility'); let { initModule } = require('./module/abstract'); +let { sanitizeConfig } = require('./config'); let commandPrefix = '!'; @@ -21,9 +22,16 @@ class Engine { } initModules() { + let sanitizedGlobalConfig = sanitizeConfig( + this.config, + [ + 'userPassword', + 'accessToken' + ]) + this.modules.forEach((mod) => { logger.info("Loading module: %s", mod.name); - initModule(mod); + initModule(mod, sanitizedGlobalConfig); logger.info("Recognized commands: %s", mod.getRecognizedCommands()) this.moduleMap.set(mod.command, mod); this.commandMap.set(mod.command, mod); @@ -118,13 +126,17 @@ class Engine { 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)); + if (responseMessage !== null) { + 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); + } } else { - logger.error("Unable to process response message: %s", promisedMessage); + logger.warn("No response message offered"); } }) } diff --git a/bot/message.js b/bot/message.js index 7a13662..01b25e0 100644 --- a/bot/message.js +++ b/bot/message.js @@ -1,3 +1,7 @@ +let showdown = require('showdown'); + +let converter = new showdown.Converter(); + let messageTypes = { TEXT: 'm.text', NOTICE: 'm.notice' @@ -7,8 +11,18 @@ function createBasicMessage(body, msgtype = messageTypes.TEXT) { return { "body": body, "msgtype": msgtype - } + }; +} + +function createMarkdownMessage(body, markdown, msgtype = messageTypes.TEXT) { + return { + "body": body, + "msgtype": msgtype, + "format": "org.matrix.custom.html", + "formatted_body": converter.makeHtml(markdown) + }; } exports.types = messageTypes; -exports.createBasic = createBasicMessage; \ No newline at end of file +exports.createBasic = createBasicMessage; +exports.createMarkdown = createMarkdownMessage; \ No newline at end of file diff --git a/bot/module/abstract.js b/bot/module/abstract.js index 38d2266..7c32d0f 100644 --- a/bot/module/abstract.js +++ b/bot/module/abstract.js @@ -5,7 +5,7 @@ let { logger } = require('../logging'); let message = require('../message'); let { isFunction, getObjectKeysToPrototype } = require('../utility'); -let { getConfig } = require('../config'); +let { getConfig, sanitizeConfig } = require('../config'); class AbstractModule { /* @@ -45,6 +45,11 @@ class AbstractModule { */ canHandleIndirectMessages = false; + /* + Indicates if the modules needs access to the global config + */ + needGlobalConfig = false; + /* Indicates if the module requires a readable config file. */ @@ -52,6 +57,11 @@ class AbstractModule { /* internal */ + /* + The global config passed in + */ + _global_config = null; + /* The loaded config file, if it exists. */ @@ -82,6 +92,20 @@ class AbstractModule { return []; } + getGlobal(key, defaultValue = null) { + if (this._global_config !== null && typeof this._global_config[key] !== 'undefined') { + return this._global_config[key]; + } + return defaultValue + } + + get(key, defaultValue = null) { + if (this._config !== null && typeof this._config[key] !== 'undefined') { + return this._config[key]; + } + return defaultValue + } + /** * Default functionality for receiving and processing a message. * @@ -141,6 +165,19 @@ class AbstractModule { cmd_help(...args) { return message.createBasic(this.helpAndUsage); } + + /** + * Return the basic config file + * + * @param {...any} args + */ + cmd_config(...args) { + if (this._config != null) { + let configBody = JSON.stringify(sanitizeConfig(this._config, this.getConfigSensitiveFields()), null, 2) + return message.createMarkdown(configBody, "```" + configBody + "```"); + } + return null; + } } let abstractModulePrototype = Object.getPrototypeOf(new AbstractModule('', '', '')); @@ -148,7 +185,7 @@ let abstractModulePrototype = Object.getPrototypeOf(new AbstractModule('', '', ' /* Initialization of a module. */ -function init(mod) { +function init(mod, globalConfig) { logger.debug("Initializing module %s", mod.name); if (mod.needConfig) { logger.debug("Loading config file %s", mod.getConfigFilePath()); @@ -159,6 +196,10 @@ function init(mod) { process.exit(1); } } + if (mod.needGlobalConfig) { + logger.debug("Bound global config to module %s", mod.name); + mod._global_config = globalConfig; + } logger.debug("Detecting command methods."); let commandMethods = getObjectKeysToPrototype(mod, abstractModulePrototype, (key) => { return key.startsWith('cmd_') && isFunction(mod[key]); diff --git a/bot/module/admin.js b/bot/module/admin.js index 7b560e9..a1b3d00 100644 --- a/bot/module/admin.js +++ b/bot/module/admin.js @@ -3,6 +3,9 @@ */ let { AbstractModule } = require('./abstract'); +let { logger } = require('../logging'); +let { sanitizeConfig } = require('../config'); +let message = require('../message'); class AdminModule extends AbstractModule { constructor() { @@ -11,8 +14,40 @@ class AdminModule extends AbstractModule { "Support administration tasks", "admin" ); + this.hidden = true; this.helpAndUsage = `Usage: admin - ...`; + admin config - print the bot configuration`; + this.needGlobalConfig = true; + } + + /** + * Override to only permit recognized admin users to access the plugin + */ + handleMessage(event, room, callback) { + if (this.getGlobal("admins", []).includes(event.sender.userId)) { + logger.debug("Authorized %s for admin action", event.sender.userId); + super.handleMessage(event, room, callback); + } else { + logger.warn("User %s tried to access admin functionality", event.sender.userId); + return event.sender.userId + " is not a recognized admin!" + } + } + + /* Commands + * All methods starting with cmd_ will be parsed at initialization to expose those methods as commdands to the user + */ + + /** + * Print the current config information + * + * @param {...any} args + */ + cmd_config(...args) { + if (this._global_config != null) { + let configBody = JSON.stringify(sanitizeConfig(this._global_config), null, 2) + return message.createMarkdown(configBody, "```" + configBody + "```"); + } + return null; } } diff --git a/bot/module/example.js b/bot/module/example.js new file mode 100644 index 0000000..6fc7a03 --- /dev/null +++ b/bot/module/example.js @@ -0,0 +1,37 @@ +/** + * Example module + * Copy this, replace the relevent parts, add it to the list in index.js + */ + +let { AbstractModule } = require('./abstract'); + +class ExampleModule extends AbstractModule { + constructor() { + super( + "Example", + "Example tasks", + "example" + ); + this.helpAndUsage = `Usage: example + ...`; + this.hidden = false; //default value + this.needGlobalConfig = false; // default value + this.needConfig = false; // default value + this.defaultCommand = 'search'; // name of the command to call when no recognized command name is present + } + + /* Commands + * All methods starting with cmd_ will be parsed at initialization to expose those methods as commdands to the user + */ + + /** + * Processed when !example blah is processed + * + * @param {...any} args the individual words passed after the command + */ + cmd_blah(...args) { + return "Bla blah bla" + } +} + +exports.module = new ExampleModule(); \ No newline at end of file diff --git a/bot/module/giphy.js b/bot/module/giphy.js index fe84d14..d8449b1 100644 --- a/bot/module/giphy.js +++ b/bot/module/giphy.js @@ -24,11 +24,15 @@ class GiphyModule extends AbstractModule { } getGiphySearch(term) { - let url = this._config.endpoint + '/gifs/search?api_key=' + this._config.apiKey + '&q=' + term + '&limit=1'; - logger.debug("Requesting: %s", url.replace(this._config.apiKey, '******')); + let url = this.get("endpoint") + '/gifs/search?api_key=' + this.get("apiKey") + '&q=' + term + '&limit=1'; + logger.debug("Requesting: %s", url.replace(this.get("apiKey"), '******')); return axios.get(url); } + /* Commands + * All methods starting with cmd_ will be parsed at initialization to expose those methods as commdands to the user + */ + /** * Return the top item for the search terms. * diff --git a/bot/module/help.js b/bot/module/help.js index 0ad96be..0dc32ba 100644 --- a/bot/module/help.js +++ b/bot/module/help.js @@ -25,6 +25,8 @@ class HelpModule extends AbstractModule { return help; } + /* Commands */ + cmd_help(...args) { logger.debug("%o", args) if (args.length < 1) { diff --git a/package-lock.json b/package-lock.json index b11c55b..91170ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -163,8 +162,7 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "caseless": { "version": "0.12.0", @@ -197,7 +195,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, "requires": { "string-width": "^3.1.0", "strip-ansi": "^5.2.0", @@ -207,14 +204,12 @@ "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, "requires": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", @@ -225,7 +220,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, "requires": { "ansi-regex": "^4.1.0" } @@ -331,8 +325,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "define-properties": { "version": "1.1.3", @@ -376,8 +369,7 @@ "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" }, "enabled": { "version": "1.0.2", @@ -467,7 +459,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, "requires": { "locate-path": "^3.0.0" } @@ -534,8 +525,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "getpass": { "version": "0.1.7", @@ -656,8 +646,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-regex": { "version": "1.0.4", @@ -756,7 +745,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -950,7 +938,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -959,7 +946,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, "requires": { "p-limit": "^2.0.0" } @@ -967,14 +953,12 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", @@ -1059,14 +1043,12 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "safe-buffer": { "version": "5.2.0", @@ -1087,8 +1069,67 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "showdown": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-1.9.1.tgz", + "integrity": "sha512-9cGuS382HcvExtf5AHk7Cb4pAeQQ+h0eTr33V1mu+crYWV4KvWAw6el92bDrqGEk5d46Ai/fhbEUwqJ/mTCNEA==", + "requires": { + "yargs": "^14.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "yargs": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.2.tgz", + "integrity": "sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA==", + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.0" + } + }, + "yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } }, "simple-swizzle": { "version": "0.2.2", @@ -1276,8 +1317,7 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "wide-align": { "version": "1.1.3", @@ -1346,7 +1386,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, "requires": { "ansi-styles": "^3.2.0", "string-width": "^3.0.0", @@ -1356,14 +1395,12 @@ "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, "requires": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", @@ -1374,7 +1411,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, "requires": { "ansi-regex": "^4.1.0" } @@ -1390,8 +1426,7 @@ "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, "yargs": { "version": "13.3.0", diff --git a/package.json b/package.json index b165106..59ff5da 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "axios": "^0.19.0", "matrix-js-sdk": "2.4.5", + "showdown": "^1.9.1", "trie-prefix-tree": "^1.5.1", "winston": "^3.2.1" },