Created
September 7, 2014 23:18
-
-
Save boxmein/0a706d49eee761ebaea8 to your computer and use it in GitHub Desktop.
Node.js IRC bot (adapted from my mini-bot) for kick/ban roulette, and more.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| Sadistic Russian Roulette and Random Banning IRC bot, codename ruletka. | |
| Requires ops in the channel. | |
| Commands: | |
| 1. \r : roulette. | |
| 2. \b : randomban. | |
| 3. \k : chance kick. | |
| Roulette: 50% chance of getting kick-banned for 2 minutes. | |
| Random-ban: | |
| 1. 90% chance to fail and backfire, causing the sender to get kick-banned | |
| for 2 minutes. | |
| 2. 10% chance to win and kick-ban a person randomly for 2 minutes. | |
| Chance-kick: 5% chance of kicking the user specified. | |
| * CTCP VERSION responds with "boxmein's roulette bot" | |
| * The letters matter only at the start of the string, the rest is completely | |
| irrelevant - '\roulette' and '\readme' are equivalent, as are '\bansomeone' | |
| and '\bakemeacookie'. | |
| * Created on ircd-seven (Freenode). Not sure how stable is on other ircd's. | |
| * NAMES command needs 'priming', thus the first '\b' command will generate the | |
| name array. | |
| */ | |
| /*jslint node: true */ | |
| console.log("-- working directory: " + process.cwd()); | |
| var net = require("net"), | |
| namestrings = []; | |
| var config = { | |
| rawlogging: false, | |
| sendlogging: true, | |
| nickname: "boxmeins-awesome-roulette-bot", | |
| realname: "i forgot to change the realname", | |
| channels: "#freenode,#defocus" | |
| }; | |
| var timebombs = { | |
| /* | |
| "boxmein": { | |
| wire: "red", | |
| wires: 3, | |
| timer: from setTimeout, | |
| sender: "mniip", | |
| time: 40 | |
| } | |
| */ | |
| }; | |
| var wires = ["red", "green", "blue", "gray", "gay", "mniip", "potato", "le", "myself", "wololo"]; | |
| var connection = { | |
| client: new net.Socket(), | |
| PORT: 6667, | |
| HOST: "irc.freenode.net", | |
| names: [], | |
| onConnected: function() { | |
| console.log("** Connection successful"); | |
| setTimeout(function() { | |
| irc.raw("NICK " + config.nickname); | |
| irc.raw("USER " + config.nickname + " 8 * :" + config.realname); | |
| irc.raw("JOIN " + config.channels); | |
| console.log("--- --- Finished startup --- ---"); | |
| }, 7000); | |
| }, | |
| onData: function(data) { | |
| // :boxmein!boxmein@unaffiliated/boxmein PRIVMSG ##powder-bots | |
| // :this is a test message | |
| if(config.rawlogging) | |
| console.log("\033[30;1m<< {0}\033[0m".trim().format(data)); | |
| // Channel messages | |
| if (data.indexOf("PRIVMSG ") != -1) { | |
| var arr = data.split(' '); | |
| var result = arr.splice(0,3); | |
| result.push(arr.join(' ')); | |
| var ircdata = { | |
| hostmask : result[0].substring(1), | |
| channel : result[2], | |
| sender : result[0].substring(1, result[0].indexOf("!")), | |
| message : result[3].substring(1).trim() | |
| }; | |
| var cmd; | |
| // Command handling | |
| ircdata.args = ircdata.message.split(" "); | |
| if (ircdata.message.indexOf("\\r") === 0) | |
| { | |
| // Roulette time | |
| var lives = Math.random() > 0.5; | |
| console.log("{0} is rouletting: {1}" | |
| .format(ircdata.sender, lives ? | |
| "he lives." : | |
| "he dies.") | |
| ); | |
| if (lives) | |
| respond(ircdata, "* The gun clicks. You survive this round."); | |
| else { | |
| respond(ircdata, "** Pow! The gun fires and you die. >:)"); | |
| // Time in seconds. | |
| op.kban(ircdata.channel, ircdata.sender, 30); | |
| } | |
| } | |
| else if (false && ircdata.message.indexOf("\\b") === 0) | |
| { | |
| // Randomban time | |
| if (Math.random() > 0.9 || ircdata.sender == "secondbox") { | |
| if (names.length === 0) | |
| irc.getnames(ircdata.channel); | |
| console.log("\033[31;1m{0} succeeded: randombanning. (len: {1})\033[0m" | |
| .format(ircdata.sender, connection.names.length)); | |
| // He succeeded @_@ | |
| irc.getnames(ircdata.channel); | |
| // Prevents myself from being banned when first name in the list. | |
| var target = connection.names[ | |
| Math.floor( | |
| Math.random() * connection.names.length - 1 | |
| ) + 1] || false; | |
| if (!target) | |
| return; | |
| console.log("\033[31;1mSelected target: {0}. Ban duration: 30 seconds\033[0m" | |
| .format(target)); | |
| op.kban(ircdata.channel, target, 30); | |
| } | |
| else { | |
| console.log("\033[32;8m{0} failed: backfiring. yeya!\033[0m" | |
| .format(ircdata.sender)); | |
| op.kban(ircdata.channel, ircdata.sender, 30); | |
| } | |
| } | |
| else if (ircdata.message.indexOf("\\k") === 0) | |
| { | |
| // Chance-kicking | |
| // 5% chance to kick desired person. | |
| // 25% chance to backfire. | |
| var kick = Math.random() > 0.95; | |
| var nick = sanitize(ircdata.args[1] || ircdata.sender); | |
| if (!nick || (connection.names.indexOf(nick) != -1)) | |
| return console.log("Nick not added OR nick not in names"); | |
| console.log("\033[32;1m{0} \033[32mis chance-kicking \033[32;1m{1}: {2}\033[0m" | |
| .format(ircdata.sender, | |
| nick, | |
| kick ? | |
| "he will be kicked." : | |
| "he will not be kicked.") | |
| ); | |
| if (nick.toLowerCase() == "ruletka") | |
| nick = ircdata.sender; | |
| if (kick) | |
| op.kick(ircdata.channel, nick, "Targeted kick at you >_>"); | |
| // 25% backfire here! | |
| else { | |
| if (Math.random() > 0.75) { | |
| console.log("\033[32;1m{0} will be kicked instead due to backfire!\033[0m" | |
| .format(ircdata.sender)); | |
| op.kick(ircdata.channel, ircdata.sender, "Backfire!"); | |
| } | |
| } | |
| } | |
| else if (false && ircdata.message.indexOf("\\t") === 0) | |
| { | |
| // Timebombing people. | |
| var nickname = sanitize(ircdata.args[1]) || false; | |
| if (!nickname) return; | |
| if (nickname in timebombs) { | |
| respond(ircdata, "This user is already being timebombed >_>"); | |
| return; | |
| } | |
| // All names I'm aware of. | |
| // if (connection.names.indexOf(nickname) !== -1) { | |
| timebombs[nickname] = {}; | |
| timebombs[nickname].sender = ircdata.sender; | |
| // set up the wire count and the right wire. | |
| timebombs[nickname].wires = Math.floor(Math.random() * (wires.length - 3)) + 3; | |
| timebombs[nickname].wire = wires[Math.floor(Math.random() * wires.length)]; | |
| // set the time of ticking in seconds. | |
| timebombs[nickname].time = Math.floor(Math.random() * 40) + 20; | |
| timebombs[nickname].opts = []; | |
| // put some random wires in | |
| for (var i=0;i<timebombs[nickname].wires;i++) { | |
| timebombs[nickname].opts.push(wires[Math.floor(Math.random() * wires.length)]); | |
| } | |
| // add the right one in there somewhere | |
| timebombs[nickname].opts.splice(Math.floor(Math.random() * timebombs[nickname].opts.length), | |
| 1, timebombs[nickname].wire); | |
| // initiate the timer | |
| timebombs[nickname].timer = setTimeout(function() { | |
| op.kick(ircdata.channel, nickname, "Pow! Timebombed!"); | |
| }, 1000 * timebombs[nickname].time); | |
| // give me knowledge | |
| console.log("Timebombing {0} with {1} wires (correct: {2}). Time is {3} seconds.\n Options are: {4}" | |
| .format(nickname, timebombs[nickname].wires, timebombs[nickname].wire, timebombs[nickname].time, | |
| timebombs[nickname].opts.join(', '))); | |
| // give the user knowledge | |
| ircdata.sender = nickname; | |
| respond(ircdata, "You were timebombed! The bomb has {0} wires and will explode in {1} seconds..." | |
| .format(timebombs[nickname].wires, timebombs[nickname].time)); | |
| respond(ircdata, "Options are: \x032 " + timebombs[nickname].opts.join(', ')); | |
| /* } | |
| else { | |
| respond(ircdata, "No such nickname ;_;"); | |
| } //*/ | |
| } | |
| else if (ircdata.message.indexOf("\\c") === 0) | |
| { | |
| // Timebomb: Cut Wire. | |
| var wire = ircdata.args.shift(); | |
| if (!wire) | |
| return; | |
| if (ircdata.sender in timebombs) | |
| { | |
| if (timebombs[ircdata.sender].wire && timebombs[ircdata.sender].wire == wire) | |
| { | |
| clearTimeout(timebombs[ircdata.sender].timer); | |
| timebombs[ircdata.sender] = null; | |
| respond(ircdata, "Phew, you're safe!"); | |
| } | |
| } | |
| else { | |
| respond(ircdata, "You don't have a timebomb in your pants!"); | |
| return; | |
| } | |
| } | |
| else if (ircdata.message.indexOf("\x01") === 0) | |
| { | |
| // CTCP! | |
| if (ircdata.message.substring(1).indexOf("VERSION") === 0) { | |
| irc.ctcp(ircdata.sender, "boxmein's roulette bot"); | |
| } | |
| } | |
| } | |
| // NAMES reqeuests | |
| else if (data.indexOf("353") != -1 && connection.expect_names) { | |
| // Save for parsing later. | |
| if (namestrings.length < 10) | |
| namestrings.push(data); | |
| else | |
| console.log("\033[31mMore names than expected.\033[0m"); | |
| } | |
| // NAMES end of list | |
| else if (data.indexOf("366") != -1 && connection.expect_names) { | |
| console.log("\033[30;1mName stream ended, parsing...\033[0m"); | |
| irc.parsenames(namestrings); | |
| connection.expect_names = false; | |
| } | |
| // "Not channel operator" | |
| else if (data.indexOf("482") != -1) { | |
| console.log("Op me please!"); | |
| } | |
| else if(data.indexOf("PING") != -1) { | |
| irc.raw("PONG " + (data.split(" ")[1] || ":00000000"), true); | |
| } | |
| }, | |
| onConnectionClose: function() { | |
| console.log("** Disconnected. "); | |
| process.exit(0); | |
| }, | |
| onErr: function(evt) { | |
| console.log(":( Error connecting: " + evt); | |
| } | |
| }; | |
| var respond = function(ircdata, message, nonick) { | |
| console.log("\033[36m-> {0}: \033[36;1m{1}\033[0m" | |
| .format(ircdata.sender, message)); | |
| irc.privmsg(ircdata.channel, ( | |
| nonick ? "" : ircdata.sender + ": ") + | |
| message); | |
| }; | |
| var sanitize = function(query) { | |
| return (''+query).trim().replace(/[^\w-_ !"#¤%&\/()]/gi, ""); | |
| }; | |
| connection.client.setEncoding("ascii"); | |
| connection.client.setNoDelay(); | |
| connection.client.connect(connection.PORT, connection.HOST, connection.onConnected); | |
| connection.client.on("data", connection.onData); | |
| connection.client.on("close", connection.onConnectionClose); | |
| connection.client.on("error", connection.onErr); | |
| var irc = { | |
| // Sends a CTCP request to the channel or user. Used for ACTION and various CTCP requests. | |
| // @param channel The user or channel to send a request to | |
| // @param data The CTCP data to send. Includes the sub-command. | |
| // Looks like "VERSION" or like "ACTION eats a vegetable" | |
| ctcp: function(channel, data) { | |
| irc.raw("PRIVMSG {0} :\x01{1}\x01".format(channel, data)); | |
| }, | |
| // Sets a channel mode. Will also work with bans/stuff since mode can contain a name. | |
| // @param channel The channel that will receive the given mode. | |
| // @param mode The mode being set. May either be "+o" or even "+b boxmein!*@*" | |
| mode: function (channel, mode) { | |
| irc.raw("MODE {0} {1}".format(channel, mode)); | |
| }, | |
| // Sends a NAMES request to the given channel to find out all about the users joined there. | |
| // @param channel The channel to send NAMES to. | |
| names: function (channel) { | |
| irc.raw("NAMES " + channel); | |
| }, | |
| // Sends a CTCP ACTION, the equivalent of /me in IRC clients. | |
| // @param channel The channel to send the ACTION to. May also be an username. | |
| // @param action The action to send. | |
| action: function(channel, action) { | |
| irc.ctcp(channel, "ACTION {0}".format(action)); | |
| }, | |
| // Sends a private message (also a channel message!) | |
| // @param channel The given channel to send to. May also be an username. | |
| // @param message The message that will be sent. | |
| // @param hide Whether or not to hide the sent message. Gets passed on to irc.raw. May be left | |
| // undefined. | |
| privmsg: function(channel, message, hide) { | |
| irc.raw("PRIVMSG {0} :{1}".format(channel, message), hide); | |
| }, | |
| // Sends a NOTICE to the channel or user | |
| // @param channel The channel or user to send the given message to. | |
| // @param message The message being sent. | |
| notice: function(channel, message) { | |
| irc.raw("NOTICE {0} :{1}".format(channel, message)); | |
| }, | |
| // Joins a channel. | |
| // @param channel The channel to join to | |
| join: function(channel) { | |
| irc.raw("JOIN {0}".format(channel)); | |
| }, | |
| // Removes self from the given channel. No leave messages. | |
| // @param channel The channel to leave from | |
| part: function(channel) { | |
| irc.raw("PART {0}".format(channel)); | |
| }, | |
| // Quits the IRC. Sends a quit message and waits for the server for 5 seconds then quits | |
| // the program. | |
| quit: function() { | |
| irc.raw("QUIT :Sayonara"); | |
| setTimeout(function() { | |
| process.exit(0); | |
| }, 5000); | |
| }, | |
| // Sends raw data to the IRC server. | |
| // @param data The data that is to be written, also known as raw IRC. Use wrappers instead :D | |
| // @param hide Whether or not to hide the sending notification into the console. | |
| raw: function(data, hide) { | |
| connection.client.write(data + "\n", "ascii", function() { | |
| if(!hide && config.sendlogging) | |
| console.log(">> " + data); | |
| }); | |
| }, | |
| // IRC Colours. | |
| col: { | |
| cchar: '\x03', // Control character => 0x03 or ^C. | |
| beep: '\x07', // Bell character | |
| clear: '\x0F', // Clear Formatting | |
| bold: '\x02', // Bold character | |
| italic: '\x15', // Italic formatting | |
| uline: '\x1E', // Underlines | |
| white: 0, // Note: may be interpreted differently | |
| black: 1, | |
| navy: 2, | |
| green: 3, | |
| red: 4, | |
| brown: 5, | |
| purple: 6, | |
| orange: 7, | |
| yellow: 8, | |
| lime: 9, | |
| teal: 10, | |
| cyan: 11, | |
| lblue: 12, | |
| pink: 13, | |
| grey: 14, | |
| lgrey: 15, | |
| c: function(col, bgcol) { | |
| return this.cchar + col + (bgcol? "," + bgcol : ''); | |
| } | |
| } | |
| }; | |
| var op = { | |
| // When it's an operator, I do stuff from here. | |
| // Needs the .format() function on string.prototype. :[ | |
| op: function(channel, user) { | |
| // +o's a given user. | |
| irc.raw("MODE {0} +o {1}".format(channel, user)); | |
| }, | |
| deop: function(channel, user) { | |
| // -o's a given user. | |
| irc.raw("MODE {0} -o {1}".format(channel, user)); | |
| }, | |
| kick: function(channel, user, words) { | |
| // removes user from channel forcedly | |
| irc.raw("KICK {0} {1} :{2}".format(channel, user, (words || "Bot kick") )); | |
| }, | |
| ban: function(channel, user) { | |
| // prevents user from talking and joining | |
| irc.raw("MODE {0} +b {1}".format(channel, user)); | |
| }, | |
| unban: function(channel, user) { | |
| // unbans user | |
| irc.raw("MODE {0} -b {1}".format(channel, user)); | |
| }, | |
| kban: function(channel, user, time) { | |
| // bans user for time, 0 for no unban | |
| op.ban(channel, user); | |
| op.kick(channel, user); | |
| if (time) | |
| setTimeout(function() { op.unban(channel, user); }, time*1000); // Seconds | |
| }, | |
| voice: function(channel, user) { | |
| // Voices the given user. Can talk when channel in moderated mode. | |
| }, | |
| devoice: function(channel, user) { | |
| // Removes voice from given user. | |
| } | |
| }; | |
| /* | |
| Utility functions courtesy of dearly beloved Stack Overflow | |
| <3 | |
| */ | |
| // woo stackoverflow | |
| String.prototype.trim = function() { | |
| return this.replace(/^\s+|\s+$/g, ""); | |
| }; | |
| if (!String.prototype.format) { | |
| String.prototype.format = function() { | |
| var args = arguments; | |
| return this.replace(/{(\d+)}/g, function(match, number) { | |
| return typeof args[number] != 'undefined' ? | |
| args[number] : | |
| match; | |
| }); | |
| }; | |
| } | |
| // Long function definitions irrelevant to the irc object defined before. | |
| // Allows the irc object above to stay neat and formatted properly. | |
| irc.parsenames = function(namestrings) { | |
| connection.names = []; | |
| if (namestrings && namestrings.length > 0) | |
| { | |
| console.log("\033[31mName stream okay. Parsing...\033[0m"); | |
| console.log("--- Listing names --- \033[30,1m"); | |
| for (var i = 0; i < namestrings.length; i++) { | |
| // [0]: "" | |
| // [1]: "zelazny.freenode.net blah blah" | |
| // [2]: ":Flambe +jacksonmj etc etc" | |
| var namestring = namestrings[i].split(':')[2] || false; | |
| if (!namestring) | |
| continue; | |
| namestring = namestring.split(' '); | |
| for (var j = 0; j < namestring.length; j++) { | |
| var name = namestring[j].replace(/^[+@%~*-]/, ''); | |
| connection.names.push(name); | |
| process.stdout.write(name + " "); | |
| } | |
| } | |
| console.log("\033[0m\n--- End of names list ---"); | |
| } | |
| }; | |
| irc.getnames = function(channel) { | |
| console.log("\033[30;1mFetching names...\033[0m"); | |
| connection.expect_names = true; | |
| irc.names(channel); | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment