Last active
December 15, 2015 15:18
-
-
Save boxmein/5280207 to your computer and use it in GitHub Desktop.
A small IRC bot which has a weird command syntax, no helps or command names, and it's quick. :D
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
/*jslint node: true */ | |
/* | |
A really small and lightweight IRC bot | |
Made from pieces of NodeIRCBot (http://github.com/boxmein/NodeIRCBot/) | |
Features: | |
1. Commands are numbered, but have a prefix character | |
2. Output logging levels | |
3. Sandboxed Javascript handling (1) | |
*/ | |
// A startup message with the current working directory for require() reference | |
console.log("-- working directory: " + process.cwd()); | |
// Libraries required: | |
// net - Networking, the connection to the IRC serer | |
// sandbox - Sandboxed Javascript interpreting | |
// http - HTTP requests | |
var net = require("net"), http = require('http'), Sandbox = require("sandbox"), sand = new Sandbox(); | |
var relays = [], relaycount = 0; | |
/* | |
Configuration variables | |
rawlogging (false): will also display every raw IRC command sent to it | |
sendlogging (true): will display every raw IRC command it sends | |
realname (magic): this name is sent as the IRC Real Name | |
nickname (boxmini): this is the bot's nickname | |
channels : the channels the bot joins. Separated by commas and no spaces! | |
*/ | |
var config = { | |
rawlogging: false, | |
sendlogging: true, | |
nickname: "boxmini", | |
realname: "magikku", | |
channels: "##boxmein,##powder-bots,##jacob1" | |
}; | |
/* | |
Commands array | |
Contains the commands addressed by index | |
input: ircdata (object) - contains lots of useful data, is created in connection.onData | |
return: the string to respond with, if any. Return false if nothing is to be told. | |
*/ | |
var commands = [ | |
// Pig latin [0] | |
function (ircdata) { | |
var endstring = ircdata.args.map(function (each) { | |
// Move the first character to the end | |
var chars = each.split(''); | |
// - => spaces! | |
chars.map(function(i) {if (i === '-') return ' '; }); | |
chars.push(chars.shift()); | |
// Append "ay" | |
chars = chars.join(''); | |
chars += "ay"; | |
// Return value | |
return chars; | |
}).join(" "); | |
return endstring; | |
}, | |
// Sandboxed Javascript [1] | |
function (ircdata) { | |
console.log("~~ doing Javascript: " + ircdata.saymsg); | |
sand.run("(function() {" + ircdata.saymsg + "})();", function (out) { | |
// Output longer than 200 chars will be truncated | |
if (out.result.length < 200) { | |
console.log(out.result); | |
respond(ircdata, out.result); | |
} | |
else { | |
console.log(out.result.slice(0,200)+"..."); | |
respond(ircdata, out.result.slice(0, 200) + "..."); | |
} | |
}); | |
}, | |
// It'll come in handy. [2] | |
function (ircdata) { | |
irc.action(ircdata.channel, "slaps Ristovski"); | |
return false; | |
}, | |
// Relaying (add) [3] | |
function (ircdata) { | |
// relaydir should look something like | |
// #channel1->#channel2 | |
var relaydir = ircdata.args[0] || false; | |
if (!relaydir) | |
return false; | |
else { | |
var channels = relaydir.split("->"); | |
channels.map(function(e) { return sanitizeChannel(e); }); | |
relays[relaycount++] = { | |
from: channels[0], | |
to: channels[1] | |
}; | |
return ''+relaycount-1; | |
} | |
}, | |
// Relaying (remove) [4] | |
function (ircdata) { | |
var index; | |
try { | |
index = parseInt(ircdata.args[0], 10); | |
} | |
catch (err) {return err;} | |
if (relays[index]) | |
delete relays[index]; | |
return false; | |
}, | |
// Reverse filter [5] | |
function (ircdata) { | |
var endstr = []; | |
ircdata.saymsg | |
.split('') | |
.map(function(e) { | |
endstr.unshift(e); | |
return e; | |
}); | |
return endstr.join(''); | |
}, | |
// Echo command [6] | |
function (ircdata) { | |
respond(ircdata, ircdata.saymsg, true); | |
}, | |
// Slap someone else [7] | |
function (ircdata) { | |
var slappee = ircdata.saymsg || false; | |
if (slappee) | |
irc.action(ircdata.channel, "slaps " + slappee); | |
} | |
// Increment every character by one [8] UNSAFE | |
/* function (ircdata) { | |
return ircdata.saymsg.split('').map(function (each) { | |
return String.fromCharCode(each.charCodeAt() + 1); | |
}).join(''); | |
}*/ | |
// Test the down status of a site [8] UNSAFE | |
/*function (ircdata) { | |
if (!ircdata.args[0]) return 0; | |
// Get the domain | |
var domain = ircdata.args.shift() | |
.replace(/^http:\/\//i,"") | |
.split("/") || false; | |
console.log("received domain: " + domain.join('/')); | |
if (!domain[0]) | |
return "No domain specified ;_;"; | |
return http.request({ | |
host: domain[0], | |
path: (domain[1] || "/") | |
}, | |
function(response) { | |
console.log("request fulfilled: status=" + response.statusCode); | |
if (response.statusCode == 200) | |
respond(ircdata,"Seems up (200)"); | |
else | |
respond(ircdata, "{0}: something happened ):)".format(response.statusCode)); | |
}).end(); | |
}*/ | |
]; | |
/* | |
Connection object | |
Contains everything one needs to get connected, including handleCommands to do initial command | |
parsing and ircdata generation | |
*/ | |
var connection = { | |
client: new net.Socket(), | |
PORT: 6667, | |
HOST: "irc.freenode.net", | |
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); | |
}, | |
// Main command handler | |
// Input: object | |
/* { | |
hostmask: "[email protected]", | |
channel: "#powder-bots", | |
sender: "sendernick", | |
message: "\<id> derp merp herp", | |
args: ["\<id>", "derp", "merp", "herp"], | |
// Added in this function | |
command: 5, | |
saymsg: "derp merp herp" | |
} | |
*/ | |
handleCommands: function(ircdata) { | |
ircdata.command = ircdata.args.shift().substring(1).trim().replace(/[^0-9]+?/gi, ""); | |
ircdata.saymsg = ircdata.args.join(" "); | |
try { | |
ircdata.command = parseInt(ircdata.command, 10); // parseInt implies integers | |
if (ircdata.command < commands.length && ircdata.command >= 0) { | |
var response = commands[ircdata.command](ircdata) || false; | |
if (response) | |
respond(ircdata, response); | |
} | |
} | |
catch (err) { | |
respond(ircdata, err); | |
} | |
}, | |
onData: function(data) { | |
// :boxmein!boxmein@unaffiliated/boxmein PRIVMSG ##powder-bots :this is a test message | |
if(config.rawlogging) | |
console.log("<< "+data); | |
if (data.indexOf("PRIVMSG ") != -1) { | |
var arr = data.split(' '); | |
var result = arr.splice(0,3); | |
result.push(arr.join(' ')); | |
// result = [":hostmask", "command", "channel", ":mess age"] | |
var ircdata = { | |
hostmask : result[0].substring(1), | |
channel : result[2], | |
sender : result[0].substring(1, result[0].indexOf("!")), | |
message : result[3].substring(1).trim() | |
// command: ['0'] | |
// args: ['arg1', 'arg2', 'arg3'] | |
// saymsg: "arg1 arg2 arg3" | |
// message: "\0 arg1 arg2 arg3" | |
// sender: "boxmein" | |
// hostmask: "boxmein!boxmein@unaffiliated/boxmein" | |
// channel: "##powder-bots" | |
}; | |
var cmd; | |
// 3. Command handling | |
ircdata.args = ircdata.message.split(" "); | |
if (ircdata.message.indexOf("\\") === 0) { // Ristovski wanted to be ignored :( | |
//if (ircdata.message.indexOf("\\") == 0 && ircdata.hostmask.indexOf("Ristovski") === -1) { | |
console.log("~~ command get: " + ircdata.message); | |
connection.handleCommands(ircdata); | |
} | |
// 4. Command handling II: Durararararararara | |
else if (ircdata.message.indexOf("dura") === 0) { | |
cmd = ircdata.args.shift(); | |
// args = ["argument1", "argument2"] | |
// cmd = "durararara" (dura + ra + ra + ra) | |
cmd = cmd.replace(/^dura/i, "\\"); | |
// cmd = "\rarararara" | |
var duracount = 0; | |
try { | |
// Count the rest of the ra's | |
duracount = cmd.match(/ra/gi).length; | |
} catch(err) {} | |
cmd = cmd.replace(/ra/i, ""); | |
// cmd = "\"; duracount = X; | |
console.log("cmd ended up: " + cmd); | |
cmd += duracount; | |
// cmd = "\X"; | |
ircdata.args.unshift(cmd); | |
// args = ["\X", "argument1", "argument2", ...] | |
connection.handleCommands(ircdata); | |
} | |
// 4.1. Command handling III: ?!????!?!?! | |
else if (ircdata.message.indexOf("?") === 0) { | |
cmd = ircdata.args.shift().substring(1); | |
cmd = cmd.replace(/\?/g, "0"); | |
cmd = cmd.replace(/!/g, "1"); | |
var cmdcount; | |
try { | |
if (cmd === "") | |
cmd = "0"; | |
cmdcount = parseInt(cmd, 2); | |
} | |
catch (err) { return; } | |
console.log("(?!): cmdcount ended up: " + cmdcount + "; cmd = " + cmd); | |
ircdata.args.unshift('\\'+cmdcount); | |
connection.handleCommands(ircdata); | |
} | |
/* // 4.2. Command handling IV: Mooooo | |
else if (ircdata.message.indexOf("?") === 0) { | |
cmd = ircdata.args.shift().substring(1); | |
cmd = cmd.replace(/\.\.\./g, "0"); | |
cmd = cmd.replace(/moo/g, "1"); | |
var cmdcount; | |
try { | |
cmdcount = parseInt(cmd, 2); | |
} | |
catch (err) { return; } | |
console.log("(moo:) cmdcount ended up: " + cmdcount + "; cmd = " + cmd); | |
ircdata.args.unshift('\\'+cmdcount); | |
connection.handleCommands(ircdata); | |
} // */ | |
// 5. Relaying (channel bridging really) | |
for (var i in relays) { | |
if (!(relays[i].hasOwnProperty("from") && relays[i].hasOwnProperty("to"))) | |
continue; | |
if (relays[i].from == ircdata.channel) { | |
irc.privmsg(relays[i].to, "<{0}/{1}> {2}".format(ircdata.channel, ircdata.sender, ircdata.message)); | |
} | |
} | |
// 6. Special words | |
var responded = false; | |
ircdata.args.map(function(word) { | |
if (!responded) { | |
switch (word) { | |
case 'implement': | |
responded = true; | |
respond(ircdata, "You keep using that word. I do not think it means what you think it means."); | |
break; | |
case 'boxmini': | |
responded = true; | |
respond(ircdata, "Huh?"); | |
break; | |
default: break; | |
} | |
} | |
}); | |
} | |
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); | |
} | |
}; | |
// A simple yet safe respond function | |
var respond = function(ircdata, message, nonick) { | |
console.log("-> {0}{1}: {2}{3}".format(irc.col.c(irc.col.navy), ircdata.sender, irc.col.c(irc.col.black, irc.col.black), message)); | |
irc.privmsg(ircdata.channel, (nonick ? "" : ircdata.sender + ": ") + message); | |
}; | |
// Overly attached sanitization. | |
var sanitizeChannel = function(query) { | |
return (''+query).trim().replace(/[^\w#-_]+?/gi, ""); | |
}; | |
// A bit better sanitization | |
var sanitize = function(query) { | |
return (''+query).trim().replace(/[^\w-_ !"#¤%&\/()]/gi, ""); | |
} | |
// Here's the entry point for the bot. Out of all places, I know. | |
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); | |
/* | |
Free to use IRC protocol wrapper functions | |
Feel free to contract that entire var since it contains nothing super interesting. | |
*/ | |
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 : ''); | |
} | |
} | |
}; | |
/* | |
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; | |
}); | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment