Skip to content
Browse files

lots o changes

  • Loading branch information...
1 parent c5a40df commit 14219d1eb6441533a23bbf3409f997548411fe49 @kkaefer committed
Showing with 797 additions and 16 deletions.
  1. +7 −4 config.js
  2. +21 −0 lib/color.js
  3. +60 −0 lib/helper.js
  4. +149 −0 lib/ircd.js
  5. +287 −0 lib/xmpp.js
  6. +273 −12 medium.js
View
11 config.js
@@ -1,7 +1,10 @@
-exports.jabber = {
- host: 'jabber.org',
- jid: 'jabber.org',
- muc: 'conference.jabber.org'
+exports.xmpp = {
+ // host: 'jabber.org',
+ // jid: 'jabber.org',
+ // muc: 'conference.jabber.org'
+ host: 'localhost',
+ jid: 'localhost',
+ muc: 'conference.localhost'
};
exports.irc = {
View
21 lib/color.js
@@ -0,0 +1,21 @@
+var colors = {
+ 'black': '30',
+ 'red': '31',
+ 'green': '32',
+ 'brown': '33',
+ 'blue': '34',
+ 'purple': '35',
+ 'cyan': '36',
+ 'gray': '37',
+ 'brown': '33'
+};
+
+function color(col) {
+ return function(str) {
+ return '\033[' + colors[col] + 'm' + str + '\033[0m';
+ };
+}
+
+for (var key in colors) {
+ global[key.toUpperCase()] = color(key);
+}
View
60 lib/helper.js
@@ -0,0 +1,60 @@
+exports.xml = {
+ $escapes: {'<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&apos;', '&': '&amp;' },
+ $escape: function(str) {
+ return str.replace(/[<>"'&]/g, function(c) {
+ return c in exports.xml.$escapes ? exports.xml.$escapes[c] : c;
+ });
+ },
+ $render: function(element) {
+ var text = '<' + element.$name;
+ for (var key in element.$attrs) {
+ if (typeof element.$attrs[key] === 'undefined') console.log(GRAY('UNDEFINED KEY: ' + key));
+ text += ' ' + key + '="' + exports.xml.$escape(element.$attrs[key]) + '"';
+ }
+
+ if (!element.length) return text + '/>';
+ else {
+ var children = element.map(function(e) {
+ return e instanceof Array ? exports.xml.$render(e) : exports.xml.$escape(e);
+ }).join('');
+ return text + '>' + children + '</' + element.$name + '>';
+ }
+ },
+
+ $: function(name, attrs) {
+ var element = Array.prototype.slice.call(arguments, 2);
+ element.$name = name;
+ element.$attrs = attrs || {};
+ return element;
+ }
+};
+
+exports.xmlFind = function find(xml, name) {
+ var result = [];
+ for (var i = 0; i < xml.length; i++) {
+ if (xml[i].$name === name) result.push(xml[i]);
+ }
+ return result;
+};
+
+exports.xmlPath = function path(xml, path) {
+ path = path.split('.');
+
+ for (var k = 0; k < path.length; k++) {
+ for (var i = 0; i < xml.length; i++) {
+ if (xml[i].$name === path[k]) xml = xml[i];
+ }
+ }
+
+ return xml;
+};
+
+exports.xml.auth = exports.xml.$.bind(undefined, 'auth');
+exports.xml.iq = exports.xml.$.bind(undefined, 'iq');
+exports.xml.query = exports.xml.$.bind(undefined, 'query');
+exports.xml.bind = exports.xml.$.bind(undefined, 'bind');
+exports.xml.session = exports.xml.$.bind(undefined, 'session');
+exports.xml.presence = exports.xml.$.bind(undefined, 'presence');
+exports.xml.x = exports.xml.$.bind(undefined, 'x');
+exports.xml.message = exports.xml.$.bind(undefined, 'message');
+exports.xml.body = exports.xml.$.bind(undefined, 'body');
View
149 lib/ircd.js
@@ -0,0 +1,149 @@
+var net = require('net');
+var util = require('util');
+var crypto = require('crypto');
+var EventEmitter = require('events').EventEmitter;
+var dns = require('dns');
+var fs = require('fs');
+
+// Load the credentials (self-signed) for the server.
+var caPem = fs.readFileSync(__dirname + '/../support/test_ca.pem', 'ascii');
+var certPem = fs.readFileSync(__dirname + '/../support/test_cert.pem', 'ascii');
+var keyPem = fs.readFileSync(__dirname + '/../support/test_key.pem', 'ascii');
+var credentials = crypto.createCredentials({ key: keyPem, cert: certPem, ca: caPem });
+
+
+exports.createServer = function(config, callback) {
+ return net.createServer(function (stream) {
+ callback(new Connection(stream, config));
+ }).listen(config.port || 6667, undefined, function(info) {
+ console.log('IRC daemon listening on port ' + (config.port || 6667) + '...');
+ });
+};
+
+
+
+
+
+
+function Connection(stream, config) {
+ this.stream = stream;
+ this.config = config;
+
+ this.user = {
+ host: this.stream.remoteAddress
+ };
+
+ this.setup();
+}
+util.inherits(Connection, EventEmitter);
+exports.Connection = Connection;
+
+Connection.prototype.setup = function() {
+ this.stream.setSecure(credentials);
+ this.stream.setEncoding('ascii');
+
+ // Dechunk the incoming data and call the according handler functions.
+ var connection = this;
+ var previous = '';
+ this.stream.on('data', function(str) {
+ var lines = str.split('\r\n');
+ lines[0] = previous + lines[0];
+ previous = lines.pop();
+ for (var i = 0; i < lines.length; i++) {
+ var cmd = lines[i].split(' ', 1)[0];
+ console.log(BROWN('IRC in<< ' + lines[i]));
+ if (cmd in connection) {
+ connection[cmd](lines[i].substring(cmd.length + 1));
+ }
+ else {
+ console.log('IRC COMMAND NOT HANDLED');
+ }
+ }
+ });
+};
+
+Connection.prototype.end = function() {
+ this.stream.end();
+};
+
+Connection.prototype.PASS = function(pass) {
+ this.user.password = pass;
+};
+
+Connection.prototype.NICK = function(nick) {
+ this.user.nick = nick;
+ if (this.user.user) {
+ // Nick change.
+ this.emit('nick', nick);
+ }
+};
+
+Connection.prototype.USER = function(line) {
+ var parts = line.split(/\s+/g, 3);
+ this.user.user = parts[0];
+ this.user.mode = parseInt(parts[1], 10);
+ this.user.realname = line.substring(parts[0].length + parts[1].length + parts[2].length + 3);
+
+ this.reply('001', ':Welcome to the Jabber MUC Relay Network, ' + this.user.nick);
+ this.reply('002', ':Your host is ' + this.config.host + ', running version ' + this.config.name + ' ' + this.config.version);
+ this.reply('003', ':This server was created ' + this.config.created);
+ this.reply('004', this.config.host + ' ' + this.config.name + '-' + this.config.version + ' ');
+
+ this.emit('login');
+};
+
+Connection.prototype.LIST = function(line) {
+ this.emit('list', line);
+};
+
+Connection.prototype.JOIN = function(line) {
+ // Discard optional password.
+ var channel = line.split(' ', 1)[0];
+ var rest = line.substring(channel.length + 1);
+ this.emit('join', channel, rest);
+};
+
+Connection.prototype.MODE = function(line) {
+ var recipient = line.split(' ', 1)[0];
+ var rest = line.substring(recipient.length + 1);
+ this.emit('mode', recipient, rest);
+};
+
+Connection.prototype.PRIVMSG = function(line) {
+ var recipient = line.split(' ', 1)[0];
+ var message = line.substring(recipient.length + 1);
+ this.emit('privmsg', recipient, message);
+};
+
+Connection.prototype.PART = function(line) {
+ var channel = line.split(' ', 1)[0];
+ this.emit('part', channel);
+}
+
+Connection.prototype.PONG = function(line) {
+ this.emit('pong', line);
+};
+
+Connection.prototype.reply = function(code, msg) {
+ var str = ':' + this.config.name + ' ' + code + ' ' + this.user.nick + ' ' + msg + '\r\n';
+ console.log(GREEN('IRC out>> ' + util.inspect(str)));
+ this.stream.write(str);
+};
+
+Connection.prototype.command = function(cmd, msg, user) {
+ var str = ':' + (user || (this.user.nick + '!' + this.user.user)) + ' ' + cmd + ' ' + msg + '\r\n';
+ console.log(GREEN('IRC out>> ' + util.inspect(str)));
+ this.stream.write(str);
+}
+
+
+exports.sanitizeNick = function(str) {
+ var nick = str.replace(/([^\x30-\x7D-]|@)+/g, '_');
+ if (nick.match(/^[\x5B-\x60\x7B-\x7D-]/)) nick = '_' + nick;
+ return nick;
+};
+
+exports.sanitizeUser = function(str) {
+ return str.replace(/[\0\r\n @]+/g, '_');
+}
+
View
287 lib/xmpp.js
@@ -0,0 +1,287 @@
+var net = require('net');
+var util = require('util');
+var crypto = require('crypto');
+var EventEmitter = require('events').EventEmitter;
+var Buffer = require('buffer').Buffer;
+var helper = require('./helper');
+
+var find = helper.xmlFind;
+var path = helper.xmlPath;
+var xml = helper.xml;
+
+function noop() {};
+
+var ns = {
+ 'disco#info': 'http://jabber.org/protocol/disco#info',
+ 'disco#items': 'http://jabber.org/protocol/disco#items',
+ 'caps': 'http://jabber.org/protocol/caps',
+ 'muc#user': 'http://jabber.org/protocol/muc#user',
+ 'xmpp-tls': 'urn:ietf:params:xml:ns:xmpp-tls',
+ 'xmpp-sasl': 'urn:ietf:params:xml:ns:xmpp-sasl',
+ 'xmpp-bind': 'urn:ietf:params:xml:ns:xmpp-bind',
+ 'xmpp-session': 'urn:ietf:params:xml:ns:xmpp-sasl'
+};
+
+
+function JID(str) {
+ var start = str.indexOf('@') + 1, end = str.lastIndexOf('/');
+ this.user = start > 0 ? str.substring(0, start - 1) : '';
+ this.resource = end > 0 ? str.substring(end + 1) : '';
+ this.host = str.substring(start, end > 0 ? end : undefined);
+}
+exports.JID = JID;
+JID.prototype.asJID = function() {
+ var jid = this.host;
+ if (this.user) jid = this.user + '@' + jid;
+ if (this.resource) jid += '/' + this.resource;
+ return jid;
+};
+
+
+
+function Connection(config) {
+ this.config = config;
+
+ this.config.port = config.port || 5222;
+
+ this.setupStream();
+ this.setupHandlers();
+
+ this.requests = {};
+ this.uniqueID = 1;
+}
+exports.Connection = Connection;
+util.inherits(Connection, EventEmitter);
+
+Connection.prototype.once = function(type, listener) {
+ var self = this;
+ self.on(type, function g() {
+ self.removeListener(type, g);
+ listener.apply(this, arguments);
+ });
+};
+
+Connection.prototype.createParser = function() {
+ var self = this;
+ var xml = require('xml');
+ this.parser = new xml.SaxParser(function(cb) {
+ var tree = [ ];
+
+ cb.onStartElementNS(function(elem, attrs, prefix, uri, namespaces) {
+ var element = [];
+ element.$name = elem;
+ // element.$prefix = prefix;
+ // element.$uri = uri;
+ // element.$namespaces = namespaces;
+
+ for (var i = 0; i < attrs.length; i++) {
+ element[attrs[i][0]] = attrs[i][1];
+ }
+
+ if (!tree.length) {
+ self.emit('stream', element);
+ }
+ else {
+ tree[0].push(element);
+ }
+ tree.unshift(element);
+ });
+
+ cb.onCharacters(function(chars) {
+ if (!('$body' in tree[0])) tree[0].$body = '';
+ tree[0].$body += chars;
+ });
+
+ cb.onEndElementNS(function(elem, prefix, uri) {
+ var element = tree.shift();
+ if (tree.length === 1) {
+ // if (element.$name === 'iq' && ) {
+ // xml.$render(xml.iq({ from: element.to, to: element.from, id: element.id, type: 'result' }));
+ // }
+ // else
+ if (element.id && (element.id in self.requests)) {
+ // If the response has an ID associated with it, call the corresponding
+ // function supplied when placing the request.
+ var command = self.requests[element.id];
+ command.call(self, element);
+ }
+ // else if (element.$name in self) {
+ // self[element.$name](element);
+ // }
+ else {
+ self.emit(element.$name, element);
+ }
+ }
+ });
+ });
+};
+
+Connection.prototype.setupStream = function() {
+ var self = this;
+
+ this.stream = net.createConnection(this.config.port, this.config.host);
+ this.stream.setEncoding('ascii');
+ this.stream.on('connect', function() { self.initStream(); });
+ this.stream.on('secure', function() { self.initStream(); });
+ this.stream.on('data', function(str) {
+ console.log(BLUE('in <- ' + str));
+ self.parser.parseString(str);
+ });
+};
+
+Connection.prototype.setupHandlers = function() {
+ this.on('error', function(error) {
+ console.log(error);
+ });
+
+ this.once('features', function(features) {
+ if (find(features, 'starttls').length)
+ this.startTLS();
+ else
+ this.emit('error', 'Server does not support STARTTLS.');
+
+ // DEBUG: Proceed anyway without SSL!
+ this.detectFeatures(features);
+ });
+};
+
+Connection.prototype.initStream = function() {
+ var self = this;
+
+ this.createParser();
+ this.once('stream', function(stream) {
+ self.id = stream.id;
+ });
+ this.stream.write('<stream:stream ' +
+ 'xmlns="jabber:client" ' +
+ 'xmlns:stream="http://etherx.jabber.org/streams" ' +
+ 'to="' + this.config.jid + '" ' +
+ 'version="1.0">');
+};
+
+Connection.prototype.detectFeatures = function(features) {
+ var auth = {};
+ find(features, 'mechanisms').forEach(function(mechanisms) {
+ find(mechanisms, 'mechanism').forEach(function(mechanism) {
+ auth[mechanism.$body] = true;
+ });
+ });
+ this.authMechanisms = auth;
+ this.secure = true;
+ this.emit('secure');
+};
+
+Connection.prototype.startTLS = function() {
+ this.stream.write('<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>');
+
+ this.once('proceed', function() {
+ this.stream.setSecure();
+ });
+
+ this.once('features', function(features) {
+ this.detectFeatures(features);
+ });
+};
+
+Connection.prototype.authenticate = function(user) {
+ this.user = user;
+ var plain = new Buffer('\0' + user.user + '\0' + user.password).toString('base64');
+ this.stream.write('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">' + plain + '</auth>');
+
+ function success() {
+ this.removeListener('failure', failure);
+ this.initStream();
+ this.emit('authenticated');
+ }
+ function failure(error) {
+ console.log(error);
+ this.emit('error', 'Failed to authenticate.');
+ this.removeListener('success', success);
+ }
+
+ this.once('success', success);
+ this.once('failure', failure);
+};
+
+Connection.prototype.end = function() {
+ console.log(PURPLE('out -> ' + '</stream:stream>'));
+ this.stream.write('</stream:stream>');
+ this.stream.end();
+}
+
+Connection.prototype.request = function(stanza, callback) {
+ var id = 'medium' + (this.uniqueID++);
+
+ if (callback) this.requests[id] = callback;
+ stanza.$attrs.id = id;
+ stanza = xml.$render(stanza);
+ console.log(PURPLE('out -> ' + stanza));
+ this.stream.write(stanza);
+};
+
+Connection.prototype.send = function(stanza) {
+ if (!stanza.$attrs.id) {
+ stanza.$attrs.id = 'medium' + (this.uniqueID++);
+ }
+ stanza = xml.$render(stanza);
+ console.log(PURPLE('out -> ' + stanza));
+ this.stream.write(stanza);
+};
+
+Connection.prototype.bind = function() {
+ this.request(
+ xml.iq({ type: 'set' }, xml.bind({ xmlns: 'urn:ietf:params:xml:ns:xmpp-bind' })),
+ function(response) {
+ this.user = new JID(path(response, 'iq.bind.jid').$body);
+ this.emit('bind');
+ }
+ );
+};
+
+Connection.prototype.startSession = function() {
+ this.request(
+ xml.iq({ type: 'set' }, xml.session({ xmlns: 'urn:ietf:params:xml:ns:xmpp-session' })),
+ function(response) {
+ this.session = true;
+ this.emit('session');
+ }
+ );
+};
+
+
+
+
+Connection.prototype.roomInfo = function(roomJID, callback) {
+ var query = xml.query({ xmlns: 'http://jabber.org/protocol/disco#info' });
+ var stanza = xml.iq({ type: 'get', to: roomJID }, query);
+ this.request(stanza, callback || noop);
+};
+
+Connection.prototype.roomItems = function(roomJID, callback) {
+ var query = xml.query({ xmlns: 'http://jabber.org/protocol/disco#items' });
+ var stanza = xml.iq({ type: 'get', to: roomJID }, query);
+ this.request(stanza, callback || noop);
+}
+
+Connection.prototype.roomPresence = function(nickJID, callback) {
+ var capability = xml.x({ xmlns: 'http://jabber.org/protocol/muc' });
+ var stanza = xml.presence({ to: nickJID }, capability);
+ this.request(stanza, callback || noop);
+};
+
+Connection.prototype.roomMessage = function(roomJID, message, callback) {
+ var body = xml.body(null, message);
+ var stanza = xml.message({ to: roomJID, type: 'groupchat' }, body);
+ this.request(stanza, callback || noop);
+}
+
+Connection.prototype.roomNick = function(nickJID, callback) {
+ var stanza = xml.presence({ to: nickJID });
+ this.request(stanza, callback || noop);
+};
+
+Connection.prototype.leaveRoom = function(roomJID) {
+ var stanza = xml.presence({ to: roomJID, type: 'unavailable' });
+ this.request(stanza);
+}
+
View
285 medium.js
@@ -1,17 +1,278 @@
var xmpp = require('./lib/xmpp');
var ircd = require('./lib/ircd');
+var helper = require('./lib/helper');
var config = require('./config.js');
-var jabberInfo = {
- host: 'jabber.org',
- jid: 'jabber.org',
- muc: 'conference.jabber.org'
-};
+require('./lib/color');
+
+var find = helper.xmlFind;
+var path = helper.xmlPath;
+var xml = helper.xml;
+
+var connections = [];
+
+process.on('SIGINT', function() {
+ if (!connections.length) process.exit();
+ else {
+ connections.forEach(function(kill) {
+ kill();
+ });
+ connections = [];
+ }
+});
+
+ircd.createServer(config.irc, function (irc) {
+ var jabber = new xmpp.Connection(config.xmpp);
+
+ // Rooms management.
+ var rooms = {};
+ function room(name) {
+ if (!(name in rooms)) rooms[name] = new Room(irc, jabber, name);
+ return rooms[name];
+ }
+
+ connections.push(function() {
+ console.log('got sigint');
+ for (var name in rooms) rooms[name].destroy();
+ jabber.end();
+ irc.end();
+ console.log(RED('KILLED ALL CLIENTS'));
+ delete jabber;
+ delete irc;
+ });
+
+
+ // ===========================================================================
+ // == Connection setup =======================================================
+
+ irc.on('login', function fn() {
+ if (!jabber.secure) return jabber.on('secure', fn.bind(this));
+
+ if (!jabber.authMechanisms['PLAIN']) {
+ jabber.emit('error', 'Server does not support authentication mechanism PLAIN.');
+ }
+ else {
+ jabber.authenticate(irc.user);
+ jabber.on('authenticated', jabber.bind);
+ jabber.on('bind', jabber.startSession);
+ }
+ });
+
+
+ // ===========================================================================
+ // == IRC to Jabber ==========================================================
+
+ irc.on('pong', function fn(line) {
+ if (!jabber.session) return jabber.on('session', fn.bind(this, line));
+
+ console.log('got pong command: ' + line);
+ });
+
+ irc.on('list', function fn(line) {
+ if (!jabber.session) return jabber.on('session', fn.bind(this, line));
+
+ console.log('got list command: ' + line);
+ });
+
+ irc.on('join', function fn(channel) {
+ if (!jabber.session) return jabber.on('session', fn.bind(this, channel));
+ room(channel.substring(1)).jabberJoin();
+ });
+
+ irc.on('part', function fn(channel) {
+ if (!jabber.session) return jabber.on('session', fn.bind(this, channel));
+ room(channel.substring(1)).jabberLeave();
+ });
+
+ irc.on('mode', function fn(mode) {
+ if (!jabber.session) return jabber.on('session', fn.bind(this, mode));
+
+ console.log('got mode command: ' + mode);
+ });
+
+ irc.on('privmsg', function fn(recipient, message) {
+ if (!jabber.session) return jabber.on('session', fn.bind(this, recipient, message));
+
+ if (recipient[0] === '#') {
+ room(recipient.substring(1)).jabberMessage(message);
+ }
+ else {
+ console.log(RED('PRIVMSG to users not yet implemented'));
+ console.log('got privmsg command: ' + recipient + ' message: ' + message);
+ }
+ });
+
+ irc.on('nick', function fn(nick) {
+ if (!jabber.session) return jabber.on('session', fn.bind(this, recipient, message));
+
+ // Broadcast nick change to all rooms.
+ for (var name in rooms) {
+ rooms[name].jabberNick(nick);
+ }
+ });
+
+ // ===========================================================================
+ // == Jabber to IRC ==========================================================
+
+ // Handle ping message to maintain connection.
+ jabber.on('iq', function(iq) {
+ for (var i = 0, type; i < iq.length; i++) switch (iq[i].$name) {
+ case 'ping':
+ var stanza = xml.iq({ from: iq.to, to: iq.from, id: iq.id, type: 'result' });
+ jabber.send(stanza);
+ break;
+ }
+ });
-var ircInfo = {
- created: new Date,
- name: 'medium',
- version: 'v0.1',
- host: 'localhost',
- port: 6667
-};
+ jabber.on('presence', function(presence) {
+ var jid = new xmpp.JID(presence.from);
+ if (jid.host === jabber.config.muc) {
+ // This is a presence notification for a MUC room.
+ room(jid.user).presence(jid, presence);
+ }
+ });
+
+ jabber.on('message', function(message) {
+ var jid = new xmpp.JID(message.from);
+
+ if (message.type === 'groupchat' && jid.host === jabber.config.muc) {
+ var body = path(message, 'message.body').$body;
+
+ if (body) {
+ // This is a PRIVMSG
+ var delay = path(message, 'message.delay');
+ if (delay && delay.stamp) body = '[' + delay.stamp + '] ' + body;
+ var sender = ircd.sanitizeNick(jid.resource);
+ room(jid.user).ircMessage(sender, body);
+ }
+ else {
+ // This may be a topic change?
+ console.log(RED('OTHER MESSAGES NOT IMPLEMENTED'));
+ }
+
+ }
+ else {
+ console.log(RED('MESSAGE TYPE NOT IMPLEMENTED: ' + message.type));
+ }
+ });
+});
+
+function Room(irc, jabber, name) {
+ this.irc = irc;
+ this.jabber = jabber;
+ this.name = name;
+ this.members = {};
+}
+
+Room.prototype = {
+ get JID() {
+ return this.name + '@' + config.xmpp.muc;
+ },
+
+ get nickJID() {
+ return this.name + '@' + config.xmpp.muc + '/' + this.irc.user.nick;
+ },
+
+ get ircType() {
+ return '='; // public
+ },
+
+ present: false,
+ jabberJoin: function() {
+ var room = this;
+
+ room.present = false;
+ room.jabber.roomPresence(room.nickJID, function(presence) {
+ room.jabber.emit('presence', presence);
+ // When join a room, the presence of the current user is announced last.
+ // That means, when we encounter a presence notification for ourselves, we
+ // know that the initial list of items is complete for this room and we can
+ // continue with the joining process.
+ room.present = true;
+
+ // Continue with the joining process; the user's presence is now signalled
+ // to jabber and the complete list of users should be stored in this object.
+ // We can now send the join message to IRC.
+ room.irc.command('JOIN', '#' + room.name);
+ room.ircNames();
+ });
+
+ return this;
+ },
+
+ ircNames: function() {
+ var nicks = [];
+ var nickJID = this.nickJID;
+ for (var jid in this.members) {
+ // Don't include yourself in the NAMES listing.
+ if (jid !== nickJID) nicks.push(ircd.sanitizeNick(this.members[jid].nick));
+ }
+
+ this.irc.reply('353', this.ircType + ' #' + this.name + ' :' + nicks.join(' '));
+ this.irc.reply('366', ':End of /NAMES list.');
+ },
+
+ ircMessage: function(sender, body) {
+ this.irc.command('PRIVMSG', '#' + this.name + ' :' + body, sender);
+ },
+
+ ircJoin: function(jid) {
+ var nick = ircd.sanitizeNick(jid.resource) + '!' + ircd.sanitizeUser(jid.resource);
+ this.irc.command('JOIN', '#' + this.name, nick);
+ },
+
+ ircPart: function(jid) {
+ var nick = ircd.sanitizeNick(jid.resource);
+ this.irc.command('PART', '#' + this.name, nick);
+ },
+
+ jabberMessage: function(body) {
+ this.jabber.roomMessage(this.JID, body, function() {
+ // Catch the reply so that it doesn't go into general dispatching
+ // When we send a message to the jabber server, it replies with the same
+ // message, but we don't want it to appear twice.
+ });
+ },
+
+ jabberNick: function(nick) {
+ this.jabber.roomNick(this.nickJID);
+ },
+
+ presence: function(jid, presence) {
+ var id = presence.from;
+ var existing = id in this.members;
+ if (!existing) this.members[id] = {};
+ var info = this.members[id];
+
+ var item = path(presence, 'presence.x.item');
+ info.role = item.role || '';
+ info.affiliation = item.affiliation || '';
+ info.nick = jid.resource;
+
+ // <presence from='foo@conference.localhost/othernick' to='irc2muc@localhost/16168164881294954282749820' type='unavailable'><x xmlns='http://jabber.org/protocol/muc#user'><item affiliation='none' role='participant' nick='bar'/><status code='303'/></x></presence>
+
+
+ if (this.present) {
+ // User already joined. This is a notification for a new user.
+
+ if (presence.type === 'unavailable') {
+ this.ircPart(jid);
+ delete this.members[id];
+ }
+ else if (!existing) {
+ this.ircJoin(jid);
+ }
+ else {
+ console.log(RED('UNKNOWN PRESENCE OF USER'));
+ }
+ }
+ },
+
+ jabberLeave: function() {
+ this.jabber.leaveRoom(this.JID);
+ },
+
+ destroy: function() {
+ this.jabberLeave();
+ }
+};

0 comments on commit 14219d1

Please sign in to comment.
Something went wrong with that request. Please try again.