diff --git a/README.md b/README.md new file mode 100644 index 0000000..7383937 --- /dev/null +++ b/README.md @@ -0,0 +1,118 @@ +# Depressionbot IRC Bot + +## Introduction + +Depressionbot is an IRC bot which aims to be the fanciest IRC bot around - On +the general standard of software fanciness, dbot is rated as being '75% the same +as bathing in fine, fine grape juice.' + +Please note that this documentation is not complete and is a work in progress, +given I started it rather a long time after I began development of the project. +Please don't judge me too harshly for this as I am, in fact, mildly allergic to +writing documentation. + +Requirements: + +- Node JS +- [JSBot](http://github.com/reality/JSBot "JSBot"), the Javascript library I + wrote to handle the IRC protocol and event listeners etc. +- Various modules have their own requirements also. + +## Modules: + +### Command + +This handles the command execution logic for DBot. + +1. Does the input match a command key in *dbot.commands* ? +2. Is there a quote category which matches the first part of the input + (*~category*)? +3. Is there a command name similar to to the first part of the input (*~name*) + in *dbot.commands*? + +This is the only module which is force loaded, even if it's not in the +configuration. + +### Quotes + +This is the original reason that DBot was created, stores and displays quotes. + +Commands: + +- _~q category_ - Display a random quote from a given category. +- _~qadd category=newquote_ - Add a new quote to the database. +- _~qstats_ - Show a list of the biggest quote categories. +- _~qsearch category needle_ - Search for a quote in a given category. +- _~rmlast [category]_ - Remove the last quote added to a given category, or the + last quote added. +- _~rm category quote_ - Remove a given quote from the given category. +- _~qcount category_ - Show the number of quotes stored in the given category. +- _~rq_ - Show a random quote from a random category. +- _~d_ - Show a quote from the category which matches the bot's name. +- _~link category_ - Create a link to the page on the web interface which displays the + given category's quotes. +- _~qprune_ - Delete empty quote categories. + +Unfortunately, this module is fairly highly coupled with certain other areas of +the program. I am working on this, but note, for example, that one can still +access quotes with the *~category* syntax even if the quotes module isn't +loaded. + +### Admin + +Various administration functionality such as banning users, hot-reloading the +code and ordering him to talk. Note that commands added here are handled with +their own listener, rather than being part of the command logic which is handled +by the Command module. Functionality in this module can be slightly unsafe as +not much error checking on the input is performed. + +TODO: Add summaries for each command in this module. + +### Spelling + +Will attempt to correct a users' spelling by using the levenshtein distance +algorithm. One corrects the spelling of their previous message by simply posting +a message with their correction and an asterisk: + + > user: I am a tutrle. + > user: *turtle + user meant: I am a turtle. + +The regular expression for this module also accepts two asterisks at the +beginning of the correction, or at the end; it also accepts several words as the +correction and deals with these fairly intelligently. Users may also attempt +to correct another users like so: + + > userone: I am a tutrle. + > usertwo: userone: *turtle + > usertwo thinks userone meant: I am a turtle. + +### JS + +This module provides two commands which allow the execution of Javascript code. +For regular users, there is the *~js* command, which is completely sandboxed, +but can still be used for calculation and the like. + + > ~js Array(16).join('wat'-1) + " Batman!"; + 'NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman!' + +This feature is fairly safe as the user doesn't have access to anything +dangerous, and is safe from infinite loops or locking DBot up because the code +which is run is killed if it does not finish within a short amount of time. + +For administrators, the incredibly useful *~ajs* command is also available. The +input for this command is simply 'eval'-ed and therefore has full access to +DBot's memory. Of course, this is incredibly unsafe, but I find it rather fun; +remember to only give extremely trusted friends administrator access to DBot, as +there's nothing to stop them wiping the database or something similar - if +you're worried about that kind of thing, do not load this module. + +However, it's useful for many things, such as administrative activity for +which there isn't a command in the admin module. For example, you could hot-add +a new administrator like this: + + > ~ajs dbot.admin.push('batman'); + 2 + +You can also use this for debugging, or even adding new commands while DBot is +running. diff --git a/modules/admin.js b/modules/admin.js index d1dfc26..678e707 100644 --- a/modules/admin.js +++ b/modules/admin.js @@ -1,4 +1,6 @@ var fs = require('fs'); +var sys = require('sys') +var exec = require('child_process').exec; var adminCommands = function(dbot) { var dbot = dbot; @@ -6,17 +8,28 @@ var adminCommands = function(dbot) { var commands = { 'join': function(data, params) { dbot.instance.join(params[1]); - dbot.say(dbot.admin, 'Joined ' + params[1]); + dbot.say(data.channel, 'Joined ' + params[1]); }, 'opme': function(data, params) { - dbot.instance.send('MODE ' + params[1] + ' +o ', dbot.admin); + dbot.instance.send('MODE ' + params[1] + ' +o ', data.user); }, 'part': function(data, params) { dbot.instance.part(params[1]); }, + // Do a git pull and reload + 'greload': function(data, params) { + var child; + + child = exec("git pull", function (error, stdout, stderr) { + console.log(stderr); + dbot.say(data.channel, 'Git pulled that shit.'); + commands.reload(data, params); + }.bind(this)); + }, + 'reload': function(data, params) { dbot.db = JSON.parse(fs.readFileSync('db.json', 'utf-8')); dbot.reloadModules(); @@ -24,11 +37,25 @@ var adminCommands = function(dbot) { }, 'say': function(data, params) { - var c = params[1]; + if (params[1] === "@") { + var c = data.channel; + } else { + var c = params[1]; + } var m = params.slice(2).join(' '); dbot.say(c, m); }, + 'act': function(data, params) { + if (params[1] === "@") { + var c = data.channel; + } else { + var c = params[1]; + } + var m = params.slice(2).join(' '); + dbot.act(c, m); + }, + 'load': function(data, params) { dbot.moduleNames.push(params[1]); dbot.reloadModules(); @@ -92,7 +119,7 @@ var adminCommands = function(dbot) { if(data.channel == dbot.name) data.channel = data.user; params = data.message.split(' '); - if(commands.hasOwnProperty(params[0]) && data.user == dbot.admin) { + if(commands.hasOwnProperty(params[0]) && dbot.admin.include(data.user)) { commands[params[0]](data, params); dbot.save(); } diff --git a/modules/command.js b/modules/command.js new file mode 100644 index 0000000..6f7f289 --- /dev/null +++ b/modules/command.js @@ -0,0 +1,105 @@ +// Module which handles the command execution syntax for DBot. Not much is going +// to work without this. +var command = function(dbot) { + var dbot = dbot; + + var ignoreCommands = function (data, params) { + if(data.channel == dbot.name) data.channel = data.user; + var targetCommand = params[1]; + var ignoreMins = parseFloat(params[2]); + + if(!dbot.sessionData.hasOwnProperty("ignoreCommands")) { + dbot.sessionData.ignoreCommands = {}; + } + if(!dbot.sessionData.ignoreCommands.hasOwnProperty(targetCommand)) { + dbot.sessionData.ignoreCommands[targetCommand] = []; + } + + if(dbot.sessionData.ignoreCommands[targetCommand].include(data.channel)) { + dbot.say(data.channel, "Already ignoring '" + targetCommand + "' in '" + data.channel + "'."); + } else { + dbot.sessionData.ignoreCommands[targetCommand].push(data.channel); + dbot.timers.addOnceTimer(ignoreMins * 60 * 1000, function() { + dbot.sessionData.ignoreCommands[targetCommand].splice(dbot.sessionData.ignoreCommands[targetCommand].indexOf(data.channel), 1); + dbot.say(data.channel, "No longer ignoring '" + targetCommand + "' in '" + data.channel + "'."); + }); + dbot.say(data.channel, "Ignoring '" + targetCommand + "' in '" + data.channel + "' for the next " + ignoreMins + " minute" + (ignoreMins == 1 ? "" : "s") + "."); + } + }; + + return { + 'onLoad': function() { + return { + '~ignore': ignoreCommands + }; + }, + 'listener': function(data) { + var params = data.message.split(' '); + if(data.channel == dbot.name) data.channel = data.user; + + var ignoringCommand = false; + if(dbot.sessionData.hasOwnProperty("ignoreCommands")) { + if(dbot.sessionData.ignoreCommands.hasOwnProperty(params[0])) { + if(dbot.sessionData.ignoreCommands[params[0]].include(data.channel)) { + ignoringCommand = true; + } + } + } + + if(dbot.commands.hasOwnProperty(params[0])) { + if((dbot.db.bans.hasOwnProperty(params[0]) && + dbot.db.bans[params[0]].include(data.user)) || dbot.db.bans['*'].include(data.user)) { + dbot.say(data.channel, data.user + + ' is banned from using this command. Commence incineration.'); + } else if(ignoringCommand) { + // do nothing, this stops us falling through to the non-command stuff + } else { + dbot.commands[params[0]](data, params); + dbot.save(); + } + } else { + var q = data.message.valMatch(/^~([\d\w\s-]*)/, 2); + if(q) { + if(dbot.db.bans['*'].include(data.user)) { + dbot.say(data.channel, data.user + + ' is banned from using this command. Commence incineration.'); + } else { + q[1] = q[1].trim(); + key = dbot.cleanNick(q[1]) + if(dbot.db.quoteArrs.hasOwnProperty(key) && dbot.moduleNames.include('quotes')) { + var params = ['~q']; + key.split(' ').each((function(word) { + this.push(word); + }).bind(params)); + data.message = params.join(' '); + dbot.commands[params[0]](data, params); + dbot.save(); + } else { + // See if it's similar to anything + var winnerDistance = Infinity; + var winner = false; + for(var commandName in dbot.commands) { + var distance = String.prototype.distance(params[0], commandName); + if(distance < winnerDistance) { + winner = commandName; + winnerDistance = distance; + } + } + + if(winnerDistance < 3) { + dbot.say(data.channel, 'Did you mean ' + winner + '? Learn to type, hippie!'); + } + } + } + } + } + }, + + 'on': 'PRIVMSG' + }; +}; + +exports.fetch = function(dbot) { + return command(dbot); +}; + diff --git a/modules/dice.js b/modules/dice.js new file mode 100644 index 0000000..f29c2f9 --- /dev/null +++ b/modules/dice.js @@ -0,0 +1,97 @@ +var parseDiceSpec = function (specString) { + var rawSpec = specString.valMatch(/^([0-9]*)d(%|[0-9]*)(|[+-][0-9]+)$/i, 4); + if (rawSpec !== false) { + if (rawSpec[2] === "%") { + rawSpec[2] = 100; + } + return { + "count": parseInt(rawSpec[1] || 1), + "sides": parseInt(rawSpec[2] || 6), + "modifier": parseInt(rawSpec[3] || 0) + }; + } else { + return false; + } +}; + +var normalizeDiceSpec = function (specString) { + var diceSpec = parseDiceSpec(specString); + + if (diceSpec["count"] > 1) { + var count = diceSpec["count"]; + } else { + var count = ""; + } + + if (diceSpec["sides"] === 100) { + var sides = "%"; + } else { + var sides = diceSpec["sides"]; + } + + if (diceSpec["modifier"] > 0) { + var modifier = "+" + diceSpec["modifier"]; + } else if (diceSpec["modifier"] < 0) { + var modifier = diceSpec["modifier"]; + } else { + var modifier = ""; + } + + return (count + "d" + sides + modifier); +}; + +var dice = function(dbot) { + var commands = { + '~roll': function (data, params) { + var rolls = []; + + if (params.length === 1) { + params.push("d6"); + } + + for (var i = 1; i < params.length; i++) { + var diceSpec = parseDiceSpec(params[i]); + if (diceSpec === false) { + rolls.push([params[i], false]); + } else { + rolls.push([normalizeDiceSpec(params[i]), [], diceSpec["modifier"]]); + for (var j = 0; j < diceSpec["count"] ; j++) { + rolls[rolls.length-1][1].push(Math.ceil(Math.random() * diceSpec["sides"])); + } + } + } + + for (var i = 0; i < rolls.length; i++) { + if (rolls[i][1] === false) { + dbot.say(data.channel, rolls[i][0] + ": invalid dice spec"); + } else { + if (rolls[i][1].length > 1) { + var total = " (total " + rolls[i][1].sum(); + if (rolls[i][2] != 0) { + if (rolls[i][2] > 0) { + total += " + "; + } else { + total += " - "; + } + total += Math.abs(rolls[i][2]) + " -> " + (rolls[i][1].sum() + rolls[i][2]); + } + total += ")" + } else { + var total = ""; + } + dbot.say(data.channel, rolls[i][0] + ": " + rolls[i][1].join(" ") + total); + } + } + } + }; + + return { + 'onLoad': function() { + return commands; + } + }; +} + +exports.fetch = function(dbot) { + return dice(dbot); +}; diff --git a/modules/drama.js b/modules/drama.js index c7d3daa..d68cca8 100644 --- a/modules/drama.js +++ b/modules/drama.js @@ -26,7 +26,7 @@ var drama = function(dbot) { var commands = { '~train': function(data, params) { - if(data.user == dbot.admin || data.user == 'golem' || data.user == 'Sam') { + if(dbot.admin.include(data.user)) { bayes.train(last[params[1]][params[2]], params[3]); dbot.say(data.channel, 'Last thing ' + params[2] + ' said in ' + params[1] + ' (' + last[params[1]][params[2]] + ') classified as \'' + params[3] + '\''); @@ -34,7 +34,7 @@ var drama = function(dbot) { }, '~rtrain': function(data, params) { - if(data.user == dbot.admin || data.user == 'golem' || data.user == 'Sam') { + if(dbot.admin.include(data.user)) { var category = params[1]; params.splice(0, 2); var msg = params.join(' '); diff --git a/modules/js.js b/modules/js.js index 9547c85..cf08699 100644 --- a/modules/js.js +++ b/modules/js.js @@ -15,8 +15,11 @@ var js = function(dbot) { '~ajs': function(data, params) { var q = data.message.valMatch(/^~ajs (.*)/, 2); - if(data.user == dbot.admin) { - dbot.say(data.channel, eval(q[1])); + if(dbot.admin.include(data.user) ) { + var ret = eval(q[1]); + if(ret != undefined) { + dbot.say(data.channel, ret); + } } } }; diff --git a/modules/modehate.js b/modules/modehate.js index 813a6ae..b769198 100644 --- a/modules/modehate.js +++ b/modules/modehate.js @@ -13,8 +13,6 @@ var modehate = function(dbot) { 'on': 'MODE' }; }; -~ajs dbot.instance.addListener('MODE', function(data) { if(data.channel == '#42' && data.raw[0].indexOf('Snow') != -1 && data.raw[0].indexOf('+o') != -1) { dbot.instance.send('MODE #42 -o Snow'); } } - exports.fetch = function(dbot) { return modehate(dbot); diff --git a/modules/puns.js b/modules/puns.js index 1cc2661..6f5357d 100644 --- a/modules/puns.js +++ b/modules/puns.js @@ -3,13 +3,12 @@ var puns = function(dbot) { return { 'listener': function(data) { - if(data.user == 'reality') { - dbot.instance.say(data.channel, dbot.db.quoteArrs['realityonce'].random()); - } else if(dbot.db.quoteArrs.hasOwnProperty(data.user.toLowerCase())) { - dbot.say(data.channel, data.user + ': ' + dbot.db.quoteArrs[data.user.toLowerCase()].random()); - } else if(dbot.instance.inChannel(data.channel)) { - dbot.instance.say('aisbot', '.karma ' + data.user); - dbot.waitingForKarma = data.channel; + if(dbot.moduleNames.include('quotes')) { + if(dbot.db.quoteArrs.hasOwnProperty(data.user.toLowerCase())) { + data.message = '~q ' + data.user.toLowerCase(); + var params = data.message.split(' '); + dbot.commands[params[0]](data, params); + } } }, diff --git a/modules/quotes.js b/modules/quotes.js index 546f5e3..c8029d4 100644 --- a/modules/quotes.js +++ b/modules/quotes.js @@ -2,7 +2,48 @@ var quotes = function(dbot) { var quotes = dbot.db.quoteArrs; var addStack = []; var rmAllowed = true; - + + // Retrieve a random quote from a given category, interpolating any quote references (~~QUOTE CATEGORY~~) within it + var interpolatedQuote = function(key, quoteTree) { + if(quoteTree !== undefined && quoteTree.indexOf(key) != -1) { + return ''; + } else if(quoteTree === undefined) { + quoteTree = []; + } + + var quoteString = quotes[key].random(); + + // Parse quote interpolations + var quoteRefs = quoteString.match(/~~([\d\w\s-]*)~~/g); + var thisRef; + + while(quoteRefs && (thisRef = quoteRefs.shift()) !== undefined) { + var cleanRef = dbot.cleanNick(thisRef.replace(/^~~/,'').replace(/~~$/,'').trim()); + if (quotes.hasOwnProperty(cleanRef)) { + quoteTree.push(key); + quoteString = quoteString.replace("~~" + cleanRef + "~~", + interpolatedQuote(cleanRef, quoteTree.slice())); + quoteTree.pop(); + } + } + + // Parse quote parameters + /* + var paramRefs = quoteString.match(/~~\$([1-9])~~/g); + var thisParam; + + while(paramRefs && (thisParam = paramRefs.shift()) !== undefined) { + thisParam = thisParam[1]; + console.log(thisParam); + if(thisParam < params.length) { + quoteString = quoteString.replace("~~$" + thisParam + "~~", params[thisParam]); + } + } + */ + + return quoteString; + }; + var commands = { '~q': function(data, params) { var q = data.message.valMatch(/^~q ([\d\w\s-]*)/, 2); @@ -10,7 +51,7 @@ var quotes = function(dbot) { q[1] = q[1].trim(); key = q[1].toLowerCase(); if(quotes.hasOwnProperty(key)) { - dbot.say(data.channel, q[1] + ': ' + quotes[key].random()); + dbot.say(data.channel, q[1] + ': ' + interpolatedQuote(key)); } else { dbot.say(data.channel, 'Nobody loves ' + q[1]); } @@ -65,13 +106,13 @@ var quotes = function(dbot) { }, '~rmlast': function(data, params) { - if(rmAllowed == true || data.user == dbot.admin) { + if(rmAllowed == true || dbot.admin.include(data.user)) { var q = data.message.valMatch(/^~rmlast ([\d\w\s-]*)/, 2); if(q) { q[1] = q[1].trim() key = q[1].toLowerCase(); if(quotes.hasOwnProperty(q[1])) { - if(!dbot.db.locks.include(q[1]) || data.user == dbot.admin) { + if(!dbot.db.locks.include(q[1]) || dbot.admin.include(data.user)) { var quote = quotes[key].pop(); if(quotes[key].length === 0) { delete quotes[key]; @@ -104,7 +145,7 @@ var quotes = function(dbot) { }, '~rm': function(data, params) { - if(rmAllowed == true || data.user == dbot.admin) { + if(rmAllowed == true || dbot.admin.include(data.user)) { var q = data.message.valMatch(/^~rm ([\d\w\s-]*) (.+)$/, 3); if(q) { if(quotes.hasOwnProperty(q[1])) { @@ -112,6 +153,9 @@ var quotes = function(dbot) { var index = quotes[q[1]].indexOf(q[2]); if(index != -1) { quotes[q[1]].splice(index, 1); + if(quotes[q[1]].length === 0) { + delete quotes[q[1]]; + } rmAllowed = false; dbot.say(data.channel, '\'' + q[2] + '\' removed from ' + q[1]); } else { @@ -144,7 +188,6 @@ var quotes = function(dbot) { } else { // Give total quote count var totalQuoteCount = 0; for(var category in quotes) { - console.log('adding ' + category.length); totalQuoteCount += category.length; } dbot.say(data.channel, 'There are ' + totalQuoteCount + ' quotes in total.'); @@ -152,7 +195,7 @@ var quotes = function(dbot) { }, '~qadd': function(data, params) { - var q = data.message.valMatch(/^~qadd ([\d\w\s-]*)=(.+)$/, 3); + var q = data.message.valMatch(/^~qadd ([\d\w\s-]+?)[ ]?=[ ]?(.+)$/, 3); if(q) { key = q[1].toLowerCase(); if(!Object.isArray(quotes[key])) { @@ -189,11 +232,11 @@ var quotes = function(dbot) { '~rq': function(data, params) { var rQuote = Object.keys(quotes).random(); - dbot.say(data.channel, rQuote + ': ' + quotes[rQuote].random()); + dbot.say(data.channel, rQuote + ': ' + interpolatedQuote(rQuote)); }, '~d': function(data, params) { - dbot.say(data.channel, data.user + ': ' + dbot.db.quoteArrs['depressionbot'].random()); + dbot.say(data.channel, data.user + ': ' + interpolatedQuote(dbot.name)); }, '~link': function(data, params) { @@ -202,6 +245,23 @@ var quotes = function(dbot) { } else { dbot.say(data.channel, 'Link to "'+params[1]+'" - http://nc.no.de:443/quotes/'+params[1]); } + }, + + '~qprune': function(data) { + var pruned = [] + for(key in quotes) { + if(quotes.hasOwnProperty(key)) { + if(quotes[key].length == 0) { + delete quotes[key]; + pruned.push(key); + } + } + } + if(pruned.length > 0) { + dbot.say(data.channel, "Pruning empty quote categories: " + pruned.join(", ")); + } else { + dbot.say(data.channel, "No empty quote categories. Commence incineration."); + } } }; @@ -227,6 +287,9 @@ var quotes = function(dbot) { dbot.db.bans['*'].include(data.user)) { dbot.say(data.channel, data.user + ' is banned from using this command. Commence incineration.'); } else { + if(!dbot.db.quoteArrs.hasOwnProperty('realityonce')) { + dbot.db.quoteArrs['realityonce'] = []; + } dbot.db.quoteArrs['realityonce'].push('reality ' + once[1] + '.'); addStack.push('realityonce'); rmAllowed = true; diff --git a/modules/web.js b/modules/web.js index 2f7c1f0..e3ac0fa 100644 --- a/modules/web.js +++ b/modules/web.js @@ -18,26 +18,26 @@ var webInterface = function(dbot) { // Lists the quote categories app.get('/quotes', function(req, res) { - res.render('quotelist', { 'quotelist': Object.keys(dbot.db.quoteArrs) }); + res.render('quotelist', { 'name': dbot.name, 'quotelist': Object.keys(dbot.db.quoteArrs) }); }); // Lists quotes in a category app.get('/quotes/:key', function(req, res) { var key = req.params.key.toLowerCase(); if(dbot.db.quoteArrs.hasOwnProperty(key)) { - res.render('quotes', { 'quotes': dbot.db.quoteArrs[key], locals: { 'url_regex': RegExp.prototype.url_regex() } }); + res.render('quotes', { 'name': dbot.name, 'quotes': dbot.db.quoteArrs[key], locals: { 'url_regex': RegExp.prototype.url_regex() } }); } else { - res.render('error', { 'message': 'No quotes under that key.' }); + res.render('error', { 'name': dbot.name, 'message': 'No quotes under that key.' }); } }); // Load random quote category page app.get('/rq', function(req, res) { var rCategory = Object.keys(dbot.db.quoteArrs).random(); - res.render('quotes', { 'quotes': dbot.db.quoteArrs[rCategory], locals: { 'url_regex': RegExp.prototype.url_regex() } }); + res.render('quotes', { 'name': dbot.name, 'quotes': dbot.db.quoteArrs[rCategory], locals: { 'url_regex': RegExp.prototype.url_regex() } }); }); - app.listen(443); + app.listen(dbot.webPort); return { 'onDestroy': function() { diff --git a/public/ytembed.js b/public/ytembed.js new file mode 100644 index 0000000..3a979b6 --- /dev/null +++ b/public/ytembed.js @@ -0,0 +1,22 @@ +// let's fetch us some goddamn API +var apiEmbed = document.createElement('script'); +apiEmbed.src = 'http://www.youtube.com/player_api'; +document.getElementsByTagName('script')[0].parentNode.insertBefore(apiEmbed, document.getElementsByTagName('script')[0]); + +// this will be called by the player API when it's finished downloading +function onYouTubePlayerAPIReady() { + var youTubePlaceholders = document.getElementsByClassName('ytplaceholder'); + for(var i = 0; i < youTubePlaceholders.length; i++) { + var videoURL = youTubePlaceholders[i].innerHTML; + var videoIDMaybe = videoURL.match(/[?&]v=([A-Za-z0-9\-_]+)(?:[?&]|$)/); + youTubePlaceholders[i].innerText = ''; + if(videoIDMaybe) { + var ytVideoID = videoIDMaybe[1]; + var player = new YT.Player(youTubePlaceholders[i], { + height: '203', + width: '336', + videoId: ytVideoID + }); + } + } +} diff --git a/run.js b/run.js index 022d4bf..b8e8d74 100644 --- a/run.js +++ b/run.js @@ -6,16 +6,50 @@ require('./snippets'); var DBot = function(timers) { // Load external files this.config = JSON.parse(fs.readFileSync('config.json', 'utf-8')); - this.db = JSON.parse(fs.readFileSync('db.json', 'utf-8')); + this.db = null; + var rawDB; + try { + var rawDB = fs.readFileSync('db.json', 'utf-8'); + } catch (e) { + this.db = {}; // If no db file, make empty one + } + if(!this.db) { // If it wasn't empty + this.db = JSON.parse(rawDB); + } + + // Repair any deficiencies in the DB; if this is a new DB, that's everything + if(!this.db.hasOwnProperty("bans")) { + this.db.bans = {}; + } + if(!this.db.bans.hasOwnProperty("*")) { + this.db.bans["*"] = []; + } + if(!this.db.hasOwnProperty("quoteArrs")) { + this.db.quoteArrs = {}; + } + if(!this.db.hasOwnProperty("kicks")) { + this.db.kicks = {}; + } + if(!this.db.hasOwnProperty("kickers")) { + this.db.kickers = {}; + } + if(!this.db.hasOwnProperty("modehate")) { + this.db.modehate = []; + } + if(!this.db.hasOwnProperty("locks")) { + this.db.locks = []; + } // Populate bot properties with config data this.name = this.config.name || 'dbox'; - this.admin = this.config.admin || 'reality'; + this.admin = this.config.admin || [ 'reality' ]; this.password = this.config.password || 'lolturtles'; this.nickserv = this.config.nickserv || 'zippy'; this.server = this.config.server || 'elara.ivixor.net'; this.port = this.config.port || 6667; - this.moduleNames = this.config.modules || [ 'js', 'admin', 'kick', 'modehate', 'quotes', 'puns', 'spelling', 'web', 'youare' ]; + this.webPort = this.config.webPort || 443; + this.moduleNames = this.config.modules || [ 'command', 'js', 'admin', 'kick', 'modehate', 'quotes', 'puns', 'spelling', 'web', 'youare' ]; + this.sessionData = {}; this.timers = timers.create(); @@ -38,7 +72,7 @@ DBot.prototype.say = function(channel, data) { }; DBot.prototype.act = function(channel, data) { - this.instance.send('PRIVMSG', channel, ':\001ACTION' + data + '\001'); + this.instance.send('PRIVMSG', channel, ':\001ACTION ' + data + '\001'); } // Save the database file @@ -62,6 +96,12 @@ DBot.prototype.reloadModules = function() { this.timers.clearTimers(); this.save(); + // Enforce having command. it can still be reloaded, but dbot _will not_ + // function without it, so not having it should be impossible + if(!this.moduleNames.include("command")) { + this.moduleNames.push("command"); + } + // Reload Javascript snippets var path = require.resolve('./snippets'); delete require.cache[path]; @@ -97,51 +137,6 @@ DBot.prototype.reloadModules = function() { return module; }.bind(this)); - - this.instance.addListener('PRIVMSG', function(data) { - params = data.message.split(' '); - if(data.channel == this.name) data.channel = data.user; - - if(this.commands.hasOwnProperty(params[0])) { - if((this.db.bans.hasOwnProperty(params[0]) && - this.db.bans[params[0]].include(data.user)) || this.db.bans['*'].include(data.user)) - this.say(data.channel, data.user + - ' is banned from using this command. Commence incineration.'); - else { - this.commands[params[0]](data, params); - this.save(); - } - } else { - var q = data.message.valMatch(/^~([\d\w\s-]*)/, 2); - if(q) { - if(this.db.bans['*'].include(data.user)) { - this.say(data.channel, data.user + - ' is banned from using this command. Commence incineration.'); - } else { - q[1] = q[1].trim(); - key = this.cleanNick(q[1]) - if(this.db.quoteArrs.hasOwnProperty(key)) { - this.say(data.channel, q[1] + ': ' + this.db.quoteArrs[key].random()); - } else { - // See if it's similar to anything - var winnerDistance = Infinity; - var winner = false; - for(var commandName in this.commands) { - var distance = String.prototype.distance(params[0], commandName); - if(distance < winnerDistance) { - winner = commandName; - winnerDistance = distance; - } - } - - if(winnerDistance < 3) { - this.say(data.channel, 'Did you mean ' + winner + '? Learn to type, hippie!'); - } - } - } - } - } - }.bind(this)); }; DBot.prototype.cleanNick = function(key) { diff --git a/snippets.js b/snippets.js index b769658..8e6a9af 100644 --- a/snippets.js +++ b/snippets.js @@ -27,6 +27,14 @@ Array.prototype.include = function(value) { return false; }; +Array.prototype.sum = function() { + var sum = 0; + for(var i=0;i