--- /dev/null
+/* $Id: irclog.js,v 1.1 2006/11/26 05:58:58 mmondor Exp $ */
+
+/* Configuration */
+var irc_channel = '#test';
+var irc_servers = [
+/*
+ 'irc.freenode.net:6667'
+ */
+ 'ginseng.xisop:6667',
+ 'mudbug.org:6667'
+];
+var irc_nicknames = [
+ 'nanobit',
+ 'e-chemical',
+ 'e-alchemy',
+ 'nick_',
+ 'nick__'
+];
+var irc_user = 'nanobit', irc_name = 'e-chemical e-alchemy';
+var irc_reconnect_delay = 4;
+var irc_version = '$Id: irclog.js,v 1.1 2006/11/26 05:58:58 mmondor Exp $';
+
+/* XXX Would be more complex, but ideal
+var networks = [
+ {
+ name: "rubiks",
+ channels: [
+ "#test"
+ ],
+ servers: [
+ "ginseng.xisop:6667"
+ ]
+ }
+];
+*/
+
+
+
+function sleep(seconds)
+{
+ try {
+ var fh = File.popen('/bin/sleep ' + seconds, 'r');
+ try {
+ for (;;)
+ fh.read(256);
+ } catch (x) {}
+ fh.close();
+ } catch (x) { stderr.write(x + "\n"); }
+}
+
+/*
+ * Returns an object with the address and port of a server to (re)connect to.
+ * Uses round-robin among the specified servers array. We also sleep during
+ * a few seconds to prevent connect-flooding.
+ */
+var last_server = 0;
+function select_server()
+{
+ var w = irc_servers[last_server++].split(':');
+
+ if (last_server == irc_servers.length)
+ last_server = 0;
+ if (w[1] == undefined)
+ w[1] = 6667;
+ else
+ w[1] = Number(w[1]);
+
+ last_nickname = 0;
+
+ return { address: w[0], port: w[1] };
+}
+
+var last_nickname = 0;
+function select_nickname()
+{
+ var s = irc_nicknames[last_nickname++];
+
+ if (last_nickname == irc_nicknames.length)
+ last_nickname = 0;
+
+ irc_nickname = s;
+ return s;
+}
+
+function log_init()
+{
+ var i;
+
+ log_digits = [];
+ for (i = 0; i < 60; i++) {
+ if (i < 10)
+ log_digits[i] = '0' + i;
+ else
+ log_digits[i] = i.toString();
+ }
+
+ stdout.setvbuf(File._IOLBF, 0);
+}
+
+function log_timestamp()
+{
+ var t = new Date();
+ var s = t.getUTCFullYear() + log_digits[t.getUTCMonth()] +
+ log_digits[t.getUTCDate()] + log_digits[t.getUTCHours()] +
+ log_digits[t.getUTCMinutes()] + log_digits[t.getUTCSeconds()] +
+ '-0000';
+
+ return s;
+}
+
+File.prototype.getline = function()
+{
+ var line = this.gets(1024, true);
+
+ /* Specifying false to gets() should strip these, but it's broken */
+ line = line.substr(0, line.length - 2);
+
+ /* Log input line as-is with timestamp immediately */
+ stdout.write(log_timestamp() + ' < ' + line + "\n");
+
+ return line;
+}
+
+File.prototype.putline = function(line)
+{
+ stdout.write(log_timestamp() + ' > ' + line + "\n");
+ this.write(line + "\r\n");
+}
+
+File.prototype.state_all = function(line)
+{
+ var s, w, from, to, msg;
+
+ /* PING :<server> */
+ if (line.match(/^PING :/) != null) {
+ this.putline('PONG ' + line.substr(5));
+ return true;
+ }
+
+ /* :<nick>!<user>@<host> PRIVMSG <to> :<message> */
+ if ((s = line.match(/^:[^\b!:]*![^\b@:]*@[^\b:]* PRIVMSG [^\b:]* :/))
+ != null) {
+ s = s.toString();
+ msg = line.substr(s.length);
+ from = line.substr(1, line.indexOf('!') - 1);
+ w = line.split(' ');
+ to = w[2];
+ if (msg.charCodeAt(0) == 0x01 &&
+ msg.charCodeAt(msg.length - 1) == 0x01) {
+ /* CTCP */
+ msg = msg.substr(1, msg.length - 2);
+ if (msg.match(/^PING [0-9]* [0-9]*$/) != null)
+ this.putline('NOTICE ' + from + " :\001" +
+ msg + "\001");
+ else if (msg == 'VERSION')
+ this.putline('NOTICE ' + from + " :\001" +
+ 'VERSION ' + irc_version + "\001");
+ else if (msg == 'TIME')
+ this.putline('NOTICE ' + from + " :\001" +
+ 'TIME ' + (new Date()).toUTCString() +
+ "\001");
+ }
+ return true;
+ }
+
+ return false;
+}
+
+File.prototype.state_nick = function()
+{
+ var line, w;
+
+ stdout.write(log_timestamp() + ' -^- Entering state_nick()' + "\n");
+
+ this.putline('NICK ' + select_nickname());
+ for (;;) {
+ line = this.getline();
+
+ if (this.state_all(line))
+ continue;
+
+ /* :<server> 433 * <nick> :Nick already used */
+ if (line.match(/^:[^\b:]* 433 * [^\b:]* :/) != null) {
+ this.putline('NICK ' + select_nickname());
+ continue;
+ }
+
+ /* :<server> 376 <nick> :End of MOTD */
+ if (line.match(/^:[^\b:]* 376 [^\b:]* :/) != null)
+ break;
+ }
+
+ this.state = File.prototype.state_log;
+ return true;
+}
+
+File.prototype.state_log = function()
+{
+ var line, from, to, chan, msg, w;
+
+ stdout.write(log_timestamp() + ' -^- Entering state_log()' + "\n");
+
+ for (;;) {
+ if (!on_channel) {
+ sleep(++join_attempts);
+ if (join_attempts > 10)
+ join_attempts = 10;
+ this.putline('JOIN :' + irc_channel);
+ }
+
+ line = this.getline();
+
+ if (this.state_all(line))
+ continue;
+
+ /*
+ * XXX
+ * Ideally, for more security, we should detect JOIN and PART
+ * events which are for us but which we did not trigger.
+ * For instance, we want to leave automatically channels
+ * which an oper might have forced us into. Similarily,
+ * we want to rejoin in case of an unexpected PART event from
+ * our part. Moreover, it would be nice to also auto-detect
+ * further nickname changes applied to us automatically so
+ * that we may return to the nick state.
+ */
+
+ /* :<nick>!<user>@<host> JOIN :<channel> */
+ if ((s = line.match(/^:[^\b!:]*![^\b@:]*@[^\b:]* JOIN :/))
+ != null) {
+ s = s.toString();
+ msg = line.substr(s.length);
+ from = line.substr(1, line.indexOf('!') - 1);
+ if (from.toLowerCase() == irc_nickname.toLowerCase() &&
+ msg.toLowerCase() == irc_channel.toLowerCase()) {
+ on_channel = true;
+ continue;
+ }
+ }
+
+ /* :<nick>!<user>@<host> KICK <channel> <nick> :<message> */
+ if (line.match(
+ /^:[^\b!:]*![^\b@:]*@[^\b:]* KICK [^\b:]* [^\b:]* :/)
+ != null) {
+ w = line.split(' ');
+ chan = w[2];
+ to = w[3];
+ if (chan.toLowerCase() == irc_channel.toLowerCase() &&
+ to.toLowerCase() == irc_nickname.toLowerCase()) {
+ on_channel = false;
+ join_attempts = 10;
+ continue;
+ }
+ }
+ }
+
+ this.putline('QUIT :' + irc_quit_message);
+ return false;
+}
+
+function main()
+{
+ var server;
+ var fd = new FD();
+ var fh;
+ var line;
+
+ log_init();
+ on_channel = false;
+ join_attempts = 0;
+
+ for (;;) { try {
+ server = select_server();
+ stdout.write(log_timestamp() + ' -+- Connecting to ' +
+ server.address + ':' + server.port + "\n");
+ fd.socket(FD.AF_INET, FD.SOCK_STREAM, 0);
+ fd.connect(server.address, server.port);
+ fd.setsockopt(FD.SO_KEEPALIVE, 1);
+ fh = new File(fd.fd, 'r+');
+ fh.putline('USER ' + irc_user + ' hostname servername :' +
+ irc_name);
+ fh.state = File.prototype.state_nick;
+ for (;;) {
+ if (!fh.state())
+ break;
+ }
+ } catch (x) {
+ stdout.write(log_timestamp() + ' -*- Error: ' + x + "\n");
+ }
+ try {
+ fh.close();
+ fd.close();
+ } catch (x) {}
+ stdout.write(log_timestamp() + ' -+- Disconnected from ' +
+ server.address + ':' + server.port + "\n");
+ sleep(irc_reconnect_delay);
+ }
+}
+
+main();