From 6f3268dc077c2c0cb032c9073f3d23c61111ea50 Mon Sep 17 00:00:00 2001 From: Charlie Laabs Date: Fri, 21 Sep 2018 20:40:55 -0500 Subject: [PATCH] Update 0.5.0: Fix !help, training restriction, some new functionality. --- .gitignore | 2 ++ README.md | 89 ++++++++++++++++++++++++++++++++++++++++++--- index.js | 91 ++++++++++++++++++++++++++++++++++------------- package-lock.json | 2 +- package.json | 2 +- 5 files changed, 154 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index ce66808..fcceb43 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,5 @@ config.json # error output file error.json markovDB.json +.vscode/launch.json +.vscode/settings.json diff --git a/README.md b/README.md index cd66335..8c23c98 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,95 @@ # MarkBot for Discord -This is a super rough prototype. A Markov chain bot using markov-strings. Just uploading here so it can run and generate training data. +A Markov chain bot using markov-strings. # Setup +First, create a [Discord bot application](https://discordapp.com/developers/applications/). +## Windows +### Requirements +* [Node.js 8.0+ (Current)](https://nodejs.org/en/download/current/) -## Configuration -Create a file called `config.json` in the project directory with the contents: +### Setup +1. Install Node.js 8.0 or newer. +1. Download this repository using git in a command prompt + ```cmd + git clone https://github.com/charlocharlie/markov-discord.git + ``` + or by just downloading and extracting the [project zip](https://github.com/charlocharlie/markov-discord/archive/master.zip) from GitHub. +1. Open a command prompt in the `markov-discord` folder. + ```sh + # Install Windows build tools + npm install --global --production windows-build-tools + # NPM install non-development packages + npm install --production + ``` +1. Create a file called `config.json` in the project directory with the contents: + ```json + { + "prefix":"!mark", + "game":"\"!mark help\" for help", + "token":"k5NzE2NDg1MTIwMjc0ODQ0Nj.DSnXwg.ttNotARealToken5p3WfDoUxhiH" + } + ``` + Feel free to change the command prefix, game display. Add your bot token. +1. Run the bot: + ```sh + npm start + ``` + + +## Debian Linux +### Requirements +* Node.js 8.0+ +* Python 2.7 (for erlpack) +* C++ build tools (for erlpack) + +### Download +```sh +# Clone this repository +git clone https://github.com/charlocharlie/markov-discord.git +cd markov-discord ``` + +### Configure +Create a file called `config.json` in the project directory with the contents: +```json { "prefix":"!mark", "game":"\"!mark help\" for help", "token":"k5NzE2NDg1MTIwMjc0ODQ0Nj.DSnXwg.ttNotARealToken5p3WfDoUxhiH" } ``` +Feel free to change the command prefix, game display. Add your bot token. + +### Install and Run +```sh +# Install Node.js if you haven't already +wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash +nvm install node + +# NPM install non-development packages +npm install --production + +# If you run into build errors, install the following packages: +sudo apt-get install python -y +sudo apt-get install build-essential -y + +# Start the program +npm start +``` + + + +# Changelog +### 0.5.0 +Fixed bug where `!mark help` didn't work. +Only admins can train. +The bot responds when mentioned. +The bot cannot mention @everyone. +Added version number to help. +Added `!mark tts` for a quieter TTS response. +Readme overhaul. +Simpler config loading. -## Changelog ### 0.4.0 Huge refactor. Added `!mark debug` which sends debug info alongside the message. @@ -27,4 +103,7 @@ Added TTS support and random message attachments. Deleted messages no longer persist in the database longer than 24 hours. ### 0.2.0 -Updated training algorithm and data structure. \ No newline at end of file +Updated training algorithm and data structure. + +# Thanks +Thanks to [BotMaker-for-Discord](https://github.com/CorySanin/BotMaker-for-Discord) which I used as a reference when during development. \ No newline at end of file diff --git a/index.js b/index.js index 50d468d..aaf20cd 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,8 @@ const Discord = require('discord.js'); // https://discord.js.org/#/docs/main/sta const fs = require('fs'); const Markov = require('markov-strings'); const schedule = require('node-schedule'); +const { version } = require('./package.json'); +const cfg = require('./config'); const client = new Discord.Client(); // const ZEROWIDTH_SPACE = String.fromCharCode(parseInt('200B', 16)); @@ -85,16 +87,23 @@ function regenMarkov() { * Loads the config settings from disk */ function loadConfig() { - const cfgfile = 'config.json'; - if (fs.existsSync(cfgfile)) { - const cfg = JSON.parse(fs.readFileSync(cfgfile, 'utf8')); - PREFIX = cfg.prefix; - GAME = cfg.game; - // regenMarkov() - client.login(cfg.token); - } else { - console.log(`Oh no!!! ${cfgfile} could not be found!`); - } + PREFIX = cfg.prefix; + GAME = cfg.game; + // regenMarkov() + client.login(cfg.token); +} + +/** + * Checks if the author of a message as moderator-like permissions. + * @param {Message} message Message object to get the sender of the message. + * @return {Boolean} True if the sender is a moderator. + */ +function isModerator(message) { + const { member } = message; + return member.hasPermission('ADMINISTRATOR') + || member.hasPermission('MANAGE_CHANNELS') + || member.hasPermission('KICK_MEMBERS') + || member.hasPermission('MOVE_MEMBERS'); } /** @@ -120,6 +129,8 @@ function validateMessage(message) { command = 'invite'; } else if (split[1] === 'debug') { command = 'debug'; + } else if (split[1] === 'tts') { + command = 'tts'; } } return command; @@ -170,17 +181,39 @@ async function fetchMessages(message) { * General Markov-chain response function * @param {Message} message The message that invoked the action, used for channel info. * @param {Boolean} debug Sends debug info as a message if true. + * @param {Boolean} tts If the message should be sent as TTS. Defaults to the TTS setting of the + * invoking message. + * @param {Array} filterWords Array of words that the message generated will be filtered on. */ -function generateResponse(message, debug = false) { +function generateResponse(message, debug = false, tts = message.tts, filterWords) { console.log('Responding...'); - markov.generateSentence().then((result) => { + const options = {}; + if (filterWords) { + options.filter = (result) => { + for (let i = 0; i < filterWords.length; i++) { + if (result.string.includes(filterWords[i])) { + return true; + } + } + return false; + }; + options.maxTries = 5000; + } + markov.generateSentence(options).then((result) => { console.log('Generated Result:', result); - const messageOpts = { tts: message.tts }; - const randomMessage = markovDB[Math.floor(Math.random() * markovDB.length)]; - console.log('Random Message:', randomMessage); - if (Object.prototype.hasOwnProperty.call(randomMessage, 'attachment')) { - messageOpts.files = [{ attachment: randomMessage.attachment }]; + const messageOpts = { tts }; + const attachmentRefs = result.refs.filter(ref => Object.prototype.hasOwnProperty.call(ref, 'attachment')); + if (attachmentRefs.length > 0) { + const randomRef = attachmentRefs[Math.floor(Math.random() * attachmentRefs.length)]; + messageOpts.files = [{ attachment: randomRef.attachment }]; + } else { + const randomMessage = markovDB[Math.floor(Math.random() * markovDB.length)]; + if (Object.prototype.hasOwnProperty.call(randomMessage, 'attachment')) { + messageOpts.files = [{ attachment: randomMessage.attachment }]; + } } + + result.string.replace(/@everyone/g, '@everyοne'); // Replace @everyone with a homoglyph 'o' message.channel.send(result.string, messageOpts); if (debug) message.channel.send(`\`\`\`\n${JSON.stringify(result, null, 2)}\n\`\`\``); }).catch((err) => { @@ -213,7 +246,6 @@ client.on('message', (message) => { if (message.guild) { const command = validateMessage(message); if (command === 'help') { - console.log(message.channel); const richem = new Discord.RichEmbed() .setAuthor(client.user.username, client.user.avatarURL) .setThumbnail(client.user.avatarURL) @@ -226,22 +258,28 @@ client.on('message', (message) => { + 'this before shutting down to avoid any data loss. This automatically runs at midnight.') .addField('!mark invite', 'Don\'t invite this bot to other servers. The database is shared ' + 'between all servers and text channels.') - .addBlankField('!mark debug', 'Runs the !mark command and follows it up with debug info.'); + .addField('!mark debug', 'Runs the !mark command and follows it up with debug info.') + .setFooter(`Markov Discord v${version} by Charlie Laabs`); message.channel.send(richem).catch(() => { message.author.send(richem); }); } if (command === 'train') { - console.log('Training...'); - fileObj = { - messages: [], - }; - fs.writeFileSync('markovDB.json', JSON.stringify(fileObj), 'utf-8'); - fetchMessages(message); + if (isModerator(message)) { + console.log('Training...'); + fileObj = { + messages: [], + }; + fs.writeFileSync('markovDB.json', JSON.stringify(fileObj), 'utf-8'); + fetchMessages(message); + } } if (command === 'respond') { generateResponse(message); } + if (command === 'tts') { + generateResponse(message, false, true); + } if (command === 'debug') { generateResponse(message, true); } @@ -260,6 +298,9 @@ client.on('message', (message) => { dbObj.attachment = message.attachments.values().next().value.url; } messageCache.push(dbObj); + if (message.isMentioned(client.user)) { + generateResponse(message); + } } } if (command === inviteCmd) { diff --git a/package-lock.json b/package-lock.json index 98807e9..6840f3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "markbot", - "version": "0.4.0", + "version": "0.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3e5418f..9ffa6c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "markbot", - "version": "0.4.0", + "version": "0.5.0", "description": "A conversational Markov chain bot for Discord", "main": "index.js", "scripts": {