Update 0.5.0: Fix !help, training restriction, some new functionality.

This commit is contained in:
Charlie Laabs
2018-09-21 20:40:55 -05:00
parent 455c3e0029
commit 6f3268dc07
5 changed files with 154 additions and 32 deletions

2
.gitignore vendored
View File

@@ -65,3 +65,5 @@ config.json
# error output file # error output file
error.json error.json
markovDB.json markovDB.json
.vscode/launch.json
.vscode/settings.json

View File

@@ -1,19 +1,95 @@
# MarkBot for Discord # 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 # 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 ### Setup
Create a file called `config.json` in the project directory with the contents: 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", "prefix":"!mark",
"game":"\"!mark help\" for help", "game":"\"!mark help\" for help",
"token":"k5NzE2NDg1MTIwMjc0ODQ0Nj.DSnXwg.ttNotARealToken5p3WfDoUxhiH" "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 ### 0.4.0
Huge refactor. Huge refactor.
Added `!mark debug` which sends debug info alongside the message. 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. Deleted messages no longer persist in the database longer than 24 hours.
### 0.2.0 ### 0.2.0
Updated training algorithm and data structure. 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.

View File

@@ -2,6 +2,8 @@ const Discord = require('discord.js'); // https://discord.js.org/#/docs/main/sta
const fs = require('fs'); const fs = require('fs');
const Markov = require('markov-strings'); const Markov = require('markov-strings');
const schedule = require('node-schedule'); const schedule = require('node-schedule');
const { version } = require('./package.json');
const cfg = require('./config');
const client = new Discord.Client(); const client = new Discord.Client();
// const ZEROWIDTH_SPACE = String.fromCharCode(parseInt('200B', 16)); // const ZEROWIDTH_SPACE = String.fromCharCode(parseInt('200B', 16));
@@ -85,16 +87,23 @@ function regenMarkov() {
* Loads the config settings from disk * Loads the config settings from disk
*/ */
function loadConfig() { function loadConfig() {
const cfgfile = 'config.json'; PREFIX = cfg.prefix;
if (fs.existsSync(cfgfile)) { GAME = cfg.game;
const cfg = JSON.parse(fs.readFileSync(cfgfile, 'utf8')); // regenMarkov()
PREFIX = cfg.prefix; client.login(cfg.token);
GAME = cfg.game; }
// regenMarkov()
client.login(cfg.token); /**
} else { * Checks if the author of a message as moderator-like permissions.
console.log(`Oh no!!! ${cfgfile} could not be found!`); * @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'; command = 'invite';
} else if (split[1] === 'debug') { } else if (split[1] === 'debug') {
command = 'debug'; command = 'debug';
} else if (split[1] === 'tts') {
command = 'tts';
} }
} }
return command; return command;
@@ -170,17 +181,39 @@ async function fetchMessages(message) {
* General Markov-chain response function * General Markov-chain response function
* @param {Message} message The message that invoked the action, used for channel info. * @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} 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<String>} 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...'); 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); console.log('Generated Result:', result);
const messageOpts = { tts: message.tts }; const messageOpts = { tts };
const randomMessage = markovDB[Math.floor(Math.random() * markovDB.length)]; const attachmentRefs = result.refs.filter(ref => Object.prototype.hasOwnProperty.call(ref, 'attachment'));
console.log('Random Message:', randomMessage); if (attachmentRefs.length > 0) {
if (Object.prototype.hasOwnProperty.call(randomMessage, 'attachment')) { const randomRef = attachmentRefs[Math.floor(Math.random() * attachmentRefs.length)];
messageOpts.files = [{ attachment: randomMessage.attachment }]; 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); message.channel.send(result.string, messageOpts);
if (debug) message.channel.send(`\`\`\`\n${JSON.stringify(result, null, 2)}\n\`\`\``); if (debug) message.channel.send(`\`\`\`\n${JSON.stringify(result, null, 2)}\n\`\`\``);
}).catch((err) => { }).catch((err) => {
@@ -213,7 +246,6 @@ client.on('message', (message) => {
if (message.guild) { if (message.guild) {
const command = validateMessage(message); const command = validateMessage(message);
if (command === 'help') { if (command === 'help') {
console.log(message.channel);
const richem = new Discord.RichEmbed() const richem = new Discord.RichEmbed()
.setAuthor(client.user.username, client.user.avatarURL) .setAuthor(client.user.username, client.user.avatarURL)
.setThumbnail(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.') + '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 ' .addField('!mark invite', 'Don\'t invite this bot to other servers. The database is shared '
+ 'between all servers and text channels.') + '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.channel.send(richem).catch(() => {
message.author.send(richem); message.author.send(richem);
}); });
} }
if (command === 'train') { if (command === 'train') {
console.log('Training...'); if (isModerator(message)) {
fileObj = { console.log('Training...');
messages: [], fileObj = {
}; messages: [],
fs.writeFileSync('markovDB.json', JSON.stringify(fileObj), 'utf-8'); };
fetchMessages(message); fs.writeFileSync('markovDB.json', JSON.stringify(fileObj), 'utf-8');
fetchMessages(message);
}
} }
if (command === 'respond') { if (command === 'respond') {
generateResponse(message); generateResponse(message);
} }
if (command === 'tts') {
generateResponse(message, false, true);
}
if (command === 'debug') { if (command === 'debug') {
generateResponse(message, true); generateResponse(message, true);
} }
@@ -260,6 +298,9 @@ client.on('message', (message) => {
dbObj.attachment = message.attachments.values().next().value.url; dbObj.attachment = message.attachments.values().next().value.url;
} }
messageCache.push(dbObj); messageCache.push(dbObj);
if (message.isMentioned(client.user)) {
generateResponse(message);
}
} }
} }
if (command === inviteCmd) { if (command === inviteCmd) {

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "markbot", "name": "markbot",
"version": "0.4.0", "version": "0.5.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "markbot", "name": "markbot",
"version": "0.4.0", "version": "0.5.0",
"description": "A conversational Markov chain bot for Discord", "description": "A conversational Markov chain bot for Discord",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {