Skip to content

Instantly share code, notes, and snippets.

@boxmein
Created September 7, 2014 23:18
Show Gist options
  • Select an option

  • Save boxmein/0a706d49eee761ebaea8 to your computer and use it in GitHub Desktop.

Select an option

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.
/*
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