diff --git a/index.js b/index.js index 7ab997d..c23b2aa 100755 --- a/index.js +++ b/index.js @@ -12,7 +12,9 @@ var replies = require('irc-replies'); * Core plugins. */ +var authenticate = require('./lib/plugins/authenticate'); var away = require('./lib/plugins/away'); +var cap = require('./lib/plugins/cap'); var disconnect = require('./lib/plugins/disconnect'); var errors = require('./lib/plugins/errors'); var invite = require('./lib/plugins/invite'); @@ -27,6 +29,8 @@ var part = require('./lib/plugins/part'); var pong = require('./lib/plugins/pong'); var privmsg = require('./lib/plugins/privmsg'); var quit = require('./lib/plugins/quit'); +var rplloggedin = require('./lib/plugins/rplloggedin'); +var rplsaslsuccess = require('./lib/plugins/rplsaslsuccess'); var topic = require('./lib/plugins/topic'); var welcome = require('./lib/plugins/welcome'); var whois = require('./lib/plugins/whois'); @@ -55,7 +59,9 @@ function Client(stream, parser, encoding) { this.parser.on('message', this.onmessage.bind(this)); stream.pipe(this.parser); this.setMaxListeners(100); + this.use(authenticate()); this.use(away()); + this.use(cap()); this.use(disconnect()); this.use(errors()); this.use(invite()); @@ -70,6 +76,8 @@ function Client(stream, parser, encoding) { this.use(pong()); this.use(privmsg()); this.use(quit()); + this.use(rplloggedin()); + this.use(rplsaslsuccess()); this.use(topic()); this.use(welcome()); this.use(whois()); @@ -88,11 +96,53 @@ Client.prototype.__proto__ = Emitter.prototype; * @param {Function} [fn] * @api public */ - Client.prototype.write = function(str, fn) { + debug('write %s', str); this.stream.write(str + '\r\n', fn); }; +/** + * CAP REQ + * + * @param {String} reqs + * @api public + */ +Client.prototype.cap_req = function(reqs) { + this.write('CAP REQ :' + reqs); +}; + +/** + * CAP END + * + * @api public + */ +Client.prototype.cap_end = function() { + this.write('CAP END'); +}; + +/** + * AUTHENTICATE + * + * @param {String} msg + * @api public + */ +Client.prototype.authenticate = function(msg) { + this.write('AUTHENTICATE ' + msg) +}; + +/** + * AUTHENTICATE base64(\0\0) + * + * @param {String} username + * @param {String} password + * @api public + */ +Client.prototype.authenticate64 = function(username, password) { + var b = new Buffer(username + "\0" + username + "\0" + password, 'utf8'); + var b64 = b.toString('base64'); + this.write('AUTHENTICATE ' + b64) +}; + /** * PASS * diff --git a/lib/plugins/authenticate.js b/lib/plugins/authenticate.js new file mode 100644 index 0000000..9ef496d --- /dev/null +++ b/lib/plugins/authenticate.js @@ -0,0 +1,17 @@ +/** + * AUTHENTICATE plugin to emit "authenticate" events. + * + * @return {Function} + * @api public + */ + +module.exports = function () { + return function (irc) { + irc.on('data', function (msg) { + if ('AUTHENTICATE' != msg.command) return; + var e = {}; + e.message = msg.params; + irc.emit('authenticate', e); + }); + } +}; diff --git a/lib/plugins/cap.js b/lib/plugins/cap.js new file mode 100644 index 0000000..77f44ab --- /dev/null +++ b/lib/plugins/cap.js @@ -0,0 +1,20 @@ +/** + * CAP plugin to emit "cap" events. + * + * @return {Function} + * @api public + */ + +module.exports = function () { + return function (irc) { + irc.on('data', function (msg) { + if ('CAP' != msg.command) return; + var params = msg.params.split(' '); + var e = {}; + e.nick = params[0]; + e.command = params[1]; + e.capabilities = msg.trailing.split(' '); + irc.emit('cap', e); + }); + } +}; diff --git a/lib/plugins/rplloggedin.js b/lib/plugins/rplloggedin.js new file mode 100644 index 0000000..23adcf7 --- /dev/null +++ b/lib/plugins/rplloggedin.js @@ -0,0 +1,17 @@ +/** + * RPL_LOGGEDIN plugin to emit "loggedin" events. + * + * @return {Function} + * @api public + */ + +module.exports = function () { + return function (irc) { + irc.on('data', function (msg) { + if ('900' != msg.command && 'RPL_LOGGEDIN' != msg.command) return; + var e = {}; + e.message = msg.trailing; + irc.emit('loggedin', e); + }); + } +}; diff --git a/lib/plugins/rplsaslsuccess.js b/lib/plugins/rplsaslsuccess.js new file mode 100644 index 0000000..da3b718 --- /dev/null +++ b/lib/plugins/rplsaslsuccess.js @@ -0,0 +1,17 @@ +/** + * RPL_SASLSUCCESS plugin to emit "saslsuccess" events. + * + * @return {Function} + * @api public + */ + +module.exports = function () { + return function (irc) { + irc.on('data', function (msg) { + if ('903' != msg.command && 'RPL_SASLSUCCESS' != msg.command) return; + var e = {}; + e.message = msg.trailing; + irc.emit('saslsuccess', e); + }); + } +}; diff --git a/test/authenticate.js b/test/authenticate.js new file mode 100644 index 0000000..d056e25 --- /dev/null +++ b/test/authenticate.js @@ -0,0 +1,22 @@ +var irc = require('..'); +var Stream = require('stream').PassThrough; + +describe('authenticate()', function () { + var stream, client; + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on AUTHENTICATE', function () { + it('should emit "AUTHENTICATE"', function (done) { + client.on('authenticate', function (e) { + e.message.should.equal('+') + done(); + }); + + stream.write('AUTHENTICATE +\r\n'); + }); + }); +}); diff --git a/test/away.js b/test/away.js index d987979..8882424 100644 --- a/test/away.js +++ b/test/away.js @@ -2,11 +2,17 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('away()', function() { + var stream, client; + + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on RPL_AWAY', function() { it('should emit "away"', function(done) { - var stream = new Stream; - var client = irc(stream); - client.on('away', function(e) { e.nick.should.equal('colinm'); e.message.should.eql('brb food time'); diff --git a/test/cap.js b/test/cap.js new file mode 100644 index 0000000..58710cd --- /dev/null +++ b/test/cap.js @@ -0,0 +1,46 @@ +var irc = require('..'); +var Stream = require('stream').PassThrough; + +describe('cap()', function () { + var stream, client; + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on CAP', function () { + it('should emit "CAP" and parse ACK with capabilities', function (done) { + client.on('cap', function (e) { + e.nick.should.equal('jilles'); + e.command.should.eql('ACK'); + e.should.have.property('capabilities').with.lengthOf(1); + e.capabilities[0].should.eql('sasl'); + done(); + }); + + stream.write(':jaguar.test CAP jilles ACK :sasl\r\n'); + }); + it('should emit "CAP" and parse ACK with multiple capabilities', function (done) { + client.on('cap', function (e) { + e.nick.should.equal('jilles'); + e.command.should.eql('ACK'); + e.should.have.property('capabilities').with.lengthOf(3); + done(); + }); + + stream.write(':jaguar.test CAP jilles ACK :sasl foo bar\r\n'); + }); + it('should emit "CAP" and parse LS with capabilites', function (done) { + client.on('cap', function (e) { + e.nick.should.eql('*'); + e.command.should.eql('LS'); + e.should.have.property('capabilities').with.lengthOf(2); + e.capabilities.should.containDeep(['multi-prefix', 'sasl']); + done(); + }); + + stream.write(':jaguar.test CAP * LS :multi-prefix sasl\r\n'); + }); + }); +}); diff --git a/test/invite.js b/test/invite.js index bca468f..8652a52 100644 --- a/test/invite.js +++ b/test/invite.js @@ -2,11 +2,17 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('invite()', function() { + var stream, client; + + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on INVITE', function() { it('should emit "invite"', function(done) { - var stream = new Stream; - var client = irc(stream); - client.on('invite', function(e) { e.from.should.equal('test'); e.to.should.equal('astranger'); diff --git a/test/join.js b/test/join.js index bc334c3..07b9ac4 100644 --- a/test/join.js +++ b/test/join.js @@ -2,11 +2,17 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('join()', function() { + var stream, client; + + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on JOIN', function() { it('should emit "join"', function(done) { - var stream = new Stream; - var client = irc(stream); - client.on('join', function(e) { e.nick.should.equal('tjholowaychuk'); e.channel.should.equal('#express'); diff --git a/test/kick.js b/test/kick.js index 933ec80..46fd1d1 100644 --- a/test/kick.js +++ b/test/kick.js @@ -2,11 +2,17 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('kick()', function() { + var stream, client; + + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on KICK', function() { it('should emit "kick"', function(done) { - var stream = new Stream; - var client = irc(stream); - client.on('kick', function(e) { e.nick.should.equal('tjholowaychuk'); e.client.should.equal('tobi'); diff --git a/test/names.js b/test/names.js index d8c5bfc..d47a3fc 100644 --- a/test/names.js +++ b/test/names.js @@ -2,11 +2,17 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('names()', function() { + var stream, client; + + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('client.names(chan, fn)', function() { it('should respond with user names', function(done) { - var stream = new Stream; - var client = irc(stream); - client.names('#luna-lang', function(err, names) { if (err) return done(err); names.should.eql([ @@ -30,9 +36,6 @@ describe('names()', function() { }); it('should emit "names"', function(done) { - var stream = new Stream; - var client = irc(stream); - client.on('names', function(e) { e.channel.should.equal('#luna-lang'); e.names.should.eql([ @@ -53,9 +56,6 @@ describe('names()', function() { }); it('should retain ~ / @ / % / +', function(done) { - var stream = new Stream; - var client = irc(stream); - client.on('names', function(e) { e.channel.should.equal('##luna-lang'); e.names.should.eql([ diff --git a/test/nick.js b/test/nick.js index 46a60b1..b0efb7d 100644 --- a/test/nick.js +++ b/test/nick.js @@ -2,12 +2,17 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('nick()', function() { - describe('on NICK', function() { + var stream, client; - it('should emit "nick"', function(done) { - var stream = new Stream; - var client = irc(stream); + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on NICK', function() { + it('should emit "nick"', function(done) { client.on('nick', function(e) { e.nick.should.eql('colinm'); e.new.should.equal('cmilhench'); diff --git a/test/notice.js b/test/notice.js index 062e41b..e022982 100644 --- a/test/notice.js +++ b/test/notice.js @@ -2,10 +2,18 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('notice()', function() { + + var stream, client; + + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on NOTICE', function() { it('should emit "notice"', function(done) { - var stream = new Stream; - var client = irc(stream); var n = 0; client.on('notice', function(e) { @@ -21,7 +29,7 @@ describe('notice()', function() { 'This nickname is registered. ', 'Please choose a different nickname, ', 'or identify via /msg NickServ identify .'].join('')); - break; + break; case 2: e.message.should.equal([ 'You have 30 seconds to identify to your nickname ', diff --git a/test/part.js b/test/part.js index 210e2f5..db766e8 100644 --- a/test/part.js +++ b/test/part.js @@ -2,11 +2,17 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('part()', function() { + var stream, client; + + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on PART', function() { it('should emit "part"', function(done) { - var stream = new Stream; - var client = irc(stream); - client.on('part', function(e) { e.nick.should.equal('tjholowaychuk'); e.channels.should.eql(['#express']); diff --git a/test/pong.js b/test/pong.js index 6abee4a..243c0af 100644 --- a/test/pong.js +++ b/test/pong.js @@ -2,10 +2,17 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('pong()', function() { + var stream, client; + + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on PING', function() { it('should respond with PONG', function(done) { - var stream = new Stream; - var client = irc(stream); var n = 0; stream.on('data', function(chunk) { diff --git a/test/privmsg.js b/test/privmsg.js index 2cbd041..2dc615e 100644 --- a/test/privmsg.js +++ b/test/privmsg.js @@ -2,11 +2,17 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('privmsg()', function() { + var stream, client; + + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on PRIVMSG', function() { it('should emit "message"', function(done) { - var stream = new Stream; - var client = irc(stream); - client.on('message', function(e) { e.from.should.equal('tobi'); e.to.should.equal('loki'); diff --git a/test/quit.js b/test/quit.js index 39a0f71..5a62ffa 100644 --- a/test/quit.js +++ b/test/quit.js @@ -2,11 +2,17 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('quit()', function() { + var stream, client; + + beforeEach(function() { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on QUIT', function() { it('should emit "quit"', function(done) { - var stream = new Stream; - var client = irc(stream); - client.on('quit', function(e) { e.nick.should.equal('tobi'); e.message.should.eql('Remote host closed the connection'); diff --git a/test/rplloggedin.js b/test/rplloggedin.js new file mode 100644 index 0000000..8034b78 --- /dev/null +++ b/test/rplloggedin.js @@ -0,0 +1,22 @@ +var irc = require('..'); +var Stream = require('stream').PassThrough; + +describe('rplloggedin()', function () { + var stream, client; + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on successful login with sasl', function () { + it('should emit "LOGGEDIN"', function (done) { + client.on('loggedin', function (e) { + e.message.should.equal('You are now logged in as jilles'); + done(); + }); + + stream.write(':jaguar.test 900 jilles jilles!jilles@localhost.stack.nl jilles :You are now logged in as jilles\r\n'); + }); + }); +}); diff --git a/test/rplsaslsuccess.js b/test/rplsaslsuccess.js new file mode 100644 index 0000000..8773971 --- /dev/null +++ b/test/rplsaslsuccess.js @@ -0,0 +1,22 @@ +var irc = require('..'); +var Stream = require('stream').PassThrough; + +describe('rplsaslsuccess()', function () { + var stream, client; + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on successful login with sasl', function () { + it('should emit "SASLSUCCESS"', function (done) { + client.on('saslsuccess', function (e) { + e.message.should.equal('SASL authentication successful'); + done(); + }); + + stream.write(':jaguar.test 903 jilles :SASL authentication successful\r\n'); + }); + }); +}); diff --git a/test/topic.js b/test/topic.js index 7645979..61e742b 100644 --- a/test/topic.js +++ b/test/topic.js @@ -2,11 +2,17 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('topic()', function() { + var stream, client; + + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on TOPIC', function() { it('should emit "topic"', function(done) { - var stream = new Stream; - var client = irc(stream); - client.on('topic', function(e) { e.nick.should.equal('tobi'); e.channel.should.eql('#slate'); diff --git a/test/welcome.js b/test/welcome.js index 33b40b8..6dbc5e3 100644 --- a/test/welcome.js +++ b/test/welcome.js @@ -2,10 +2,17 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('welcome()', function() { + var stream, client; + + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('on RPL_WELCOME', function() { it('should set client.me to the users nick', function() { - var stream = new Stream; - var client = irc(stream); stream.write(':cameron.freenode.net 001 tobi :Welcome to the freenode Internet Relay Chat Network tobi\r\n'); process.nextTick(function() { client.me.should.equal('tobi'); @@ -13,9 +20,6 @@ describe('welcome()', function() { }); it('should emit "welcome"', function(done) { - var stream = new Stream; - var client = irc(stream); - client.on('welcome', function(nick) { nick.should.equal('tobi'); done(); diff --git a/test/whois.js b/test/whois.js index ea6deac..426e6cd 100644 --- a/test/whois.js +++ b/test/whois.js @@ -2,13 +2,20 @@ var irc = require('..'); var Stream = require('stream').PassThrough; describe('whois()', function() { + var stream, client; + + beforeEach(function () { + stream = new Stream; + stream.setTimeout = function () { + }; + client = irc(stream); + }); + describe('client.whois(target, mask, fn)', function() { it('should respond with user info', function(done) { - var stream = new Stream; - var client = irc(stream); - client.whois('colinm', function(err, e) { - if (err) return done(err);e.hostname.should.equal('client.host.net'); + if (err) return done(err); + e.hostname.should.equal('client.host.net'); e.username.should.equal('~colinm'); e.realname.should.equal('Colin Milhench'); e.server.should.equal('other.host.net'); @@ -35,9 +42,6 @@ describe('whois()', function() { }); it('should emit "info"', function(done) { - var stream = new Stream; - var client = irc(stream); - client.on('whois', function(err, e) { e.hostname.should.equal('client.host.net'); e.username.should.equal('~colinm'); @@ -63,10 +67,8 @@ describe('whois()', function() { stream.write(':irc.host.net 318 me colinm :End of /WHOIS list.\r\n'); }); - it('should emit "info"', function(done) { - var stream = new Stream; - var client = irc(stream); + it('should emit "info"', function(done) { client.whois('colinm'); client.on('whois', function(err, e) { @@ -95,8 +97,6 @@ describe('whois()', function() { }); it('should err with No such nick/channel', function(done) { - var stream = new Stream; - var client = irc(stream); client.whois('nonick'); client.on('whois', function(err, e) { err.should.equal('No such nick/channel'); @@ -107,8 +107,6 @@ describe('whois()', function() { }); it('should err with No such server', function(done) { - var stream = new Stream; - var client = irc(stream); client.whois('nonick', function(err, e) { err.should.equal('No such server'); done(); @@ -117,8 +115,6 @@ describe('whois()', function() { }); it('should err with Not enough parameters', function(done) { - var stream = new Stream; - var client = irc(stream); client.on('whois', function(err, e) { err.should.equal('Not enough parameters'); done();