const Discord = require('discord.js') //https://discord.js.org/#/docs/main/stable/general/welcome const fs = require('fs') const Markov = require('markov-strings') const uniqueBy = require('unique-by'); const schedule = require('node-schedule'); const client = new Discord.Client() const ZEROWIDTH_SPACE = String.fromCharCode(parseInt('200B', 16)) const MAXMESSAGELENGTH = 2000 let guilds = [] let connected = -1 let GAME = 'GAME' let BOTDESC = 'Amazing.' let PREFIX = '! ' let VOLUME let inviteCmd = 'invite' let commands = {} let aliases = {} let errors = [] let fileObj = { messages: [] } let markovDB = [] let messageCache = [] const markovOpts = { maxLength: 400, minWords: 3, minScore: 10 } let markov = new Markov(markovDB, markovOpts); function regenMarkov() { console.log("Regenerating Markov corpus...") try { fileObj = JSON.parse(fs.readFileSync('markovDB.json', 'utf8')) } catch (err) { console.log(err) } // console.log("MessageCache", messageCache) markovDB = fileObj.messages if (markovDB.length == 0) markovDB.push({ string: 'hello', id: null }) //markovDB = uniqueArray(markovDB.concat(messageCache), 'id') markovDB = uniqueBy(markovDB.concat(messageCache), 'id') //markovDB = markovDB.concat(messageCache) markov = new Markov(markovDB, markovOpts); markov.buildCorpusSync() fileObj.messages = markovDB // console.log("WRITING THE FOLLOWING DATA:") // console.log(fileObj) fs.writeFileSync('markovDB.json', JSON.stringify(fileObj), 'utf-8') fileObj = null; markovDB = [] messageCache = [] console.log("Done regenerating Markov corpus.") } function loadConfig() { let cfgfile = 'config.json' if (fs.existsSync(cfgfile)) { let cfg = JSON.parse(fs.readFileSync(cfgfile, 'utf8')) PREFIX = cfg.prefix GAME = cfg.game BOTDESC = cfg.description inviteCmd = cfg.invitecmd //regenMarkov() client.login(cfg.token) } else { console.log('Oh no!!! ' + cfgfile + ' could not be found!') } } client.on('ready', () => { console.log('Markbot by Charlie Laabs') client.user.setActivity(GAME) }) client.on('error', (err) => { let errText = 'ERROR: ' + err.name + ' - ' + err.message console.log(errText) errors.push(errText) fs.writeFile('error.json', JSON.stringify(errors), function (err) { if (err) console.log('error writing to error file: ' + err.message) }) }) client.on('message', message => { if (message.guild) { let command = validateMessage(message) if (command === 'help') { let richem = new Discord.RichEmbed() .setAuthor(client.user.username, client.user.avatarURL) .setThumbnail(client.user.avatarURL) .setDescription('A Markov chain chatbot that speaks based on previous chat input.') .addField('!mark', 'Generates a sentence to say based on the chat database') .addField('!mark train', 'Fetches the maximum amount of previous messages in the current text channel, adds it to the database, and regenerates the corpus. Takes about 2 minutes.') .addField('!mark regen', 'Manually regenerates the corpus to add recent chat info. Run 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.') message.channel.send(richem) .catch(reason => { message.author.send(richem) }) } if (command === 'train') { console.log("Training...") fetchMessageChunk(message, null, []) } if (command === 'respond') { console.log("Responding...") markov.generateSentence().then(result => { console.log(result) message.channel.send(result.string) }).catch(err => { console.log(err) if (err.message == 'Cannot build sentence with current corpus and options') // message.reply('Not enough chat data for a response. Run `!mark train` to process past messages.') console.log('Not enough chat data for a response.') }) } if (command === 'regen') { console.log("Regenerating...") regenMarkov() } if (command === null) { console.log("Listening...") if (!message.author.bot) { messageCache.push({ string: message.content, id: message.id }) } } if (command === inviteCmd) { let richem = new Discord.RichEmbed() .setAuthor('Invite ' + client.user.username, client.user.avatarURL) .setThumbnail(client.user.avatarURL) .addField('Invite', "[Invite " + client.user.username + " to your server](https://discordapp.com/oauth2/authorize?client_id=" + client.user.id + "&scope=bot)") message.channel.send(richem) .catch(reason => { message.author.send(richem) }) } } }) function validateMessage(message) { let messageText = message.content.toLowerCase() let command = null; let thisPrefix = messageText.substring(0, PREFIX.length) if (thisPrefix == PREFIX) { let split = messageText.split(" ") if (split[0] == PREFIX && split.length == 1) command = 'respond' else if (split[1] == 'train') command = 'train' else if (split[1] == 'help') command = 'help' else if (split[1] == 'regen') command = 'regen' else if (split[1] == 'invite') command = 'invite' } return command } function fetchMessageChunk(message, oldestMessageID, historyCache) { message.channel.fetchMessages({ before: oldestMessageID, limit: 100 }) .then(messages => { historyCache = historyCache.concat(messages.filter(elem => !elem.author.bot).map(elem => { return { string: elem.content, id: elem.id } })); oldestMessageID = messages.last().id return historyCache.concat(fetchMessageChunk(message, oldestMessageID, historyCache)) }).catch(err => { console.log("Trained from " + historyCache.length + " past messages.") messageCache = messageCache.concat(historyCache) regenMarkov() message.reply('Finished training from past ' + historyCache.length + ' messages.') }); } loadConfig() const daily = schedule.scheduleJob('0 0 * * *', regenMarkov());