├── .gitignore ├── .jscsrc ├── docs ├── index.rst ├── Makefile ├── make.bat ├── conf.py └── API.rst ├── .travis.yml ├── test ├── test-parse-line.js ├── data │ ├── ircd.key │ ├── ircd.pem │ └── fixtures.json ├── test-double-crlf.js ├── test-auditorium.js ├── test-quit.js ├── test-433-before-001.js ├── test-convert-encoding.js ├── helpers.js ├── test-irc.js └── test-mode.js ├── test.js ├── lib ├── colors.js ├── parse_message.js ├── cycling_ping_timer.js ├── codes.js └── irc.js ├── package.json ├── example ├── bot.js └── secure.js ├── CONTRIBUTING.md ├── README.md ├── COPYING └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules/ 3 | _build/ 4 | .idea/ 5 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "validateIndentation": 4, 3 | "disallowMultipleVarDecl": null, 4 | "requireMultipleVarDecl": null, 5 | "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties", 6 | "safeContextKeyword": "self" 7 | } 8 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to node-irc's documentation! 2 | ==================================== 3 | 4 | .. include:: ../README.rst 5 | 6 | More detailed docs: 7 | ------------------- 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | 12 | API 13 | 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "4" 6 | - "5" 7 | - "6" 8 | - "7" 9 | before_install: 10 | - sudo apt-get -y install libicu-dev 11 | - npm install -g npm 12 | script: 13 | - "npm run-script lint" 14 | - "npm test" 15 | notifications: 16 | irc: "chat.freenode.net##node-irc" 17 | -------------------------------------------------------------------------------- /test/test-parse-line.js: -------------------------------------------------------------------------------- 1 | var parseMessage = require('../lib/parse_message'); 2 | var test = require('tape'); 3 | 4 | var testHelpers = require('./helpers'); 5 | 6 | test('irc.parseMessage', function(t) { 7 | var checks = testHelpers.getFixtures('parse-line'); 8 | 9 | Object.keys(checks).forEach(function(line) { 10 | var stripColors = false; 11 | if (checks[line].hasOwnProperty('stripColors')) { 12 | stripColors = checks[line].stripColors; 13 | delete checks[line].stripColors; 14 | } 15 | t.equal( 16 | JSON.stringify(checks[line]), 17 | JSON.stringify(parseMessage(line, stripColors)), 18 | line + ' parses correctly' 19 | ); 20 | }); 21 | t.end(); 22 | }); 23 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var irc = require('./lib/irc.js'); 4 | var util = require('util'); 5 | var color = require('ansi-color').set; 6 | 7 | var c = new irc.Client( 8 | 'irc.dollyfish.net.nz', 9 | 'nodebot', 10 | { 11 | channels: ['#test'], 12 | //debug: true 13 | } 14 | ); 15 | 16 | c.addListener('raw', function(message) { console.log('raw: ', message) }); 17 | c.addListener('error', function(message) { console.log(color('error: ', 'red'), message) }); 18 | 19 | var repl = require('repl').start('> '); 20 | repl.context.repl = repl; 21 | repl.context.util = util; 22 | repl.context.irc = irc; 23 | repl.context.c = c; 24 | 25 | repl.inputStream.addListener('close', function() { 26 | console.log("\nClosing session"); 27 | c.disconnect('Closing session'); 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /lib/colors.js: -------------------------------------------------------------------------------- 1 | var codes = { 2 | white: '\u000300', 3 | black: '\u000301', 4 | dark_blue: '\u000302', 5 | dark_green: '\u000303', 6 | light_red: '\u000304', 7 | dark_red: '\u000305', 8 | magenta: '\u000306', 9 | orange: '\u000307', 10 | yellow: '\u000308', 11 | light_green: '\u000309', 12 | cyan: '\u000310', 13 | light_cyan: '\u000311', 14 | light_blue: '\u000312', 15 | light_magenta: '\u000313', 16 | gray: '\u000314', 17 | light_gray: '\u000315', 18 | 19 | bold: '\u0002', 20 | underline: '\u001f', 21 | 22 | reset: '\u000f' 23 | }; 24 | exports.codes = codes; 25 | 26 | function wrap(color, text, resetColor) { 27 | if (codes[color]) { 28 | text = codes[color] + text; 29 | text += (codes[resetColor]) ? codes[resetColor] : codes.reset; 30 | } 31 | return text; 32 | } 33 | exports.wrap = wrap; 34 | -------------------------------------------------------------------------------- /test/data/ircd.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWwIBAAKBgQDH5pYbcECKUrbRbUXKUu7lMCgb9UkPi4+Ur9f0LYdspHZJlv0S 3 | yBn4RpJOl8EsMhWI+houY3mBlcCL/DwiGfMDk5TSomyrI6eONFworokTJpG2h0f0 4 | cWnGdDW1zu8Z1odo047NWzwwv2mU03fkZmzfCclAzjKkDMMqP34mPl5TnwIDAQAB 5 | AoGAJslK3tAM9cnOxxvYqsUkrTuGzMXvAyElHshvsmUTHbVbbjPprrc8sruer7kq 6 | NhURsJ42bkHG1ankzkSGtmcqi3LdBBhVLm5gyog2JxQlTxvUVOPvyrOsQkl3uDwL 7 | aZqGTESHlLx7jhOKgiImqo0uGxNy46tzsHbpFGAeqTYcYKECQQD6faxqytMpMc/h 8 | zcrWsRhe7Omj5D6VdrbkGkM8razn4Oyr42p8Xylcde2MlnTiTAL5ElxlLd4PYsLD 9 | hKme/M5tAkEAzEwT1GU7CYjPdHHfsHUbDIHBh0BOJje2TXhDOa5tiZbOZevIk6TZ 10 | V6p/9zjLe5RAc/dpzHv1C+vQOkhgvoNyuwJARwjGkU5NTXxTwGwUnoeAKsMyioia 11 | etY8jTkpYha6VtOBKkmGlBiEaTUEFX9BTD9UBIABdavpMiHGq51+YJi+jQJAGYic 12 | pdwtH8jwnM4qtgQ86DhDduMLoW0vJMmWJVxuplap30Uz4XgmDfXqXnzDueNSluvi 13 | VkNb4iyL7uzi4ozNRwJALT0vP65RQ2d7OUEwB4XZFExKYzHADiFtw0NZtcWRW6y3 14 | rN0uXMxEZ6vRQurVjO9GhB76fAo/UooX0MVF0ShFNQ== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /test/data/ircd.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICojCCAgugAwIBAgIJAMid3M25tUeUMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV 3 | BAYTAlpaMREwDwYDVQQIDAhJbnRlcm5ldDEPMA0GA1UEBwwGZ2l0aHViMREwDwYD 4 | VQQKDAhub2RlLWlyYzEQMA4GA1UECwwHdGVzdGluZzESMBAGA1UEAwwJbG9jYWxo 5 | b3N0MB4XDTE1MDExMjIzNDg0MloXDTI1MDEwOTIzNDg0MlowajELMAkGA1UEBhMC 6 | WloxETAPBgNVBAgMCEludGVybmV0MQ8wDQYDVQQHDAZnaXRodWIxETAPBgNVBAoM 7 | CG5vZGUtaXJjMRAwDgYDVQQLDAd0ZXN0aW5nMRIwEAYDVQQDDAlsb2NhbGhvc3Qw 8 | gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMfmlhtwQIpSttFtRcpS7uUwKBv1 9 | SQ+Lj5Sv1/Qth2ykdkmW/RLIGfhGkk6XwSwyFYj6Gi5jeYGVwIv8PCIZ8wOTlNKi 10 | bKsjp440XCiuiRMmkbaHR/RxacZ0NbXO7xnWh2jTjs1bPDC/aZTTd+RmbN8JyUDO 11 | MqQMwyo/fiY+XlOfAgMBAAGjUDBOMB0GA1UdDgQWBBTUaumzrTJrl1goRRzOGgEO 12 | VNKFmjAfBgNVHSMEGDAWgBTUaumzrTJrl1goRRzOGgEOVNKFmjAMBgNVHRMEBTAD 13 | AQH/MA0GCSqGSIb3DQEBBQUAA4GBAGKppBE9mjk2zJPSxPcHl3RSpnPs5ZkuBLnK 14 | rxZ2bR9VJhoQEwtiZRxkSXSdooj3eJgzMobYMEhSvFibUeBuIppB7oacys2Bd+O1 15 | xzILcbgEPqsk5JFbYT9KD8r+sZy5Wa1A39eNkmdD/oWt9Mb1PLrDfM/melvZ9/vW 16 | oMSmMipK 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /test/test-double-crlf.js: -------------------------------------------------------------------------------- 1 | var net = require('net'); 2 | 3 | var irc = require('../lib/irc'); 4 | var test = require('tape'); 5 | 6 | var testHelpers = require('./helpers'); 7 | 8 | test('sent messages ending with double CRLF', function(t) { 9 | var mock = testHelpers.MockIrcd(); 10 | var client = new irc.Client('localhost', 'testbot', { debug: true}); 11 | 12 | var expected = testHelpers.getFixtures('double-CRLF'); 13 | 14 | t.plan(expected.sent.length + expected.received.length); 15 | 16 | mock.server.on('connection', function() { 17 | mock.send(expected.received[0][0]); 18 | }); 19 | 20 | client.on('registered', function() { 21 | t.equal(mock.outgoing[0], expected.received[0][0], expected.received[0][1]); 22 | client.disconnect(); 23 | }); 24 | 25 | mock.on('end', function() { 26 | var msgs = mock.getIncomingMsgs(); 27 | 28 | for (var i = 0; i < msgs.length; i++) { 29 | t.equal(msgs[i], expected.sent[i][0], expected.sent[i][1]); 30 | } 31 | mock.close(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/test-auditorium.js: -------------------------------------------------------------------------------- 1 | var net = require('net'); 2 | 3 | var irc = require('../lib/irc'); 4 | var test = require('tape'); 5 | 6 | var testHelpers = require('./helpers'); 7 | 8 | test('user gets opped in auditorium', function(t) { 9 | var mock = testHelpers.MockIrcd(); 10 | var client = new irc.Client('localhost', 'testbot', {debug: true}); 11 | 12 | client.on('+mode', function(channel, by, mode, argument) { 13 | if (channel == '#auditorium' && argument == 'user') { 14 | client.disconnect(); 15 | } 16 | }); 17 | 18 | mock.server.on('connection', function() { 19 | // Initiate connection 20 | mock.send(':localhost 001 testbot :Welcome to the Internet Relay Chat Network testbot\r\n'); 21 | 22 | // Set prefix modes 23 | mock.send(':localhost 005 testbot PREFIX=(ov)@+ CHANTYPES=#& :are supported by this server\r\n'); 24 | 25 | // Force join into auditorium 26 | mock.send(':testbot JOIN #auditorium\r\n'); 27 | 28 | // +o the invisible user 29 | mock.send(':ChanServ MODE #auditorium +o user\r\n'); 30 | }); 31 | 32 | mock.on('end', function() { 33 | mock.close(); 34 | t.end(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/test-quit.js: -------------------------------------------------------------------------------- 1 | var irc = require('../lib/irc'); 2 | var test = require('tape'); 3 | 4 | var testHelpers = require('./helpers'); 5 | 6 | test('connect and quit with message', function(t) { 7 | var client, mock, expected; 8 | 9 | mock = testHelpers.MockIrcd(); 10 | client = new irc.Client('localhost', 'testbot', {debug: true}); 11 | 12 | expected = testHelpers.getFixtures('quit'); 13 | 14 | t.plan(expected.sent.length + expected.received.length + 1); 15 | 16 | mock.server.on('connection', function() { 17 | mock.send(':localhost 001 testbot :Welcome to the Internet Relay Chat Network testbot\r\n'); 18 | }); 19 | 20 | client.on('registered', function() { 21 | t.equal(mock.outgoing[0], expected.received[0][0], expected.received[0][1]); 22 | client.disconnect('quitting as a test', function() {}); 23 | }); 24 | 25 | mock.on('end', function() { 26 | var msgs = mock.getIncomingMsgs(); 27 | 28 | t.equal(msgs.length, expected.sent.length, 'Server received the correct amount of messages.') 29 | 30 | for (var i = 0; i < msgs.length; i++) { 31 | t.equal(msgs[i], expected.sent[i][0], expected.sent[i][1]); 32 | } 33 | mock.close(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "irc", 3 | "description": "An IRC client library for node", 4 | "version": "0.5.2", 5 | "author": "Martyn Smith ", 6 | "scripts": { 7 | "test": "./node_modules/faucet/bin/cmd.js test/test-*.js", 8 | "lint": "./node_modules/jscs/bin/jscs --preset=airbnb */*.js" 9 | }, 10 | "contributors": [ 11 | "Fionn Kelleher ", 12 | "xAndy ", 13 | "Mischa Spiegelmock ", 14 | "Justin Gallardo ", 15 | "Chris Nehren ", 16 | "Henri Niemeläinen ", 17 | "Alex Miles ", 18 | "Simmo Saan " 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "http://github.com/martynsmith/node-irc" 23 | }, 24 | "bugs": { 25 | "mail": "martyn@dollyfish.net.nz", 26 | "url": "http://github.com/martynsmith/node-irc/issues" 27 | }, 28 | "main": "lib/irc", 29 | "engines": { 30 | "node": ">=0.10.0" 31 | }, 32 | "license": "GPL-3.0", 33 | "dependencies": { 34 | "irc-colors": "^1.1.0" 35 | }, 36 | "optionalDependencies": { 37 | "iconv": "~2.2.1", 38 | "node-icu-charset-detector": "~0.2.0" 39 | }, 40 | "devDependencies": { 41 | "ansi-color": "0.2.1", 42 | "faucet": "0.0.1", 43 | "jscs": "1.9.0", 44 | "tape": "^3.0.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/test-433-before-001.js: -------------------------------------------------------------------------------- 1 | var irc = require('../lib/irc'); 2 | var test = require('tape'); 3 | 4 | var testHelpers = require('./helpers'); 5 | 6 | test('connect and sets hostmask when nick in use', function(t) { 7 | var client, mock, expected; 8 | 9 | mock = testHelpers.MockIrcd(); 10 | client = new irc.Client('localhost', 'testbot', {debug: true}); 11 | 12 | expected = testHelpers.getFixtures('433-before-001'); 13 | 14 | t.plan(expected.sent.length + expected.received.length + expected.clientInfo.length); 15 | 16 | mock.server.on('connection', function() { 17 | mock.send(':localhost 433 * testbot :Nickname is already in use.\r\n') 18 | mock.send(':localhost 001 testbot1 :Welcome to the Internet Relay Chat Network testbot\r\n'); 19 | }); 20 | 21 | client.on('registered', function() { 22 | t.equal(mock.outgoing[0], expected.received[0][0], expected.received[0][1]); 23 | t.equal(mock.outgoing[1], expected.received[1][0], expected.received[1][1]); 24 | client.disconnect(function() { 25 | t.equal(client.hostMask, 'testbot', 'hostmask is as expected after 433'); 26 | t.equal(client.nick, 'testbot1', 'nick is as expected after 433'); 27 | t.equal(client.maxLineLength, 482, 'maxLineLength is as expected after 433'); 28 | }); 29 | }); 30 | 31 | mock.on('end', function() { 32 | var msgs = mock.getIncomingMsgs(); 33 | 34 | for (var i = 0; i < msgs.length; i++) { 35 | t.equal(msgs[i], expected.sent[i][0], expected.sent[i][1]); 36 | } 37 | mock.close(); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/test-convert-encoding.js: -------------------------------------------------------------------------------- 1 | var irc = require('../lib/irc'); 2 | var test = require('tape'); 3 | var testHelpers = require('./helpers'); 4 | var checks = testHelpers.getFixtures('convert-encoding'); 5 | var bindTo = { opt: { encoding: 'utf-8' } }; 6 | 7 | test('irc.Client.convertEncoding old', function(assert) { 8 | var convertEncoding = function(str) { 9 | var self = this; 10 | 11 | if (self.opt.encoding) { 12 | var charsetDetector = require('node-icu-charset-detector'); 13 | var Iconv = require('iconv').Iconv; 14 | var charset = charsetDetector.detectCharset(str).toString(); 15 | var to = new Iconv(charset, self.opt.encoding); 16 | 17 | return to.convert(str); 18 | } else { 19 | return str; 20 | } 21 | }.bind(bindTo); 22 | 23 | checks.causesException.forEach(function iterate(line) { 24 | var causedException = false; 25 | try { 26 | convertEncoding(line); 27 | } catch (e) { 28 | causedException = true; 29 | } 30 | 31 | assert.equal(causedException, true, line + ' caused exception'); 32 | }); 33 | 34 | assert.end(); 35 | }); 36 | 37 | test('irc.Client.convertEncoding', function(assert) { 38 | var convertEncoding = irc.Client.prototype.convertEncoding.bind(bindTo); 39 | 40 | checks.causesException.forEach(function iterate(line) { 41 | var causedException = false; 42 | 43 | try { 44 | convertEncoding(line); 45 | } catch (e) { 46 | causedException = true; 47 | } 48 | 49 | assert.equal(causedException, false, line + ' didn\'t cause exception'); 50 | }); 51 | 52 | assert.end(); 53 | }); 54 | -------------------------------------------------------------------------------- /example/bot.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var irc = require('../'); 4 | 5 | var bot = new irc.Client('irc.dollyfish.net.nz', 'nodebot', { 6 | debug: true, 7 | channels: ['#test', '#othertest'] 8 | }); 9 | 10 | bot.addListener('error', function(message) { 11 | console.error('ERROR: %s: %s', message.command, message.args.join(' ')); 12 | }); 13 | 14 | bot.addListener('message#blah', function(from, message) { 15 | console.log('<%s> %s', from, message); 16 | }); 17 | 18 | bot.addListener('message', function(from, to, message) { 19 | console.log('%s => %s: %s', from, to, message); 20 | 21 | if (to.match(/^[#&]/)) { 22 | // channel message 23 | if (message.match(/hello/i)) { 24 | bot.say(to, 'Hello there ' + from); 25 | } 26 | if (message.match(/dance/)) { 27 | setTimeout(function() { bot.say(to, '\u0001ACTION dances: :D\\-<\u0001'); }, 1000); 28 | setTimeout(function() { bot.say(to, '\u0001ACTION dances: :D|-<\u0001'); }, 2000); 29 | setTimeout(function() { bot.say(to, '\u0001ACTION dances: :D/-<\u0001'); }, 3000); 30 | setTimeout(function() { bot.say(to, '\u0001ACTION dances: :D|-<\u0001'); }, 4000); 31 | } 32 | } 33 | else { 34 | // private message 35 | console.log('private message'); 36 | } 37 | }); 38 | bot.addListener('pm', function(nick, message) { 39 | console.log('Got private message from %s: %s', nick, message); 40 | }); 41 | bot.addListener('join', function(channel, who) { 42 | console.log('%s has joined %s', who, channel); 43 | }); 44 | bot.addListener('part', function(channel, who, reason) { 45 | console.log('%s has left %s: %s', who, channel, reason); 46 | }); 47 | bot.addListener('kick', function(channel, who, by, reason) { 48 | console.log('%s was kicked from %s by %s: %s', who, channel, by, reason); 49 | }); 50 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | /* Mock irc server */ 2 | 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var net = require('net'); 6 | var tls = require('tls'); 7 | var util = require('util'); 8 | var EventEmitter = require('events').EventEmitter; 9 | 10 | var MockIrcd = function(port, encoding, isSecure) { 11 | var self = this; 12 | var connectionClass; 13 | var options = {}; 14 | 15 | if (isSecure) { 16 | connectionClass = tls; 17 | options = { 18 | key: fs.readFileSync(path.resolve(__dirname, 'data/ircd.key')), 19 | cert: fs.readFileSync(path.resolve(__dirname, 'data/ircd.pem')) 20 | }; 21 | } else { 22 | connectionClass = net; 23 | } 24 | 25 | this.port = port || (isSecure ? 6697 : 6667); 26 | this.encoding = encoding || 'utf-8'; 27 | this.incoming = []; 28 | this.outgoing = []; 29 | 30 | this.server = connectionClass.createServer(options, function(c) { 31 | c.on('data', function(data) { 32 | var msg = data.toString(self.encoding).split('\r\n').filter(function(m) { return m; }); 33 | self.incoming = self.incoming.concat(msg); 34 | }); 35 | 36 | self.on('send', function(data) { 37 | self.outgoing.push(data); 38 | c.write(data); 39 | }); 40 | 41 | c.on('end', function() { 42 | self.emit('end'); 43 | }); 44 | }); 45 | 46 | this.server.listen(this.port); 47 | }; 48 | util.inherits(MockIrcd, EventEmitter); 49 | 50 | MockIrcd.prototype.send = function(data) { 51 | this.emit('send', data); 52 | }; 53 | 54 | MockIrcd.prototype.close = function() { 55 | this.server.close(); 56 | }; 57 | 58 | MockIrcd.prototype.getIncomingMsgs = function() { 59 | return this.incoming; 60 | }; 61 | 62 | var fixtures = require('./data/fixtures'); 63 | module.exports.getFixtures = function(testSuite) { 64 | return fixtures[testSuite]; 65 | }; 66 | 67 | module.exports.MockIrcd = function(port, encoding, isSecure) { 68 | return new MockIrcd(port, encoding, isSecure); 69 | }; 70 | -------------------------------------------------------------------------------- /lib/parse_message.js: -------------------------------------------------------------------------------- 1 | var ircColors = require('irc-colors'); 2 | var replyFor = require('./codes'); 3 | 4 | /** 5 | * parseMessage(line, stripColors) 6 | * 7 | * takes a raw "line" from the IRC server and turns it into an object with 8 | * useful keys 9 | * @param {String} line Raw message from IRC server. 10 | * @param {Boolean} stripColors If true, strip IRC colors. 11 | * @return {Object} A parsed message object. 12 | */ 13 | module.exports = function parseMessage(line, stripColors) { 14 | var message = {}; 15 | var match; 16 | 17 | if (stripColors) { 18 | line = ircColors.stripColorsAndStyle(line); 19 | } 20 | 21 | // Parse prefix 22 | match = line.match(/^:([^ ]+) +/); 23 | if (match) { 24 | message.prefix = match[1]; 25 | line = line.replace(/^:[^ ]+ +/, ''); 26 | match = message.prefix.match(/^([_a-zA-Z0-9\~\[\]\\`^{}|-]*)(!([^@]+)@(.*))?$/); 27 | if (match) { 28 | message.nick = match[1]; 29 | message.user = match[3]; 30 | message.host = match[4]; 31 | } 32 | else { 33 | message.server = message.prefix; 34 | } 35 | } 36 | 37 | // Parse command 38 | match = line.match(/^([^ ]+) */); 39 | message.command = match[1]; 40 | message.rawCommand = match[1]; 41 | message.commandType = 'normal'; 42 | line = line.replace(/^[^ ]+ +/, ''); 43 | 44 | if (replyFor[message.rawCommand]) { 45 | message.command = replyFor[message.rawCommand].name; 46 | message.commandType = replyFor[message.rawCommand].type; 47 | } 48 | 49 | message.args = []; 50 | var middle, trailing; 51 | 52 | // Parse parameters 53 | if (line.search(/^:|\s+:/) != -1) { 54 | match = line.match(/(.*?)(?:^:|\s+:)(.*)/); 55 | middle = match[1].trimRight(); 56 | trailing = match[2]; 57 | } 58 | else { 59 | middle = line; 60 | } 61 | 62 | if (middle.length) 63 | message.args = middle.split(/ +/); 64 | 65 | if (typeof (trailing) != 'undefined' && trailing.length) 66 | message.args.push(trailing); 67 | 68 | return message; 69 | } 70 | -------------------------------------------------------------------------------- /example/secure.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var irc = require('../'); 4 | /* 5 | * To set the key/cert explicitly, you could do the following 6 | var fs = require('fs'); 7 | 8 | var options = { 9 | key: fs.readFileSync('privkey.pem'), 10 | cert: fs.readFileSync('certificate.crt') 11 | }; 12 | */ 13 | 14 | // Or to just use defaults 15 | var options = true; 16 | 17 | var bot = new irc.Client('chat.us.freenode.net', 'nodebot', { 18 | port: 6697, 19 | debug: true, 20 | secure: options, 21 | channels: ['#botwar'] 22 | }); 23 | 24 | bot.addListener('error', function(message) { 25 | console.error('ERROR: %s: %s', message.command, message.args.join(' ')); 26 | }); 27 | 28 | bot.addListener('message#blah', function(from, message) { 29 | console.log('<%s> %s', from, message); 30 | }); 31 | 32 | bot.addListener('message', function(from, to, message) { 33 | console.log('%s => %s: %s', from, to, message); 34 | 35 | if (to.match(/^[#&]/)) { 36 | // channel message 37 | if (message.match(/hello/i)) { 38 | bot.say(to, 'Hello there ' + from); 39 | } 40 | if (message.match(/dance/)) { 41 | setTimeout(function() { bot.say(to, '\u0001ACTION dances: :D\\-<\u0001'); }, 1000); 42 | setTimeout(function() { bot.say(to, '\u0001ACTION dances: :D|-<\u0001'); }, 2000); 43 | setTimeout(function() { bot.say(to, '\u0001ACTION dances: :D/-<\u0001'); }, 3000); 44 | setTimeout(function() { bot.say(to, '\u0001ACTION dances: :D|-<\u0001'); }, 4000); 45 | } 46 | } 47 | else { 48 | // private message 49 | console.log('private message'); 50 | } 51 | }); 52 | bot.addListener('pm', function(nick, message) { 53 | console.log('Got private message from %s: %s', nick, message); 54 | }); 55 | bot.addListener('join', function(channel, who) { 56 | console.log('%s has joined %s', who, channel); 57 | }); 58 | bot.addListener('part', function(channel, who, reason) { 59 | console.log('%s has left %s: %s', who, channel, reason); 60 | }); 61 | bot.addListener('kick', function(channel, who, by, reason) { 62 | console.log('%s was kicked from %s by %s: %s', who, channel, by, reason); 63 | }); 64 | -------------------------------------------------------------------------------- /lib/cycling_ping_timer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var EventEmitter = require('events').EventEmitter; 5 | 6 | /** 7 | * This class encapsulates the ping timeout functionality. When enough 8 | * silence (lack of server-sent activity) time passes, an object of this type 9 | * will emit a 'wantPing' event, indicating you should send a PING message 10 | * to the server in order to get some signs of life from it. If enough 11 | * time passes after that (i.e. server does not respond to PING), then 12 | * an object of this type will emit a 'pingTimeout' event. 13 | * 14 | * To start the gears turning, call start() on an instance of this class To 15 | * put it in the 'started' state. 16 | * 17 | * When server-side activity occurs, call notifyOfActivity() on the object. 18 | * 19 | * When a pingTimeout occurs, the object will go into the 'stopped' state. 20 | */ 21 | var ctr = 0; 22 | 23 | function CyclingPingTimer(client) { 24 | var timerNumber = ctr++; 25 | var started = false; 26 | var self = this; 27 | 28 | // Only one of these two should be non-null at any given time. 29 | var loopingTimeout = null; 30 | var pingWaitTimeout = null; 31 | 32 | // conditionally log debug messages 33 | function debug(msg) { 34 | if (client.opt.debug) { 35 | console.error('CyclingPingTimer ' + timerNumber + ': ' + msg); 36 | } 37 | } 38 | 39 | // set up EventEmitter functionality 40 | EventEmitter.call(self); 41 | 42 | self.on('wantPing', function() { 43 | debug('server silent for too long, let\'s send a PING'); 44 | pingWaitTimeout = setTimeout(function() { 45 | self.stop(); 46 | debug('ping timeout!'); 47 | self.emit('pingTimeout'); 48 | }, client.opt.millisecondsBeforePingTimeout); 49 | }); 50 | 51 | self.notifyOfActivity = function() { 52 | if (started) { 53 | self.stop(); 54 | self.start(); 55 | } 56 | }; 57 | 58 | self.stop = function() { 59 | if (!started) { 60 | return; 61 | } 62 | started = false; 63 | 64 | clearTimeout(loopingTimeout); 65 | clearTimeout(pingWaitTimeout); 66 | 67 | loopingTimeout = null; 68 | pingWaitTimeout = null; 69 | }; 70 | 71 | self.start = function() { 72 | if (started) { 73 | debug('can\'t start, not stopped!'); 74 | return; 75 | } 76 | started = true; 77 | 78 | loopingTimeout = setTimeout(function() { 79 | loopingTimeout = null; 80 | self.emit('wantPing'); 81 | }, client.opt.millisecondsOfSilenceBeforePingSent); 82 | }; 83 | } 84 | 85 | util.inherits(CyclingPingTimer, EventEmitter); 86 | 87 | module.exports = CyclingPingTimer; 88 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to node-irc 2 | 3 | First, and most importantly, thank you for contributing to node-irc! Your efforts make the library 4 | as awesome as it is, and we really couldn't do it without you. Through your pull requests, issues, 5 | and discussion, together we are building the best IRC library for node. So, once again, *thank you*! 6 | 7 | What follows is a set of guidelines for contributing to node-irc. We ask that you follow them 8 | because they make our lives as maintainers easier, which means that your pull requests and issues 9 | have a higher chance of being addressed quickly. Please help us help you! 10 | 11 | This guide is roughly based on the [Atom contributor's guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md), so thanks to the Atom team for 12 | providing such a solid framework on which to hang our ideas. 13 | 14 | # Submitting Issues 15 | * Include the version of node-irc, or the SHA1 from git if you're using git. 16 | * Include the behavior you expect, other clients / bots where you've seen that behavior, the 17 | observed behavior, and details on how the two differ if it's not obvious. 18 | * Enable debug mode for node-irc and include its output in your report. 19 | * In the case of a crash, include the full stack trace and any error output. 20 | * Perform a cursory search to see if similar issues or pull requests have already been filed, and 21 | comment on them to move the discussion forward. 22 | * Most importantly, provide a minimal test case that demonstrates your bug. This is the best way 23 | to help us quickly isolate and fix your bug. 24 | * Consider joining us on IRC (##node-irc on freenode) for realtime discussion. Not only is it a 25 | friendly gesture, it also helps us isolate your issue far more quickly than the back-and-forth of 26 | issues on github allows. 27 | 28 | # Pull requests 29 | * Add yourself to the contributors section of package.json to claim credit for your work! 30 | * Do your work on a branch from master and file your pull request from that branch to master. 31 | * Make sure your code passes all tests (`npm run test; npm run lint`). 32 | * If possible, write *new* tests for the functionality you add. Once we have sane testing in place, 33 | this will become a hard requirement, so it's best to get used to it now! 34 | * If you change any user-facing element of the library (e.g. the API), document your changes. 35 | * If you make *breaking* changes, say so clearly in your pull request, so we know to schedule the 36 | merge when we plan to break the API for other changes. 37 | * Squash your commits into one before issuing your pull request. If you are not sure how to do this, 38 | take a look at the [edx instructions](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request) and change the 39 | obvious things to apply to your node-irc fork. If this doesn't make sense, or you're having trouble, 40 | come talk to us on IRC! We'll be glad to walk you through it. 41 | * End files with a newline. 42 | 43 | # Commit messages 44 | * Use the present tense ("Add feature" not "Added feature"). 45 | * Use the imperative mood ("Change message handling..." not "Changes message handling..."). 46 | * Limit the first line to 72 characters or less. 47 | * Reference issues and pull requests liberally. 48 | -------------------------------------------------------------------------------- /test/test-irc.js: -------------------------------------------------------------------------------- 1 | var net = require('net'); 2 | 3 | var irc = require('../lib/irc'); 4 | var test = require('tape'); 5 | 6 | var testHelpers = require('./helpers'); 7 | 8 | var expected = testHelpers.getFixtures('basic'); 9 | var greeting = ':localhost 001 testbot :Welcome to the Internet Relay Chat Network testbot\r\n'; 10 | 11 | test('connect, register and quit', function(t) { 12 | runTests(t, false, false); 13 | }); 14 | 15 | test('connect, register and quit, securely', function(t) { 16 | runTests(t, true, false); 17 | }); 18 | 19 | test('connect, register and quit, securely, with secure object', function(t) { 20 | runTests(t, true, true); 21 | }); 22 | 23 | function runTests(t, isSecure, useSecureObject) { 24 | var port = isSecure ? 6697 : 6667; 25 | var mock = testHelpers.MockIrcd(port, 'utf-8', isSecure); 26 | var client; 27 | if (isSecure && useSecureObject) { 28 | client = new irc.Client('notlocalhost', 'testbot', { 29 | secure: { 30 | host: 'localhost', 31 | port: port, 32 | rejectUnauthorized: false 33 | }, 34 | selfSigned: true, 35 | retryCount: 0, 36 | debug: true 37 | }); 38 | } else { 39 | var client = new irc.Client('localhost', 'testbot', { 40 | secure: isSecure, 41 | selfSigned: true, 42 | port: port, 43 | retryCount: 0, 44 | debug: true 45 | }); 46 | } 47 | 48 | t.plan(expected.sent.length + expected.received.length); 49 | 50 | mock.server.on(isSecure ? 'secureConnection' : 'connection', function() { 51 | mock.send(greeting); 52 | }); 53 | 54 | client.on('registered', function() { 55 | t.equal(mock.outgoing[0], expected.received[0][0], expected.received[0][1]); 56 | client.disconnect(); 57 | }); 58 | 59 | mock.on('end', function() { 60 | var msgs = mock.getIncomingMsgs(); 61 | 62 | for (var i = 0; i < msgs.length; i++) { 63 | t.equal(msgs[i], expected.sent[i][0], expected.sent[i][1]); 64 | } 65 | mock.close(); 66 | }); 67 | } 68 | 69 | test ('splitting of long lines', function(t) { 70 | var port = 6667; 71 | var mock = testHelpers.MockIrcd(port, 'utf-8', false); 72 | var client = new irc.Client('localhost', 'testbot', { 73 | secure: false, 74 | selfSigned: true, 75 | port: port, 76 | retryCount: 0, 77 | debug: true 78 | }); 79 | 80 | var group = testHelpers.getFixtures('_splitLongLines'); 81 | t.plan(group.length); 82 | group.forEach(function(item) { 83 | t.deepEqual(client._splitLongLines(item.input, item.maxLength, []), item.result); 84 | }); 85 | mock.close(); 86 | }); 87 | 88 | test ('splitting of long lines with no maxLength defined.', function(t) { 89 | var port = 6667; 90 | var mock = testHelpers.MockIrcd(port, 'utf-8', false); 91 | var client = new irc.Client('localhost', 'testbot', { 92 | secure: false, 93 | selfSigned: true, 94 | port: port, 95 | retryCount: 0, 96 | debug: true 97 | }); 98 | 99 | var group = testHelpers.getFixtures('_splitLongLines_no_max'); 100 | console.log(group.length); 101 | t.plan(group.length); 102 | group.forEach(function(item) { 103 | t.deepEqual(client._splitLongLines(item.input, null, []), item.result); 104 | }); 105 | mock.close(); 106 | }); 107 | 108 | test ('opt.messageSplit used when set', function(t) { 109 | var port = 6667; 110 | var mock = testHelpers.MockIrcd(port, 'utf-8', false); 111 | var client = new irc.Client('localhost', 'testbot', { 112 | secure: false, 113 | selfSigned: true, 114 | port: port, 115 | retryCount: 0, 116 | debug: true, 117 | messageSplit: 10 118 | }); 119 | 120 | var group = testHelpers.getFixtures('_speak'); 121 | t.plan(group.length); 122 | group.forEach(function(item) { 123 | client.maxLineLength = item.length; 124 | client._splitLongLines = function(words, maxLength, destination) { 125 | t.equal(maxLength, item.expected); 126 | return [words]; 127 | } 128 | client._speak('kind', 'target', 'test message'); 129 | }); 130 | 131 | mock.close(); 132 | }); 133 | -------------------------------------------------------------------------------- /test/test-mode.js: -------------------------------------------------------------------------------- 1 | var irc = require('../lib/irc'); 2 | var test = require('tape'); 3 | 4 | var testHelpers = require('./helpers'); 5 | 6 | test('various origins and types of chanmodes get handled correctly', function(t) { 7 | var mock = testHelpers.MockIrcd(); 8 | var client = new irc.Client('localhost', 'testbot', { debug: true }); 9 | 10 | var count = 0; 11 | client.on('+mode', function() { 12 | //console.log(client.chans['#channel']); 13 | t.deepEqual(client.chans['#channel'], expected[count++]); 14 | }); 15 | client.on('-mode', function() { 16 | //console.log(client.chans['#channel']); 17 | t.deepEqual(client.chans['#channel'], expected[count++]); 18 | }); 19 | 20 | var expected = [ 21 | { key: '#channel', serverName: '#channel', users: {}, modeParams: { n: [] }, mode: 'n' }, 22 | { key: '#channel', serverName: '#channel', users: {}, modeParams: { n: [], t: [] }, mode: 'nt' }, 23 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntb', modeParams: { b: ['*!*@AN.IP.1'], n: [], t: [] } }, 24 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntb', modeParams: { b: ['*!*@AN.IP.1', '*!*@AN.IP.2'], n: [], t: [] } }, 25 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntb', modeParams: { b: ['*!*@AN.IP.1', '*!*@AN.IP.2', '*!*@AN.IP.3'], n: [], t: [] } }, 26 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntb', modeParams: { b: ['*!*@AN.IP.1', '*!*@AN.IP.3'], n: [], t: [] } }, 27 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntbf', modeParams: { f: ['[10j]:15'], b: ['*!*@AN.IP.1', '*!*@AN.IP.3'], n: [], t: [] } }, 28 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntbf', modeParams: { f: ['[8j]:15'], b: ['*!*@AN.IP.1', '*!*@AN.IP.3'], n: [], t: [] } }, 29 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntb', modeParams: { b: ['*!*@AN.IP.1', '*!*@AN.IP.3'], n: [], t: [] } }, 30 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntbj', modeParams: { j: ['3:5'], b: ['*!*@AN.IP.1', '*!*@AN.IP.3'], n: [], t: [] } }, 31 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntbj', modeParams: { j: ['2:5'], b: ['*!*@AN.IP.1', '*!*@AN.IP.3'], n: [], t: [] } }, 32 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntb', modeParams: { b: ['*!*@AN.IP.1', '*!*@AN.IP.3'], n: [], t: [] } }, 33 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntbp', modeParams: { p: [], b: ['*!*@AN.IP.1', '*!*@AN.IP.3'], n: [], t: [] } }, 34 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntbps', modeParams: { s: [], p: [], b: ['*!*@AN.IP.1', '*!*@AN.IP.3'], n: [], t: [] } }, 35 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntbpsK', modeParams: { K: [], s: [], p: [], b: ['*!*@AN.IP.1', '*!*@AN.IP.3'], n: [], t: [] } }, 36 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntbsK', modeParams: { K: [], s: [], b: ['*!*@AN.IP.1', '*!*@AN.IP.3'], n: [], t: [] } }, 37 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntbK', modeParams: { K: [], b: ['*!*@AN.IP.1', '*!*@AN.IP.3'], n: [], t: [] } }, 38 | { key: '#channel', serverName: '#channel', users: { testbot: '@' }, mode: '+ntbKF', modeParams: { F: [], K: [], b: ['*!*@AN.IP.1', '*!*@AN.IP.3'], n: [], t: [] } } 39 | ]; 40 | 41 | mock.server.on('connection', function() { 42 | mock.send(':localhost 001 testbot :Welcome!\r\n'); 43 | mock.send(':localhost 005 testbot MODES=12 CHANTYPES=# PREFIX=(ohv)@%+ CHANMODES=beIqa,kfL,lj,psmntirRcOAQKVCuzNSMTGHFEB\r\n'); 44 | mock.send(':testbot MODE testbot :+ix\r\n'); 45 | mock.send(':testbot JOIN :#channel\r\n'); 46 | mock.send(':localhost MODE #channel +nt\r\n'); 47 | mock.send(':localhost 353 testbot = #channel :@testbot\r\n'); 48 | mock.send(':localhost 366 testbot #channel :End of /NAMES list.\r\n'); 49 | mock.send(':localhost 324 testbot #channel +nt\r\n'); 50 | mock.send(':localhost MODE #channel +b *!*@AN.IP.1\r\n'); 51 | mock.send(':localhost MODE #channel +bb *!*@AN.IP.2 *!*@AN.IP.3\r\n'); 52 | mock.send(':localhost MODE #channel -b *!*@AN.IP.2\r\n'); 53 | mock.send(':localhost MODE #channel +f [10j]:15\r\n'); 54 | mock.send(':localhost MODE #channel +f [8j]:15\r\n'); 55 | mock.send(':localhost MODE #channel -f+j [10j]:15 3:5\r\n'); 56 | mock.send(':localhost MODE #channel +j 2:5\r\n'); 57 | mock.send(':localhost MODE #channel -j\r\n'); 58 | mock.send(':localhost MODE #channel +ps\r\n'); 59 | mock.send(':localhost MODE #channel +K-p-s+F\r\n'); 60 | 61 | client.disconnect(); 62 | }); 63 | 64 | mock.on('end', function() { 65 | mock.close(); 66 | t.end(); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/node-irc.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/node-irc.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/node-irc" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/node-irc" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | if NOT "%PAPER%" == "" ( 11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 12 | ) 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "help" ( 17 | :help 18 | echo.Please use `make ^` where ^ is one of 19 | echo. html to make standalone HTML files 20 | echo. dirhtml to make HTML files named index.html in directories 21 | echo. singlehtml to make a single large HTML file 22 | echo. pickle to make pickle files 23 | echo. json to make JSON files 24 | echo. htmlhelp to make HTML files and a HTML help project 25 | echo. qthelp to make HTML files and a qthelp project 26 | echo. devhelp to make HTML files and a Devhelp project 27 | echo. epub to make an epub 28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 29 | echo. text to make text files 30 | echo. man to make manual pages 31 | echo. changes to make an overview over all changed/added/deprecated items 32 | echo. linkcheck to check all external links for integrity 33 | echo. doctest to run all doctests embedded in the documentation if enabled 34 | goto end 35 | ) 36 | 37 | if "%1" == "clean" ( 38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 39 | del /q /s %BUILDDIR%\* 40 | goto end 41 | ) 42 | 43 | if "%1" == "html" ( 44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 45 | if errorlevel 1 exit /b 1 46 | echo. 47 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 48 | goto end 49 | ) 50 | 51 | if "%1" == "dirhtml" ( 52 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 53 | if errorlevel 1 exit /b 1 54 | echo. 55 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 56 | goto end 57 | ) 58 | 59 | if "%1" == "singlehtml" ( 60 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 61 | if errorlevel 1 exit /b 1 62 | echo. 63 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 64 | goto end 65 | ) 66 | 67 | if "%1" == "pickle" ( 68 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 69 | if errorlevel 1 exit /b 1 70 | echo. 71 | echo.Build finished; now you can process the pickle files. 72 | goto end 73 | ) 74 | 75 | if "%1" == "json" ( 76 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished; now you can process the JSON files. 80 | goto end 81 | ) 82 | 83 | if "%1" == "htmlhelp" ( 84 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished; now you can run HTML Help Workshop with the ^ 88 | .hhp project file in %BUILDDIR%/htmlhelp. 89 | goto end 90 | ) 91 | 92 | if "%1" == "qthelp" ( 93 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 94 | if errorlevel 1 exit /b 1 95 | echo. 96 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 97 | .qhcp project file in %BUILDDIR%/qthelp, like this: 98 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\node-irc.qhcp 99 | echo.To view the help file: 100 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\node-irc.ghc 101 | goto end 102 | ) 103 | 104 | if "%1" == "devhelp" ( 105 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 106 | if errorlevel 1 exit /b 1 107 | echo. 108 | echo.Build finished. 109 | goto end 110 | ) 111 | 112 | if "%1" == "epub" ( 113 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 117 | goto end 118 | ) 119 | 120 | if "%1" == "latex" ( 121 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 122 | if errorlevel 1 exit /b 1 123 | echo. 124 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 125 | goto end 126 | ) 127 | 128 | if "%1" == "text" ( 129 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 130 | if errorlevel 1 exit /b 1 131 | echo. 132 | echo.Build finished. The text files are in %BUILDDIR%/text. 133 | goto end 134 | ) 135 | 136 | if "%1" == "man" ( 137 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 141 | goto end 142 | ) 143 | 144 | if "%1" == "changes" ( 145 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.The overview file is in %BUILDDIR%/changes. 149 | goto end 150 | ) 151 | 152 | if "%1" == "linkcheck" ( 153 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Link check complete; look for any errors in the above output ^ 157 | or in %BUILDDIR%/linkcheck/output.txt. 158 | goto end 159 | ) 160 | 161 | if "%1" == "doctest" ( 162 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 163 | if errorlevel 1 exit /b 1 164 | echo. 165 | echo.Testing of doctests in the sources finished, look at the ^ 166 | results in %BUILDDIR%/doctest/output.txt. 167 | goto end 168 | ) 169 | 170 | :end 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Travis](https://img.shields.io/travis/martynsmith/node-irc.svg?style=flat)](https://travis-ci.org/martynsmith/node-irc) 2 | [![npm](https://img.shields.io/npm/v/irc.svg?style=flat)](https://www.npmjs.com/package/irc) 3 | [![Dependency Status](https://img.shields.io/david/martynsmith/node-irc.svg?style=flat)](https://david-dm.org/martynsmith/node-irc#info=Dependencies) 4 | [![devDependency Status](https://img.shields.io/david/dev/martynsmith/node-irc.svg?style=flat)](https://david-dm.org/martynsmith/node-irc#info=devDependencies) 5 | [![License](https://img.shields.io/badge/license-GPLv3-blue.svg?style=flat)](http://opensource.org/licenses/GPL-3.0) 6 | [![Join the chat at https://gitter.im/martynsmith/node-irc](https://badges.gitter.im/martynsmith/node-irc.svg)](https://gitter.im/martynsmith/node-irc?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | 8 | 9 | [node-irc](http://node-irc.readthedocs.org/) is an IRC client library written in [JavaScript](http://en.wikipedia.org/wiki/JavaScript) for [Node](http://nodejs.org/). 10 | 11 | You can access more detailed documentation for this module at [Read the Docs](http://readthedocs.org/docs/node-irc/en/latest/) 12 | 13 | 14 | ## Installation 15 | 16 | The easiest way to get it is via [npm](http://github.com/isaacs/npm): 17 | 18 | ``` 19 | npm install irc 20 | ``` 21 | 22 | If you want to run the latest version (i.e. later than the version available via 23 | [npm](http://github.com/isaacs/npm)) you can clone this repo, then use [npm](http://github.com/isaacs/npm) to link-install it: 24 | 25 | ``` 26 | npm link /path/to/your/clone 27 | ``` 28 | 29 | Of course, you can just clone this, and manually point at the library itself, 30 | but we really recommend using [npm](http://github.com/isaacs/npm)! 31 | 32 | Note that as of version 0.3.8, node-irc supports character set detection using 33 | [icu](http://site.icu-project.org/). You'll need to install libiconv (if 34 | necessary; Linux systems tend to ship this in their glibc) and libicu (and its 35 | headers, if necessary, [install instructions](https://github.com/mooz/node-icu-charset-detector#installing-icu)) in order to use this feature. If you do not have these 36 | libraries or their headers installed, you will receive errors when trying to 37 | build these dependencies. However, node-irc will still install (assuming 38 | nothing else failed) and you'll be able to use it, just not the character 39 | set features. 40 | 41 | ## Basic Usage 42 | 43 | This library provides basic IRC client functionality. In the simplest case you 44 | can connect to an IRC server like so: 45 | 46 | ```js 47 | var irc = require('irc'); 48 | var client = new irc.Client('irc.yourserver.com', 'myNick', { 49 | channels: ['#channel'], 50 | }); 51 | ``` 52 | 53 | Of course it's not much use once it's connected if that's all you have! 54 | 55 | The client emits a large number of events that correlate to things you'd 56 | normally see in your favorite IRC client. Most likely the first one you'll want 57 | to use is: 58 | 59 | ```js 60 | client.addListener('message', function (from, to, message) { 61 | console.log(from + ' => ' + to + ': ' + message); 62 | }); 63 | ``` 64 | 65 | or if you're only interested in messages to the bot itself: 66 | 67 | ```js 68 | client.addListener('pm', function (from, message) { 69 | console.log(from + ' => ME: ' + message); 70 | }); 71 | ``` 72 | 73 | or to a particular channel: 74 | 75 | ```js 76 | client.addListener('message#yourchannel', function (from, message) { 77 | console.log(from + ' => #yourchannel: ' + message); 78 | }); 79 | ``` 80 | 81 | At the moment there are functions for joining: 82 | 83 | ```js 84 | client.join('#yourchannel yourpass'); 85 | ``` 86 | 87 | parting: 88 | 89 | ```js 90 | client.part('#yourchannel'); 91 | ``` 92 | 93 | talking: 94 | 95 | ```js 96 | client.say('#yourchannel', "I'm a bot!"); 97 | client.say('nonbeliever', "SRSLY, I AM!"); 98 | ``` 99 | 100 | and many others. Check out the API documentation for a complete reference. 101 | 102 | For any commands that there aren't methods for you can use the send() method 103 | which sends raw messages to the server: 104 | 105 | ```js 106 | client.send('MODE', '#yourchannel', '+o', 'yournick'); 107 | ``` 108 | 109 | ## Help! - it keeps crashing! 110 | 111 | When the client receives errors from the IRC network, it emits an "error" 112 | event. As stated in the [Node JS EventEmitter documentation](http://nodejs.org/api/events.html#events_class_events_eventemitter) if you don't bind 113 | something to this error, it will cause a fatal stack trace. 114 | 115 | The upshot of this is basically that if you bind an error handler to your 116 | client, errors will be sent there instead of crashing your program.: 117 | 118 | ```js 119 | client.addListener('error', function(message) { 120 | console.log('error: ', message); 121 | }); 122 | ``` 123 | 124 | 125 | ## Further Support 126 | 127 | Further documentation (including a complete API reference) is available in 128 | reStructuredText format in the docs/ folder of this project, or online at [Read the Docs](http://readthedocs.org/docs/node-irc/en/latest/). 129 | 130 | If you find any issues with the documentation (or the module) please send a pull 131 | request or file an issue and we'll do our best to accommodate. 132 | 133 | You can also visit us on ##node-irc on freenode to discuss issues you're having 134 | with the library, pull requests, or anything else related to node-irc. 135 | -------------------------------------------------------------------------------- /test/data/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "basic": { 3 | "sent": [ 4 | ["NICK testbot", "Client sent NICK message"], 5 | ["USER nodebot 8 * :nodeJS IRC client", "Client sent USER message"], 6 | ["QUIT :node-irc says goodbye", "Client sent QUIT message"] 7 | ], 8 | 9 | "received": [ 10 | [":localhost 001 testbot :Welcome to the Internet Relay Chat Network testbot\r\n", "Received welcome message"] 11 | ] 12 | }, 13 | "double-CRLF": { 14 | "sent": [ 15 | ["NICK testbot", "Client sent NICK message"], 16 | ["USER nodebot 8 * :nodeJS IRC client", "Client sent USER message"], 17 | ["QUIT :node-irc says goodbye", "Client sent QUIT message"] 18 | ], 19 | 20 | "received": [ 21 | [":localhost 001 testbot :Welcome to the Internet Relay Chat Network testbot\r\n\r\n", "Received welcome message"] 22 | ] 23 | }, 24 | "parse-line": { 25 | ":irc.dollyfish.net.nz 372 nodebot :The message of the day was last changed: 2012-6-16 23:57": { 26 | "prefix": "irc.dollyfish.net.nz", 27 | "server": "irc.dollyfish.net.nz", 28 | "command": "rpl_motd", 29 | "rawCommand": "372", 30 | "commandType": "reply", 31 | "args": ["nodebot", "The message of the day was last changed: 2012-6-16 23:57"] 32 | }, 33 | ":Ned!~martyn@irc.dollyfish.net.nz PRIVMSG #test :Hello nodebot!": { 34 | "prefix": "Ned!~martyn@irc.dollyfish.net.nz", 35 | "nick": "Ned", 36 | "user": "~martyn", 37 | "host": "irc.dollyfish.net.nz", 38 | "command": "PRIVMSG", 39 | "rawCommand": "PRIVMSG", 40 | "commandType": "normal", 41 | "args": ["#test", "Hello nodebot!"] 42 | }, 43 | ":Ned!~martyn@irc.dollyfish.net.nz PRIVMSG #test ::-)": { 44 | "prefix": "Ned!~martyn@irc.dollyfish.net.nz", 45 | "nick": "Ned", 46 | "user": "~martyn", 47 | "host": "irc.dollyfish.net.nz", 48 | "command": "PRIVMSG", 49 | "rawCommand": "PRIVMSG", 50 | "commandType": "normal", 51 | "args": ["#test", ":-)"] 52 | }, 53 | ":Ned!~martyn@irc.dollyfish.net.nz PRIVMSG #test ::": { 54 | "prefix": "Ned!~martyn@irc.dollyfish.net.nz", 55 | "nick": "Ned", 56 | "user": "~martyn", 57 | "host": "irc.dollyfish.net.nz", 58 | "command": "PRIVMSG", 59 | "rawCommand": "PRIVMSG", 60 | "commandType": "normal", 61 | "args": ["#test", ":"] 62 | }, 63 | ":Ned!~martyn@irc.dollyfish.net.nz PRIVMSG #test ::^:^:": { 64 | "prefix": "Ned!~martyn@irc.dollyfish.net.nz", 65 | "nick": "Ned", 66 | "user": "~martyn", 67 | "host": "irc.dollyfish.net.nz", 68 | "command": "PRIVMSG", 69 | "rawCommand": "PRIVMSG", 70 | "commandType": "normal", 71 | "args": ["#test", ":^:^:"] 72 | }, 73 | ":some.irc.net 324 webuser #channel +Cnj 5:10": { 74 | "prefix": "some.irc.net", 75 | "server": "some.irc.net", 76 | "command": "rpl_channelmodeis", 77 | "rawCommand": "324", 78 | "commandType": "reply", 79 | "args": ["webuser", "#channel", "+Cnj", "5:10"] 80 | }, 81 | ":nick!user@host QUIT :Ping timeout: 252 seconds": { 82 | "prefix": "nick!user@host", 83 | "nick": "nick", 84 | "user": "user", 85 | "host": "host", 86 | "command": "QUIT", 87 | "rawCommand": "QUIT", 88 | "commandType": "normal", 89 | "args": ["Ping timeout: 252 seconds"] 90 | }, 91 | ":nick!user@host PRIVMSG #channel :so : colons: :are :: not a problem ::::": { 92 | "prefix": "nick!user@host", 93 | "nick": "nick", 94 | "user": "user", 95 | "host": "host", 96 | "command": "PRIVMSG", 97 | "rawCommand": "PRIVMSG", 98 | "commandType": "normal", 99 | "args": ["#channel", "so : colons: :are :: not a problem ::::"] 100 | }, 101 | ":nick!user@host PRIVMSG #channel :\u000314,01\u001fneither are colors or styles\u001f\u0003": { 102 | "prefix": "nick!user@host", 103 | "nick": "nick", 104 | "user": "user", 105 | "host": "host", 106 | "command": "PRIVMSG", 107 | "rawCommand": "PRIVMSG", 108 | "commandType": "normal", 109 | "args": ["#channel", "neither are colors or styles"], 110 | "stripColors": true 111 | }, 112 | ":nick!user@host PRIVMSG #channel :\u000314,01\u001fwe can leave styles and colors alone if desired\u001f\u0003": { 113 | "prefix": "nick!user@host", 114 | "nick": "nick", 115 | "user": "user", 116 | "host": "host", 117 | "command": "PRIVMSG", 118 | "rawCommand": "PRIVMSG", 119 | "commandType": "normal", 120 | "args": ["#channel", "\u000314,01\u001fwe can leave styles and colors alone if desired\u001f\u0003"], 121 | "stripColors": false 122 | }, 123 | ":pratchett.freenode.net 324 nodebot #ubuntu +CLcntjf 5:10 #ubuntu-unregged": { 124 | "prefix": "pratchett.freenode.net", 125 | "server": "pratchett.freenode.net", 126 | "command": "rpl_channelmodeis", 127 | "rawCommand": "324", 128 | "commandType": "reply", 129 | "args": ["nodebot", "#ubuntu", "+CLcntjf", "5:10", "#ubuntu-unregged"] 130 | } 131 | 132 | }, 133 | "433-before-001": { 134 | "sent": [ 135 | ["NICK testbot", "Client sent NICK message"], 136 | ["USER nodebot 8 * :nodeJS IRC client", "Client sent USER message"], 137 | ["NICK testbot1", "Client sent proper response to 433 nickname in use message"], 138 | ["QUIT :node-irc says goodbye", "Client sent QUIT message"] 139 | ], 140 | 141 | "received": [ 142 | [":localhost 433 * testbot :Nickname is already in use.\r\n", "Received nick in use error"], 143 | [":localhost 001 testbot1 :Welcome to the Internet Relay Chat Network testbot\r\n", "Received welcome message"] 144 | ], 145 | "clientInfo": [ 146 | "hostmask is as expected after 433", 147 | "nick is as expected after 433", 148 | "maxLineLength is as expected after 433" 149 | ] 150 | }, 151 | "convert-encoding": { 152 | "causesException": [ 153 | ":ubottu!ubottu@ubuntu/bot/ubottu MODE #ubuntu -bo *!~Brian@* ubottu\r\n", 154 | "Elizabeth", 155 | ":sblack1!~sblack1@unaffiliated/sblack1 NICK :sblack\r\n", 156 | ":TijG!~TijG@null.1ago.be PRIVMSG #ubuntu :ThinkPad\r\n" 157 | ] 158 | }, 159 | "_splitLongLines": [ 160 | { 161 | "input": "abcde ", 162 | "maxLength": 5, 163 | "result": ["abcde"] 164 | }, 165 | { 166 | "input": "abcde", 167 | "maxLength": 5, 168 | "result": ["abcde"] 169 | }, 170 | { 171 | "input": "abcdefghijklmnopqrstuvwxyz", 172 | "maxLength": 5, 173 | "result": ["abcde", "fghij", "klmno", "pqrst", "uvwxy", "z"] 174 | }, 175 | { 176 | "input": "abc abcdef abc abcd abc", 177 | "maxLength": 5, 178 | "result": ["abc", "abcde", "f abc", "abcd", "abc"] 179 | } 180 | ], 181 | "_splitLongLines_no_max": [ 182 | { 183 | "input": "abcdefghijklmnopqrstuvwxyz", 184 | "result": ["abcdefghijklmnopqrstuvwxyz"] 185 | } 186 | ], 187 | "_speak": [ 188 | { 189 | "length": 30, 190 | "expected": 10 191 | }, 192 | { 193 | "length": 7, 194 | "expected": 1 195 | } 196 | ], 197 | "quit": { 198 | "sent": [ 199 | ["NICK testbot", "Client sent NICK message"], 200 | ["USER nodebot 8 * :nodeJS IRC client", "Client sent USER message"], 201 | ["QUIT :quitting as a test", "Client sent QUIT message"] 202 | ], 203 | 204 | "received": [ 205 | [":localhost 001 testbot :Welcome to the Internet Relay Chat Network testbot\r\n", "Received welcome message"] 206 | ] 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # node-irc documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Oct 1 00:02:31 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'node-irc' 44 | copyright = u'2011, Martyn Smith' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '2.1' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '2.1' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'node-ircdoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | # The paper size ('letter' or 'a4'). 173 | #latex_paper_size = 'letter' 174 | 175 | # The font size ('10pt', '11pt' or '12pt'). 176 | #latex_font_size = '10pt' 177 | 178 | # Grouping the document tree into LaTeX files. List of tuples 179 | # (source start file, target name, title, author, documentclass [howto/manual]). 180 | latex_documents = [ 181 | ('index', 'node-irc.tex', u'node-irc Documentation', 182 | u'Martyn Smith', 'manual'), 183 | ] 184 | 185 | # The name of an image file (relative to this directory) to place at the top of 186 | # the title page. 187 | #latex_logo = None 188 | 189 | # For "manual" documents, if this is true, then toplevel headings are parts, 190 | # not chapters. 191 | #latex_use_parts = False 192 | 193 | # If true, show page references after internal links. 194 | #latex_show_pagerefs = False 195 | 196 | # If true, show URL addresses after external links. 197 | #latex_show_urls = False 198 | 199 | # Additional stuff for the LaTeX preamble. 200 | #latex_preamble = '' 201 | 202 | # Documents to append as an appendix to all manuals. 203 | #latex_appendices = [] 204 | 205 | # If false, no module index is generated. 206 | #latex_domain_indices = True 207 | 208 | 209 | # -- Options for manual page output -------------------------------------------- 210 | 211 | # One entry per manual page. List of tuples 212 | # (source start file, name, description, authors, manual section). 213 | man_pages = [ 214 | ('index', 'node-irc', u'node-irc Documentation', 215 | [u'Martyn Smith'], 1) 216 | ] 217 | -------------------------------------------------------------------------------- /lib/codes.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '001': { 3 | name: 'rpl_welcome', 4 | type: 'reply' 5 | }, 6 | '002': { 7 | name: 'rpl_yourhost', 8 | type: 'reply' 9 | }, 10 | '003': { 11 | name: 'rpl_created', 12 | type: 'reply' 13 | }, 14 | '004': { 15 | name: 'rpl_myinfo', 16 | type: 'reply' 17 | }, 18 | '005': { 19 | name: 'rpl_isupport', 20 | type: 'reply' 21 | }, 22 | 200: { 23 | name: 'rpl_tracelink', 24 | type: 'reply' 25 | }, 26 | 201: { 27 | name: 'rpl_traceconnecting', 28 | type: 'reply' 29 | }, 30 | 202: { 31 | name: 'rpl_tracehandshake', 32 | type: 'reply' 33 | }, 34 | 203: { 35 | name: 'rpl_traceunknown', 36 | type: 'reply' 37 | }, 38 | 204: { 39 | name: 'rpl_traceoperator', 40 | type: 'reply' 41 | }, 42 | 205: { 43 | name: 'rpl_traceuser', 44 | type: 'reply' 45 | }, 46 | 206: { 47 | name: 'rpl_traceserver', 48 | type: 'reply' 49 | }, 50 | 208: { 51 | name: 'rpl_tracenewtype', 52 | type: 'reply' 53 | }, 54 | 211: { 55 | name: 'rpl_statslinkinfo', 56 | type: 'reply' 57 | }, 58 | 212: { 59 | name: 'rpl_statscommands', 60 | type: 'reply' 61 | }, 62 | 213: { 63 | name: 'rpl_statscline', 64 | type: 'reply' 65 | }, 66 | 214: { 67 | name: 'rpl_statsnline', 68 | type: 'reply' 69 | }, 70 | 215: { 71 | name: 'rpl_statsiline', 72 | type: 'reply' 73 | }, 74 | 216: { 75 | name: 'rpl_statskline', 76 | type: 'reply' 77 | }, 78 | 218: { 79 | name: 'rpl_statsyline', 80 | type: 'reply' 81 | }, 82 | 219: { 83 | name: 'rpl_endofstats', 84 | type: 'reply' 85 | }, 86 | 221: { 87 | name: 'rpl_umodeis', 88 | type: 'reply' 89 | }, 90 | 241: { 91 | name: 'rpl_statslline', 92 | type: 'reply' 93 | }, 94 | 242: { 95 | name: 'rpl_statsuptime', 96 | type: 'reply' 97 | }, 98 | 243: { 99 | name: 'rpl_statsoline', 100 | type: 'reply' 101 | }, 102 | 244: { 103 | name: 'rpl_statshline', 104 | type: 'reply' 105 | }, 106 | 250: { 107 | name: 'rpl_statsconn', 108 | type: 'reply' 109 | }, 110 | 251: { 111 | name: 'rpl_luserclient', 112 | type: 'reply' 113 | }, 114 | 252: { 115 | name: 'rpl_luserop', 116 | type: 'reply' 117 | }, 118 | 253: { 119 | name: 'rpl_luserunknown', 120 | type: 'reply' 121 | }, 122 | 254: { 123 | name: 'rpl_luserchannels', 124 | type: 'reply' 125 | }, 126 | 255: { 127 | name: 'rpl_luserme', 128 | type: 'reply' 129 | }, 130 | 256: { 131 | name: 'rpl_adminme', 132 | type: 'reply' 133 | }, 134 | 257: { 135 | name: 'rpl_adminloc1', 136 | type: 'reply' 137 | }, 138 | 258: { 139 | name: 'rpl_adminloc2', 140 | type: 'reply' 141 | }, 142 | 259: { 143 | name: 'rpl_adminemail', 144 | type: 'reply' 145 | }, 146 | 261: { 147 | name: 'rpl_tracelog', 148 | type: 'reply' 149 | }, 150 | 265: { 151 | name: 'rpl_localusers', 152 | type: 'reply' 153 | }, 154 | 266: { 155 | name: 'rpl_globalusers', 156 | type: 'reply' 157 | }, 158 | 300: { 159 | name: 'rpl_none', 160 | type: 'reply' 161 | }, 162 | 301: { 163 | name: 'rpl_away', 164 | type: 'reply' 165 | }, 166 | 302: { 167 | name: 'rpl_userhost', 168 | type: 'reply' 169 | }, 170 | 303: { 171 | name: 'rpl_ison', 172 | type: 'reply' 173 | }, 174 | 305: { 175 | name: 'rpl_unaway', 176 | type: 'reply' 177 | }, 178 | 306: { 179 | name: 'rpl_nowaway', 180 | type: 'reply' 181 | }, 182 | 311: { 183 | name: 'rpl_whoisuser', 184 | type: 'reply' 185 | }, 186 | 312: { 187 | name: 'rpl_whoisserver', 188 | type: 'reply' 189 | }, 190 | 313: { 191 | name: 'rpl_whoisoperator', 192 | type: 'reply' 193 | }, 194 | 314: { 195 | name: 'rpl_whowasuser', 196 | type: 'reply' 197 | }, 198 | 315: { 199 | name: 'rpl_endofwho', 200 | type: 'reply' 201 | }, 202 | 317: { 203 | name: 'rpl_whoisidle', 204 | type: 'reply' 205 | }, 206 | 318: { 207 | name: 'rpl_endofwhois', 208 | type: 'reply' 209 | }, 210 | 319: { 211 | name: 'rpl_whoischannels', 212 | type: 'reply' 213 | }, 214 | 321: { 215 | name: 'rpl_liststart', 216 | type: 'reply' 217 | }, 218 | 322: { 219 | name: 'rpl_list', 220 | type: 'reply' 221 | }, 222 | 323: { 223 | name: 'rpl_listend', 224 | type: 'reply' 225 | }, 226 | 324: { 227 | name: 'rpl_channelmodeis', 228 | type: 'reply' 229 | }, 230 | 329: { 231 | name: 'rpl_creationtime', 232 | type: 'reply' 233 | }, 234 | 331: { 235 | name: 'rpl_notopic', 236 | type: 'reply' 237 | }, 238 | 332: { 239 | name: 'rpl_topic', 240 | type: 'reply' 241 | }, 242 | 333: { 243 | name: 'rpl_topicwhotime', 244 | type: 'reply' 245 | }, 246 | 341: { 247 | name: 'rpl_inviting', 248 | type: 'reply' 249 | }, 250 | 342: { 251 | name: 'rpl_summoning', 252 | type: 'reply' 253 | }, 254 | 351: { 255 | name: 'rpl_version', 256 | type: 'reply' 257 | }, 258 | 352: { 259 | name: 'rpl_whoreply', 260 | type: 'reply' 261 | }, 262 | 353: { 263 | name: 'rpl_namreply', 264 | type: 'reply' 265 | }, 266 | 364: { 267 | name: 'rpl_links', 268 | type: 'reply' 269 | }, 270 | 365: { 271 | name: 'rpl_endoflinks', 272 | type: 'reply' 273 | }, 274 | 366: { 275 | name: 'rpl_endofnames', 276 | type: 'reply' 277 | }, 278 | 367: { 279 | name: 'rpl_banlist', 280 | type: 'reply' 281 | }, 282 | 368: { 283 | name: 'rpl_endofbanlist', 284 | type: 'reply' 285 | }, 286 | 369: { 287 | name: 'rpl_endofwhowas', 288 | type: 'reply' 289 | }, 290 | 371: { 291 | name: 'rpl_info', 292 | type: 'reply' 293 | }, 294 | 372: { 295 | name: 'rpl_motd', 296 | type: 'reply' 297 | }, 298 | 374: { 299 | name: 'rpl_endofinfo', 300 | type: 'reply' 301 | }, 302 | 375: { 303 | name: 'rpl_motdstart', 304 | type: 'reply' 305 | }, 306 | 376: { 307 | name: 'rpl_endofmotd', 308 | type: 'reply' 309 | }, 310 | 381: { 311 | name: 'rpl_youreoper', 312 | type: 'reply' 313 | }, 314 | 382: { 315 | name: 'rpl_rehashing', 316 | type: 'reply' 317 | }, 318 | 391: { 319 | name: 'rpl_time', 320 | type: 'reply' 321 | }, 322 | 392: { 323 | name: 'rpl_usersstart', 324 | type: 'reply' 325 | }, 326 | 393: { 327 | name: 'rpl_users', 328 | type: 'reply' 329 | }, 330 | 394: { 331 | name: 'rpl_endofusers', 332 | type: 'reply' 333 | }, 334 | 395: { 335 | name: 'rpl_nousers', 336 | type: 'reply' 337 | }, 338 | 401: { 339 | name: 'err_nosuchnick', 340 | type: 'error' 341 | }, 342 | 402: { 343 | name: 'err_nosuchserver', 344 | type: 'error' 345 | }, 346 | 403: { 347 | name: 'err_nosuchchannel', 348 | type: 'error' 349 | }, 350 | 404: { 351 | name: 'err_cannotsendtochan', 352 | type: 'error' 353 | }, 354 | 405: { 355 | name: 'err_toomanychannels', 356 | type: 'error' 357 | }, 358 | 406: { 359 | name: 'err_wasnosuchnick', 360 | type: 'error' 361 | }, 362 | 407: { 363 | name: 'err_toomanytargets', 364 | type: 'error' 365 | }, 366 | 409: { 367 | name: 'err_noorigin', 368 | type: 'error' 369 | }, 370 | 411: { 371 | name: 'err_norecipient', 372 | type: 'error' 373 | }, 374 | 412: { 375 | name: 'err_notexttosend', 376 | type: 'error' 377 | }, 378 | 413: { 379 | name: 'err_notoplevel', 380 | type: 'error' 381 | }, 382 | 414: { 383 | name: 'err_wildtoplevel', 384 | type: 'error' 385 | }, 386 | 421: { 387 | name: 'err_unknowncommand', 388 | type: 'error' 389 | }, 390 | 422: { 391 | name: 'err_nomotd', 392 | type: 'error' 393 | }, 394 | 423: { 395 | name: 'err_noadmininfo', 396 | type: 'error' 397 | }, 398 | 424: { 399 | name: 'err_fileerror', 400 | type: 'error' 401 | }, 402 | 431: { 403 | name: 'err_nonicknamegiven', 404 | type: 'error' 405 | }, 406 | 432: { 407 | name: 'err_erroneusnickname', 408 | type: 'error' 409 | }, 410 | 433: { 411 | name: 'err_nicknameinuse', 412 | type: 'error' 413 | }, 414 | 436: { 415 | name: 'err_nickcollision', 416 | type: 'error' 417 | }, 418 | 441: { 419 | name: 'err_usernotinchannel', 420 | type: 'error' 421 | }, 422 | 442: { 423 | name: 'err_notonchannel', 424 | type: 'error' 425 | }, 426 | 443: { 427 | name: 'err_useronchannel', 428 | type: 'error' 429 | }, 430 | 444: { 431 | name: 'err_nologin', 432 | type: 'error' 433 | }, 434 | 445: { 435 | name: 'err_summondisabled', 436 | type: 'error' 437 | }, 438 | 446: { 439 | name: 'err_usersdisabled', 440 | type: 'error' 441 | }, 442 | 451: { 443 | name: 'err_notregistered', 444 | type: 'error' 445 | }, 446 | 461: { 447 | name: 'err_needmoreparams', 448 | type: 'error' 449 | }, 450 | 462: { 451 | name: 'err_alreadyregistred', 452 | type: 'error' 453 | }, 454 | 463: { 455 | name: 'err_nopermforhost', 456 | type: 'error' 457 | }, 458 | 464: { 459 | name: 'err_passwdmismatch', 460 | type: 'error' 461 | }, 462 | 465: { 463 | name: 'err_yourebannedcreep', 464 | type: 'error' 465 | }, 466 | 467: { 467 | name: 'err_keyset', 468 | type: 'error' 469 | }, 470 | 471: { 471 | name: 'err_channelisfull', 472 | type: 'error' 473 | }, 474 | 472: { 475 | name: 'err_unknownmode', 476 | type: 'error' 477 | }, 478 | 473: { 479 | name: 'err_inviteonlychan', 480 | type: 'error' 481 | }, 482 | 474: { 483 | name: 'err_bannedfromchan', 484 | type: 'error' 485 | }, 486 | 475: { 487 | name: 'err_badchannelkey', 488 | type: 'error' 489 | }, 490 | 481: { 491 | name: 'err_noprivileges', 492 | type: 'error' 493 | }, 494 | 482: { 495 | name: 'err_chanoprivsneeded', 496 | type: 'error' 497 | }, 498 | 483: { 499 | name: 'err_cantkillserver', 500 | type: 'error' 501 | }, 502 | 491: { 503 | name: 'err_nooperhost', 504 | type: 'error' 505 | }, 506 | 501: { 507 | name: 'err_umodeunknownflag', 508 | type: 'error' 509 | }, 510 | 502: { 511 | name: 'err_usersdontmatch', 512 | type: 'error' 513 | } 514 | }; 515 | -------------------------------------------------------------------------------- /docs/API.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | This library provides IRC client functionality 5 | 6 | Client 7 | ---------- 8 | 9 | .. js:function:: irc.Client(server, nick [, options]) 10 | 11 | This object is the base of everything, it represents a single nick connected to 12 | a single IRC server. 13 | 14 | The first two arguments are the server to connect to, and the nickname to 15 | attempt to use. The third optional argument is an options object with default 16 | values:: 17 | 18 | { 19 | userName: 'nodebot', 20 | realName: 'nodeJS IRC client', 21 | port: 6667, 22 | localAddress: null, 23 | debug: false, 24 | showErrors: false, 25 | autoRejoin: false, 26 | autoConnect: true, 27 | channels: [], 28 | secure: false, 29 | selfSigned: false, 30 | certExpired: false, 31 | floodProtection: false, 32 | floodProtectionDelay: 1000, 33 | sasl: false, 34 | retryCount: 0, 35 | retryDelay: 2000, 36 | stripColors: false, 37 | channelPrefixes: "&#", 38 | messageSplit: 512, 39 | encoding: '' 40 | } 41 | 42 | `secure` (SSL connection) can be a true value or an object (the kind of object 43 | returned from `crypto.createCredentials()`) specifying cert etc for validation. 44 | If you set `selfSigned` to true SSL accepts certificates from a non trusted CA. 45 | If you set `certExpired` to true, the bot connects even if the ssl cert has expired. 46 | 47 | `localAddress` is the address to bind to when connecting. 48 | 49 | `floodProtection` queues all your messages and slowly unpacks it to make sure 50 | that we won't get kicked out because for Excess Flood. You can also use 51 | `Client.activateFloodProtection()` to activate flood protection after 52 | instantiating the client. 53 | 54 | `floodProtectionDelay` sets the amount of time that the client will wait 55 | between sending subsequent messages when `floodProtection` is enabled. 56 | 57 | Set `sasl` to true to enable SASL support. You'll also want to set `nick`, 58 | `userName`, and `password` for authentication. 59 | 60 | `stripColors` removes mirc colors (0x03 followed by one or two ascii 61 | numbers for foreground,background) and ircII "effect" codes (0x02 62 | bold, 0x1f underline, 0x16 reverse, 0x0f reset) from the entire 63 | message before parsing it and passing it along. 64 | 65 | `messageSplit` will split up large messages sent with the `say` method 66 | into multiple messages of length fewer than `messageSplit` characters. 67 | 68 | With `encoding` you can set IRC bot to convert all messages to specified character set. If you don't want to use 69 | this just leave value blank or false. Example values are UTF-8, ISO-8859-15, etc. 70 | 71 | Setting `debug` to true will emit timestamped messages to the console 72 | using `util.log` when certain events are fired. 73 | 74 | `autoRejoin` has the client rejoin channels after being kicked. 75 | 76 | Setting `autoConnect` to false prevents the Client from connecting on 77 | instantiation. You will need to call `connect()` on the client instance:: 78 | 79 | var client = new irc.Client({ autoConnect: false, ... }); 80 | client.connect(); 81 | 82 | `retryCount` is the number of times the client will try to automatically reconnect when disconnected. It defaults to 0. 83 | 84 | `retryDelay` is the number of milliseconds to wait before retying to automatically reconnect when disconnected. It defaults to 2000. 85 | 86 | .. js:function:: Client.send(command, arg1, arg2, ...) 87 | 88 | Sends a raw message to the server; generally speaking, it's best not to use 89 | this method unless you know what you're doing. Instead, use one of the 90 | methods below. 91 | 92 | .. js:function:: Client.join(channel, callback) 93 | 94 | Joins the specified channel. 95 | 96 | :param string channel: Channel to join 97 | :param function callback: Callback to automatically subscribed to the 98 | `join#channel` event, but removed after the first invocation. `channel` 99 | supports multiple JOIN arguments as a space separated string (similar to 100 | the IRC protocol). 101 | 102 | .. js:function:: Client.part(channel, [message], callback) 103 | 104 | Parts the specified channel. 105 | 106 | :param string channel: Channel to part 107 | :param string message: Optional message to send upon leaving the channel 108 | :param function callback: Callback to automatically subscribed to the 109 | `part#channel` event, but removed after the first invocation. 110 | 111 | .. js:function:: Client.say(target, message) 112 | 113 | Sends a message to the specified target. 114 | 115 | :param string target: is either a nickname, or a channel. 116 | :param string message: the message to send to the target. 117 | 118 | .. js:function:: Client.ctcp(target, type, text) 119 | 120 | Sends a CTCP message to the specified target. 121 | 122 | :param string target: is either a nickname, or a channel. 123 | :param string type: the type of the CTCP message. Specify "privmsg" for a 124 | PRIVMSG, and anything else for a NOTICE. 125 | :param string text: the CTCP message to send. 126 | 127 | .. js:function:: Client.action(target, message) 128 | 129 | Sends an action to the specified target. 130 | 131 | .. js:function:: Client.notice(target, message) 132 | 133 | Sends a notice to the specified target. 134 | 135 | :param string target: is either a nickname, or a channel. 136 | :param string message: the message to send as a notice to the target. 137 | 138 | .. js:function:: Client.whois(nick, callback) 139 | 140 | Request a whois for the specified `nick`. 141 | 142 | :param string nick: is a nickname 143 | :param function callback: Callback to fire when the server has finished 144 | generating the whois information and is passed exactly the same 145 | information as a `whois` event described above. 146 | 147 | .. js:function:: Client.list([arg1, arg2, ...]) 148 | 149 | Request a channel listing from the server. The arguments for this method are 150 | fairly server specific, this method just passes them through exactly as 151 | specified. 152 | 153 | Responses from the server are available via the `channellist_start`, 154 | `channellist_item`, and `channellist` events. 155 | 156 | .. js:function:: Client.connect([retryCount [, callback]]) 157 | 158 | Connects to the server. Used when `autoConnect` in the options is set to 159 | false. If `retryCount` is a function it will be treated as the `callback` 160 | (i.e. both arguments to this function are optional). 161 | 162 | :param integer retryCount: Optional number of times to attempt reconnection 163 | :param function callback: Optional callback 164 | 165 | .. js:function:: Client.disconnect([message [, callback]]) 166 | 167 | Disconnects from the IRC server. If `message` is a function it will be 168 | treated as the `callback` (i.e. both arguments to this function are 169 | optional). 170 | 171 | :param string message: Optional message to send when disconnecting. 172 | :param function callback: Optional callback 173 | 174 | .. js:function:: Client.activateFloodProtection([interval]) 175 | 176 | Activates flood protection "after the fact". You can also use 177 | `floodProtection` while instantiating the Client to enable flood 178 | protection, and `floodProtectionDelay` to set the default message 179 | interval. 180 | 181 | :param integer interval: Optional configuration for amount of time 182 | to wait between messages. Takes value from client configuration 183 | if unspecified. 184 | 185 | Events 186 | ------ 187 | 188 | `irc.Client` instances are EventEmitters with the following events: 189 | 190 | 191 | .. js:data:: 'registered' 192 | 193 | `function (message) { }` 194 | 195 | Emitted when the server sends the initial 001 line, indicating you've connected 196 | to the server. See the `raw` event for details on the `message` object. 197 | 198 | .. js:data:: 'motd' 199 | 200 | `function (motd) { }` 201 | 202 | Emitted when the server sends the message of the day to clients. 203 | 204 | .. js:data:: 'names' 205 | 206 | `function (channel, nicks) { }` 207 | 208 | Emitted when the server sends a list of nicks for a channel (which happens 209 | immediately after joining and on request. The nicks object passed to the 210 | callback is keyed by nick names, and has values '', '+', or '@' depending on the 211 | level of that nick in the channel. 212 | 213 | .. js:data:: 'names#channel' 214 | 215 | `function (nicks) { }` 216 | 217 | As per 'names' event but only emits for the subscribed channel. 218 | 219 | .. js:data:: 'topic' 220 | 221 | `function (channel, topic, nick, message) { }` 222 | 223 | Emitted when the server sends the channel topic on joining a channel, or when a 224 | user changes the topic on a channel. See the `raw` event for details on the 225 | `message` object. 226 | 227 | .. js:data:: 'join' 228 | 229 | `function (channel, nick, message) { }` 230 | 231 | Emitted when a user joins a channel (including when the client itself joins a 232 | channel). See the `raw` event for details on the `message` object. 233 | 234 | .. js:data:: 'join#channel' 235 | 236 | `function (nick, message) { }` 237 | 238 | As per 'join' event but only emits for the subscribed channel. 239 | See the `raw` event for details on the `message` object. 240 | 241 | .. js:data:: 'part' 242 | 243 | `function (channel, nick, reason, message) { }` 244 | 245 | Emitted when a user parts a channel (including when the client itself parts a 246 | channel). See the `raw` event for details on the `message` object. 247 | 248 | .. js:data:: 'part#channel' 249 | 250 | `function (nick, reason, message) { }` 251 | 252 | As per 'part' event but only emits for the subscribed channel. 253 | See the `raw` event for details on the `message` object. 254 | 255 | .. js:data:: 'quit' 256 | 257 | `function (nick, reason, channels, message) { }` 258 | 259 | Emitted when a user disconnects from the IRC, leaving the specified array of 260 | channels. See the `raw` event for details on the `message` object. 261 | 262 | .. js:data:: 'kick' 263 | 264 | `function (channel, nick, by, reason, message) { }` 265 | 266 | Emitted when a user is kicked from a channel. See the `raw` event for details 267 | on the `message` object. 268 | 269 | .. js:data:: 'kick#channel' 270 | 271 | `function (nick, by, reason, message) { }` 272 | 273 | As per 'kick' event but only emits for the subscribed channel. 274 | See the `raw` event for details on the `message` object. 275 | 276 | .. js:data:: 'kill' 277 | 278 | `function (nick, reason, channels, message) { }` 279 | 280 | Emitted when a user is killed from the IRC server. 281 | `channels` is an array of channels the killed user was in which 282 | are known to the client. 283 | See the `raw` event for details on the `message` object. 284 | 285 | .. js:data:: 'message' 286 | 287 | `function (nick, to, text, message) { }` 288 | 289 | Emitted when a message is sent. `to` can be either a nick (which is most likely 290 | this clients nick and means a private message), or a channel (which means a 291 | message to that channel). See the `raw` event for details on the `message` object. 292 | 293 | .. js:data:: 'message#' 294 | 295 | `function (nick, to, text, message) { }` 296 | 297 | Emitted when a message is sent to any channel (i.e. exactly the same as the 298 | `message` event but excluding private messages. 299 | See the `raw` event for details on the `message` object. 300 | 301 | .. js:data:: 'message#channel' 302 | 303 | `function (nick, text, message) { }` 304 | 305 | As per 'message' event but only emits for the subscribed channel. 306 | See the `raw` event for details on the `message` object. 307 | 308 | .. js:data:: 'selfMessage' 309 | 310 | `function (to, text) { }` 311 | 312 | Emitted when a message is sent from the client. `to` is who the message was 313 | sent to. It can be either a nick (which most likely means a private message), 314 | or a channel (which means a message to that channel). 315 | 316 | .. js:data:: 'notice' 317 | 318 | `function (nick, to, text, message) { }` 319 | 320 | Emitted when a notice is sent. `to` can be either a nick (which is most likely 321 | this clients nick and means a private message), or a channel (which means a 322 | message to that channel). `nick` is either the senders nick or `null` which 323 | means that the notice comes from the server. See the `raw` event for details 324 | on the `message` object. 325 | 326 | .. js:data:: 'ping' 327 | 328 | `function (server) { }` 329 | 330 | Emitted when a server PINGs the client. The client will automatically send a 331 | PONG request just before this is emitted. 332 | 333 | .. js:data:: 'pm' 334 | 335 | `function (nick, text, message) { }` 336 | 337 | As per 'message' event but only emits when the message is direct to the client. 338 | See the `raw` event for details on the `message` object. 339 | 340 | .. js:data:: 'ctcp' 341 | 342 | `function (from, to, text, type, message) { }` 343 | 344 | Emitted when a CTCP notice or privmsg was received (`type` is either `'notice'` 345 | or `'privmsg'`). See the `raw` event for details on the `message` object. 346 | 347 | .. js:data:: 'ctcp-notice' 348 | 349 | `function (from, to, text, message) { }` 350 | 351 | Emitted when a CTCP notice was received. 352 | See the `raw` event for details on the `message` object. 353 | 354 | .. js:data:: 'ctcp-privmsg' 355 | 356 | `function (from, to, text, message) { }` 357 | 358 | Emitted when a CTCP privmsg was received. 359 | See the `raw` event for details on the `message` object. 360 | 361 | .. js:data:: 'ctcp-version' 362 | 363 | `function (from, to, message) { }` 364 | 365 | Emitted when a CTCP VERSION request was received. 366 | See the `raw` event for details on the `message` object. 367 | 368 | .. js:data:: 'nick' 369 | 370 | `function (oldnick, newnick, channels, message) { }` 371 | 372 | Emitted when a user changes nick along with the channels the user is in. 373 | See the `raw` event for details on the `message` object. 374 | 375 | .. js:data:: 'invite' 376 | 377 | `function (channel, from, message) { }` 378 | 379 | Emitted when the client receives an `/invite`. See the `raw` event for details 380 | on the `message` object. 381 | 382 | .. js:data:: '+mode' 383 | 384 | `function (channel, by, mode, argument, message) { }` 385 | 386 | Emitted when a mode is added to a user or channel. `channel` is the channel 387 | which the mode is being set on/in. `by` is the user setting the mode. `mode` 388 | is the single character mode identifier. If the mode is being set on a user, 389 | `argument` is the nick of the user. If the mode is being set on a channel, 390 | `argument` is the argument to the mode. If a channel mode doesn't have any 391 | arguments, `argument` will be 'undefined'. See the `raw` event for details 392 | on the `message` object. 393 | 394 | .. js:data:: '-mode' 395 | 396 | `function (channel, by, mode, argument, message) { }` 397 | 398 | Emitted when a mode is removed from a user or channel. `channel` is the channel 399 | which the mode is being set on/in. `by` is the user setting the mode. `mode` 400 | is the single character mode identifier. If the mode is being set on a user, 401 | `argument` is the nick of the user. If the mode is being set on a channel, 402 | `argument` is the argument to the mode. If a channel mode doesn't have any 403 | arguments, `argument` will be 'undefined'. See the `raw` event for details 404 | on the `message` object. 405 | 406 | .. js:data:: 'whois' 407 | 408 | `function (info) { }` 409 | 410 | Emitted whenever the server finishes outputting a WHOIS response. The 411 | information should look something like:: 412 | 413 | { 414 | nick: "Ned", 415 | user: "martyn", 416 | host: "10.0.0.18", 417 | realname: "Unknown", 418 | channels: ["@#purpledishwashers", "#blah", "#mmmmbacon"], 419 | server: "*.dollyfish.net.nz", 420 | serverinfo: "The Dollyfish Underworld", 421 | operator: "is an IRC Operator" 422 | } 423 | 424 | .. js:data:: 'channellist_start' 425 | 426 | `function () {}` 427 | 428 | Emitted whenever the server starts a new channel listing 429 | 430 | .. js:data:: 'channellist_item' 431 | 432 | `function (channel_info) {}` 433 | 434 | Emitted for each channel the server returns. The channel_info object 435 | contains keys 'name', 'users' (number of users on the channel), and 'topic'. 436 | 437 | .. js:data:: 'channellist' 438 | 439 | `function (channel_list) {}` 440 | 441 | Emitted when the server has finished returning a channel list. The 442 | channel_list array is simply a list of the objects that were returned in the 443 | intervening `channellist_item` events. 444 | 445 | This data is also available via the Client.channellist property after this 446 | event has fired. 447 | 448 | .. js:data:: 'raw' 449 | 450 | `function (message) { }` 451 | 452 | Emitted when ever the client receives a "message" from the server. A message is 453 | basically a single line of data from the server, but the parameter to the 454 | callback has already been parsed and contains:: 455 | 456 | message = { 457 | prefix: "The prefix for the message (optional)", 458 | nick: "The nickname portion of the prefix (optional)", 459 | user: "The username portion of the prefix (optional)", 460 | host: "The hostname portion of the prefix (optional)", 461 | server: "The servername (if the prefix was a servername)", 462 | rawCommand: "The command exactly as sent from the server", 463 | command: "Human readable version of the command", 464 | commandType: "normal, error, or reply", 465 | args: ['arguments', 'to', 'the', 'command'], 466 | } 467 | 468 | You can read more about the IRC protocol by reading `RFC 1459 469 | `_ 470 | 471 | .. js:data:: 'error' 472 | 473 | `function (message) { }` 474 | 475 | Emitted when ever the server responds with an error-type message. The message 476 | parameter is exactly as in the 'raw' event. 477 | 478 | .. js:data:: 'action' 479 | 480 | `function (from, to, text, message) { }` 481 | 482 | Emitted whenever a user performs an action (e.g. `/me waves`). 483 | The message parameter is exactly as in the 'raw' event. 484 | 485 | Colors 486 | ------ 487 | 488 | .. js:function:: irc.colors.wrap(color, text [, reset_color]) 489 | 490 | Takes a color by name, text, and optionally what color to return. 491 | 492 | :param string color: the name of the color as a string 493 | :param string text: the text you want colorized 494 | :param string reset_color: the name of the color you want set after the text (defaults to 'reset') 495 | 496 | .. js:data:: irc.colors.codes 497 | 498 | This contains the set of colors available and a function to wrap text in a 499 | color. 500 | 501 | The following color choices are available: 502 | 503 | { 504 | white: '\u000300', 505 | black: '\u000301', 506 | dark_blue: '\u000302', 507 | dark_green: '\u000303', 508 | light_red: '\u000304', 509 | dark_red: '\u000305', 510 | magenta: '\u000306', 511 | orange: '\u000307', 512 | yellow: '\u000308', 513 | light_green: '\u000309', 514 | cyan: '\u000310', 515 | light_cyan: '\u000311', 516 | light_blue: '\u000312', 517 | light_magenta: '\u000313', 518 | gray: '\u000314', 519 | light_gray: '\u000315', 520 | reset: '\u000f', 521 | } 522 | 523 | Internal 524 | ------ 525 | 526 | .. js:data:: Client.conn 527 | 528 | Socket to the server. Rarely, if ever needed. Use `Client.send` instead. 529 | 530 | .. js:data:: Client.chans 531 | 532 | Channels joined. Includes channel modes, user list, and topic information. Only updated *after* the server recognizes the join. 533 | 534 | .. js:data:: Client.nick 535 | 536 | The current nick of the client. Updated if the nick changes (e.g. nick collision when connecting to a server). 537 | 538 | .. js:function:: client._whoisData 539 | 540 | Buffer of whois data as whois is sent over multiple lines. 541 | 542 | .. js:function:: client._addWhoisData 543 | 544 | Self-explanatory. 545 | 546 | .. js:function:: client._clearWhoisData 547 | 548 | Self-explanatory. 549 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v0.5.2](https://github.com/martynsmith/node-irc/tree/v0.5.2) (2016-11-25) 4 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.5.1...v0.5.2) 5 | 6 | **Merged pull requests:** 7 | 8 | - Update iconv to 2.2.1 and node-icu-charset-detector to 0.2.0 to fix node 6+ support [\#487](https://github.com/martynsmith/node-irc/pull/487) ([paladox](https://github.com/paladox)) 9 | 10 | ## [v0.5.1](https://github.com/martynsmith/node-irc/tree/v0.5.1) (2016-11-17) 11 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.5.0...v0.5.1) 12 | 13 | **Implemented enhancements:** 14 | 15 | - Detect ping timeout [\#76](https://github.com/martynsmith/node-irc/issues/76) 16 | 17 | **Fixed bugs:** 18 | 19 | - Call stack size exceeded [\#337](https://github.com/martynsmith/node-irc/issues/337) 20 | - Many servers do not send a meaningful hostname in 001 [\#288](https://github.com/martynsmith/node-irc/issues/288) 21 | - disconnect does not appear to send the reason to the server [\#89](https://github.com/martynsmith/node-irc/issues/89) 22 | 23 | **Closed issues:** 24 | 25 | - Creating a whitelist against the nickname [\#484](https://github.com/martynsmith/node-irc/issues/484) 26 | - Deployed on Heroku, app is running, no IRC connection [\#481](https://github.com/martynsmith/node-irc/issues/481) 27 | - Install does not work on Debian stable. [\#475](https://github.com/martynsmith/node-irc/issues/475) 28 | - Non private reply with highlighted nick [\#474](https://github.com/martynsmith/node-irc/issues/474) 29 | - retryDelay not mentioned in the docs [\#446](https://github.com/martynsmith/node-irc/issues/446) 30 | - 'names' event returns only 10 nicks [\#414](https://github.com/martynsmith/node-irc/issues/414) 31 | - can't get chat messages [\#384](https://github.com/martynsmith/node-irc/issues/384) 32 | - parse message TypeError: Cannot read property '1' of null [\#331](https://github.com/martynsmith/node-irc/issues/331) 33 | - TypeError: No method channel [\#254](https://github.com/martynsmith/node-irc/issues/254) 34 | - Unable to connect to OFTC network over SSL [\#247](https://github.com/martynsmith/node-irc/issues/247) 35 | - Specific mode sequences can crash the bot [\#233](https://github.com/martynsmith/node-irc/issues/233) 36 | - Event listener ctcp-privmsg's "message" is empty [\#207](https://github.com/martynsmith/node-irc/issues/207) 37 | - Mass channel MODE with -lk throws error [\#177](https://github.com/martynsmith/node-irc/issues/177) 38 | 39 | **Merged pull requests:** 40 | 41 | - Respect opt.messageSplit when calculating message length [\#385](https://github.com/martynsmith/node-irc/pull/385) ([LinuxMercedes](https://github.com/LinuxMercedes)) 42 | 43 | ## [v0.5.0](https://github.com/martynsmith/node-irc/tree/v0.5.0) (2016-03-26) 44 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.4.1...v0.5.0) 45 | 46 | **Implemented enhancements:** 47 | 48 | - Allow tilde in nicks [\#438](https://github.com/martynsmith/node-irc/pull/438) ([hexjelly](https://github.com/hexjelly)) 49 | 50 | **Fixed bugs:** 51 | 52 | - Fixes \#427 [\#429](https://github.com/martynsmith/node-irc/pull/429) ([ghost](https://github.com/ghost)) 53 | 54 | **Closed issues:** 55 | 56 | - How to get current server. [\#453](https://github.com/martynsmith/node-irc/issues/453) 57 | - Library never connects to server [\#451](https://github.com/martynsmith/node-irc/issues/451) 58 | - Ping timeout causes double reconnect [\#449](https://github.com/martynsmith/node-irc/issues/449) 59 | - Changelog for v4.0? [\#435](https://github.com/martynsmith/node-irc/issues/435) 60 | - How to multiple server connections at the same time [\#434](https://github.com/martynsmith/node-irc/issues/434) 61 | - Add connected flag [\#430](https://github.com/martynsmith/node-irc/issues/430) 62 | - Add link to docs on github wiki page [\#422](https://github.com/martynsmith/node-irc/issues/422) 63 | - maxLineLength is not set by default and can crash the bot [\#419](https://github.com/martynsmith/node-irc/issues/419) 64 | - PING/PONG Error! [\#415](https://github.com/martynsmith/node-irc/issues/415) 65 | - quit event provides wrong channel information [\#398](https://github.com/martynsmith/node-irc/issues/398) 66 | - Detect client timeout ? [\#375](https://github.com/martynsmith/node-irc/issues/375) 67 | - User MODE changes are not being received in +MODE/-MODE handlers [\#374](https://github.com/martynsmith/node-irc/issues/374) 68 | - Error client.say\(nick, "record\\w3xp\\random\\wins"\); [\#369](https://github.com/martynsmith/node-irc/issues/369) 69 | - SASL over SSL never happens [\#250](https://github.com/martynsmith/node-irc/issues/250) 70 | - Message Events Ignored [\#242](https://github.com/martynsmith/node-irc/issues/242) 71 | - Bot crashes on mode +q-o [\#221](https://github.com/martynsmith/node-irc/issues/221) 72 | - Cannot pass MODE command with multiple arguments [\#147](https://github.com/martynsmith/node-irc/issues/147) 73 | - Certain MODE messages could access on undefined [\#144](https://github.com/martynsmith/node-irc/issues/144) 74 | - mode emit event [\#136](https://github.com/martynsmith/node-irc/issues/136) 75 | - QUIT, KILL removes users from user list before processing event hooks [\#73](https://github.com/martynsmith/node-irc/issues/73) 76 | 77 | **Merged pull requests:** 78 | 79 | - fix\(ping timeouts\): When a ping timeout is detected properly destroy … [\#452](https://github.com/martynsmith/node-irc/pull/452) ([jirwin](https://github.com/jirwin)) 80 | - Added link to install instructions for ICU [\#450](https://github.com/martynsmith/node-irc/pull/450) ([spalger](https://github.com/spalger)) 81 | - User status isn't updated on MODE if he's not VOICE or OP [\#448](https://github.com/martynsmith/node-irc/pull/448) ([Zoddo](https://github.com/Zoddo)) 82 | - Add a Gitter chat badge to README.md [\#444](https://github.com/martynsmith/node-irc/pull/444) ([gitter-badger](https://github.com/gitter-badger)) 83 | - Detect and recover from ping timeouts [\#418](https://github.com/martynsmith/node-irc/pull/418) ([philip-peterson](https://github.com/philip-peterson)) 84 | - Adding support for command rpl\_whoreply \(352\) [\#413](https://github.com/martynsmith/node-irc/pull/413) ([lan17](https://github.com/lan17)) 85 | - Update .gitignore [\#373](https://github.com/martynsmith/node-irc/pull/373) ([Phalanxia](https://github.com/Phalanxia)) 86 | - Update license attribute [\#372](https://github.com/martynsmith/node-irc/pull/372) ([pdehaan](https://github.com/pdehaan)) 87 | 88 | ## [v0.4.1](https://github.com/martynsmith/node-irc/tree/v0.4.1) (2016-01-27) 89 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.4.0...v0.4.1) 90 | 91 | **Implemented enhancements:** 92 | 93 | - Dealing with OPER command replies. [\#439](https://github.com/martynsmith/node-irc/pull/439) ([ellisgl](https://github.com/ellisgl)) 94 | 95 | **Fixed bugs:** 96 | 97 | - Fix SASL auth [\#443](https://github.com/martynsmith/node-irc/pull/443) ([ggreer](https://github.com/ggreer)) 98 | 99 | **Closed issues:** 100 | 101 | - Can't use it sadly [\#433](https://github.com/martynsmith/node-irc/issues/433) 102 | - how do I auto reconnect if the server goes down? [\#431](https://github.com/martynsmith/node-irc/issues/431) 103 | - WebIRC Support [\#427](https://github.com/martynsmith/node-irc/issues/427) 104 | - Error Handling Improvements \(all errors should gracefully fail\) [\#421](https://github.com/martynsmith/node-irc/issues/421) 105 | - client.send\(\) always include : in first text [\#420](https://github.com/martynsmith/node-irc/issues/420) 106 | - node-irc with express/socket.io [\#417](https://github.com/martynsmith/node-irc/issues/417) 107 | - Not enough parameters' [\#416](https://github.com/martynsmith/node-irc/issues/416) 108 | - Help with error [\#393](https://github.com/martynsmith/node-irc/issues/393) 109 | - Microsoft Visual Studio needed to install this in windoze [\#390](https://github.com/martynsmith/node-irc/issues/390) 110 | - oper command [\#234](https://github.com/martynsmith/node-irc/issues/234) 111 | 112 | **Merged pull requests:** 113 | 114 | - Remove \#blah from the example [\#440](https://github.com/martynsmith/node-irc/pull/440) ([ben-rabid](https://github.com/ben-rabid)) 115 | - Move dependency 'ansi-color' to devDependencies [\#407](https://github.com/martynsmith/node-irc/pull/407) ([ho-ho-ho](https://github.com/ho-ho-ho)) 116 | 117 | ## [v0.4.0](https://github.com/martynsmith/node-irc/tree/v0.4.0) (2015-09-30) 118 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.3.12...v0.4.0) 119 | 120 | **Fixed bugs:** 121 | 122 | - Fix compile warnings on node v4 [\#409](https://github.com/martynsmith/node-irc/pull/409) ([feross](https://github.com/feross)) 123 | 124 | **Closed issues:** 125 | 126 | - Error: Cannot enqueue Handshake after already enqueuing a Handshake. [\#404](https://github.com/martynsmith/node-irc/issues/404) 127 | - How to get current Config? [\#401](https://github.com/martynsmith/node-irc/issues/401) 128 | - Error Installing [\#400](https://github.com/martynsmith/node-irc/issues/400) 129 | - maxLineLength undefined when splitting long lines [\#395](https://github.com/martynsmith/node-irc/issues/395) 130 | - Package 'ansi-color' not found [\#389](https://github.com/martynsmith/node-irc/issues/389) 131 | - speak function bug, can't compile [\#388](https://github.com/martynsmith/node-irc/issues/388) 132 | - Error undefined nick [\#371](https://github.com/martynsmith/node-irc/issues/371) 133 | - Send CustomCommand to server [\#367](https://github.com/martynsmith/node-irc/issues/367) 134 | - The framework constantly crashes - "Cannot call method 'replace' of undefined" [\#364](https://github.com/martynsmith/node-irc/issues/364) 135 | - Trying to make a bot and can't figure out how to kick and do other op tasks [\#363](https://github.com/martynsmith/node-irc/issues/363) 136 | - Update Client.chans on change MODE [\#361](https://github.com/martynsmith/node-irc/issues/361) 137 | - Can node-irc determine who is a mod? [\#340](https://github.com/martynsmith/node-irc/issues/340) 138 | - Config with Password? [\#336](https://github.com/martynsmith/node-irc/issues/336) 139 | - Update node-icu-charset-detector version for nodejs 0.12 compatibility [\#332](https://github.com/martynsmith/node-irc/issues/332) 140 | - \[Question\] Timestamps or how much time a user has been connected? [\#321](https://github.com/martynsmith/node-irc/issues/321) 141 | 142 | **Merged pull requests:** 143 | 144 | - Bug fix: 'pm' event wouldnt always be trigged [\#397](https://github.com/martynsmith/node-irc/pull/397) ([ravenstar](https://github.com/ravenstar)) 145 | - Call updateMaxLineLength on connection [\#396](https://github.com/martynsmith/node-irc/pull/396) ([secretrobotron](https://github.com/secretrobotron)) 146 | - Fix typo. [\#383](https://github.com/martynsmith/node-irc/pull/383) ([schmich](https://github.com/schmich)) 147 | - SyntaxError: Octal literals are not allowed in strict mode. [\#368](https://github.com/martynsmith/node-irc/pull/368) ([tom--](https://github.com/tom--)) 148 | - Fix channel user modes on Twitch IRC \(closes \#364\) [\#366](https://github.com/martynsmith/node-irc/pull/366) ([sim642](https://github.com/sim642)) 149 | 150 | ## [v0.3.12](https://github.com/martynsmith/node-irc/tree/v0.3.12) (2015-04-25) 151 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.3.11...v0.3.12) 152 | 153 | **Closed issues:** 154 | 155 | - Document 'selfMessage' from \#17 [\#349](https://github.com/martynsmith/node-irc/issues/349) 156 | - Random crash after rpl\_luserunknown [\#342](https://github.com/martynsmith/node-irc/issues/342) 157 | 158 | **Merged pull requests:** 159 | 160 | - Cosmetics: fix minor spelling mistakes \[ci skip\] [\#356](https://github.com/martynsmith/node-irc/pull/356) ([vBm](https://github.com/vBm)) 161 | - Travis: Sort supported node versions [\#355](https://github.com/martynsmith/node-irc/pull/355) ([vBm](https://github.com/vBm)) 162 | - Readme: Add badges for npm version, dependency status and license [\#354](https://github.com/martynsmith/node-irc/pull/354) ([vBm](https://github.com/vBm)) 163 | - Fix for unrealircd auditorium [\#352](https://github.com/martynsmith/node-irc/pull/352) ([PNWebster](https://github.com/PNWebster)) 164 | - Add information about action events in the docs [\#350](https://github.com/martynsmith/node-irc/pull/350) ([ekmartin](https://github.com/ekmartin)) 165 | - Fix charset conversion for invalid charsets [\#347](https://github.com/martynsmith/node-irc/pull/347) ([aivot-on](https://github.com/aivot-on)) 166 | - fix\(travis\): Add node 0.12 and iojs to travis. [\#333](https://github.com/martynsmith/node-irc/pull/333) ([jirwin](https://github.com/jirwin)) 167 | 168 | ## [v0.3.11](https://github.com/martynsmith/node-irc/tree/v0.3.11) (2015-04-06) 169 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.3.10...v0.3.11) 170 | 171 | ## [v0.3.10](https://github.com/martynsmith/node-irc/tree/v0.3.10) (2015-04-02) 172 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.3.9...v0.3.10) 173 | 174 | **Closed issues:** 175 | 176 | - Error with node-icu-charset-detector [\#327](https://github.com/martynsmith/node-irc/issues/327) 177 | - Cannot call method 'match' of undefined [\#326](https://github.com/martynsmith/node-irc/issues/326) 178 | - TypeError: Cannot read property '1' of null [\#325](https://github.com/martynsmith/node-irc/issues/325) 179 | - Crashes if channel is undefined on say command [\#314](https://github.com/martynsmith/node-irc/issues/314) 180 | - Issue installing on OS X Mavericks [\#308](https://github.com/martynsmith/node-irc/issues/308) 181 | 182 | **Merged pull requests:** 183 | 184 | - Fixed case sensitivity bug in client.whois\(\) [\#338](https://github.com/martynsmith/node-irc/pull/338) ([itsrachelfish](https://github.com/itsrachelfish)) 185 | - fix\(deps\): Upgrade node-icu to 0.1.0 for v0.12 support. [\#334](https://github.com/martynsmith/node-irc/pull/334) ([jirwin](https://github.com/jirwin)) 186 | - Update API documentation with missing event and internal variable [\#330](https://github.com/martynsmith/node-irc/pull/330) ([RyanMorrison04](https://github.com/RyanMorrison04)) 187 | - Documentation improvements [\#323](https://github.com/martynsmith/node-irc/pull/323) ([TimothyGu](https://github.com/TimothyGu)) 188 | - fix blank lines being passed to parse message [\#318](https://github.com/martynsmith/node-irc/pull/318) ([helderroem](https://github.com/helderroem)) 189 | - Rember to add path.resolve while requiring things! [\#316](https://github.com/martynsmith/node-irc/pull/316) ([Palid](https://github.com/Palid)) 190 | - Fix option handling when passing a secure object [\#311](https://github.com/martynsmith/node-irc/pull/311) ([masochist](https://github.com/masochist)) 191 | - Added a bit more information about Client.chans [\#310](https://github.com/martynsmith/node-irc/pull/310) ([itsrachelfish](https://github.com/itsrachelfish)) 192 | 193 | ## [v0.3.9](https://github.com/martynsmith/node-irc/tree/v0.3.9) (2015-01-16) 194 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.3.8...v0.3.9) 195 | 196 | **Implemented enhancements:** 197 | 198 | - Factor out test data into a fixtures file. [\#302](https://github.com/martynsmith/node-irc/pull/302) ([masochist](https://github.com/masochist)) 199 | 200 | **Fixed bugs:** 201 | 202 | - Fix TLS connections. [\#304](https://github.com/martynsmith/node-irc/pull/304) ([masochist](https://github.com/masochist)) 203 | 204 | **Closed issues:** 205 | 206 | - Please add feature for IRCv3 message tags! [\#298](https://github.com/martynsmith/node-irc/issues/298) 207 | - Switch to irc-color [\#297](https://github.com/martynsmith/node-irc/issues/297) 208 | - SSL Broken as of v0.3.8 [\#296](https://github.com/martynsmith/node-irc/issues/296) 209 | - Version 0.3.8 failed while using hubot-irc [\#289](https://github.com/martynsmith/node-irc/issues/289) 210 | - Loading self signed certs [\#262](https://github.com/martynsmith/node-irc/issues/262) 211 | - 0.3.x : 'nicknameinuse' event missing [\#258](https://github.com/martynsmith/node-irc/issues/258) 212 | - Is there an autoConnect callback? [\#239](https://github.com/martynsmith/node-irc/issues/239) 213 | 214 | **Merged pull requests:** 215 | 216 | - Log net connection errors. Thanks Trinitas. [\#307](https://github.com/martynsmith/node-irc/pull/307) ([jirwin](https://github.com/jirwin)) 217 | - Bring in irc-colors for stripping colors [\#306](https://github.com/martynsmith/node-irc/pull/306) ([masochist](https://github.com/masochist)) 218 | - do not autorejoin on kicks. bad bot! no cookie! [\#303](https://github.com/martynsmith/node-irc/pull/303) ([masochist](https://github.com/masochist)) 219 | - fix\(style\): Clean up various style issues in irc.js [\#299](https://github.com/martynsmith/node-irc/pull/299) ([jirwin](https://github.com/jirwin)) 220 | - Write a test for setting the hostmask when nick is in use [\#294](https://github.com/martynsmith/node-irc/pull/294) ([masochist](https://github.com/masochist)) 221 | - fix\(parseMessage\): Factor parseMessage to another file for decoupling. [\#293](https://github.com/martynsmith/node-irc/pull/293) ([jirwin](https://github.com/jirwin)) 222 | - Set self.hostMask to the empty string to elegantly solve \#286 [\#292](https://github.com/martynsmith/node-irc/pull/292) ([masochist](https://github.com/masochist)) 223 | - First draft of contributing doc [\#287](https://github.com/martynsmith/node-irc/pull/287) ([masochist](https://github.com/masochist)) 224 | - Fix data split delimiter [\#280](https://github.com/martynsmith/node-irc/pull/280) ([ota42y](https://github.com/ota42y)) 225 | 226 | ## [v0.3.8](https://github.com/martynsmith/node-irc/tree/v0.3.8) (2015-01-09) 227 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.3.7...v0.3.8) 228 | 229 | **Fixed bugs:** 230 | 231 | - Client.whois on nick not in use crashes bot running with v.0.3.3 [\#267](https://github.com/martynsmith/node-irc/issues/267) 232 | 233 | **Closed issues:** 234 | 235 | - Documentation on RTD gone? [\#264](https://github.com/martynsmith/node-irc/issues/264) 236 | - Allow passworded IRC connections [\#263](https://github.com/martynsmith/node-irc/issues/263) 237 | - Parse RPL\_CREATIONTIME [\#260](https://github.com/martynsmith/node-irc/issues/260) 238 | - News from 0.3.x? [\#259](https://github.com/martynsmith/node-irc/issues/259) 239 | - The master branch is not up to date with npm [\#257](https://github.com/martynsmith/node-irc/issues/257) 240 | - Browserify support? [\#253](https://github.com/martynsmith/node-irc/issues/253) 241 | - self.chan and self.chandata events [\#243](https://github.com/martynsmith/node-irc/issues/243) 242 | 243 | **Merged pull requests:** 244 | 245 | - fix\(webirc\): Set sane defaults for WEBIRC options. [\#283](https://github.com/martynsmith/node-irc/pull/283) ([jirwin](https://github.com/jirwin)) 246 | - WIP: fix\(tests\): A first attempt at a sane pattern to begin testing the handling of the protocol. [\#282](https://github.com/martynsmith/node-irc/pull/282) ([jirwin](https://github.com/jirwin)) 247 | - fix\(irc.js\): Use the proper EventEmitter class. [\#281](https://github.com/martynsmith/node-irc/pull/281) ([jirwin](https://github.com/jirwin)) 248 | - Update colors.js [\#279](https://github.com/martynsmith/node-irc/pull/279) ([bcome](https://github.com/bcome)) 249 | - Optional encoding option [\#278](https://github.com/martynsmith/node-irc/pull/278) ([tarlepp](https://github.com/tarlepp)) 250 | - WEBIRC support [\#276](https://github.com/martynsmith/node-irc/pull/276) ([Trinitas](https://github.com/Trinitas)) 251 | - fix\(style\): Remove folding hints from codes and irc. [\#275](https://github.com/martynsmith/node-irc/pull/275) ([jirwin](https://github.com/jirwin)) 252 | - fix\(tests\): Ditch mocha and should for tape! [\#274](https://github.com/martynsmith/node-irc/pull/274) ([jirwin](https://github.com/jirwin)) 253 | - Add travis with lint and tests [\#271](https://github.com/martynsmith/node-irc/pull/271) ([jirwin](https://github.com/jirwin)) 254 | - Add proper long line wrapping. [\#268](https://github.com/martynsmith/node-irc/pull/268) ([masochist](https://github.com/masochist)) 255 | - update README regarding npm and the 0.3.x branch [\#256](https://github.com/martynsmith/node-irc/pull/256) ([mbouchenoire](https://github.com/mbouchenoire)) 256 | - Updated API information [\#240](https://github.com/martynsmith/node-irc/pull/240) ([Hydrothermal](https://github.com/Hydrothermal)) 257 | - Add option to specify bind address when connecting [\#146](https://github.com/martynsmith/node-irc/pull/146) ([revmischa](https://github.com/revmischa)) 258 | 259 | ## [v0.3.7](https://github.com/martynsmith/node-irc/tree/v0.3.7) (2014-05-29) 260 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.3.5...v0.3.7) 261 | 262 | **Closed issues:** 263 | 264 | - Sending nick out of sequence can cause exceptions [\#235](https://github.com/martynsmith/node-irc/issues/235) 265 | - Events need a different approach [\#231](https://github.com/martynsmith/node-irc/issues/231) 266 | - Check if an user is a voice, half-operator, operator,... [\#230](https://github.com/martynsmith/node-irc/issues/230) 267 | - my script throws error "You have not registered" [\#229](https://github.com/martynsmith/node-irc/issues/229) 268 | - Cannot call method 'indexOf' of undefined [\#227](https://github.com/martynsmith/node-irc/issues/227) 269 | - I need SPEED ! [\#223](https://github.com/martynsmith/node-irc/issues/223) 270 | - With stripColors: true set, a post only containing control characters, such as color or bold crashes the library [\#218](https://github.com/martynsmith/node-irc/issues/218) 271 | - Bot Disconnects Every 10 Minutes [\#215](https://github.com/martynsmith/node-irc/issues/215) 272 | - State of project [\#213](https://github.com/martynsmith/node-irc/issues/213) 273 | - add the 'action' event to the documentation [\#212](https://github.com/martynsmith/node-irc/issues/212) 274 | - line ending problem: module does not support UNIX Line Ending [\#208](https://github.com/martynsmith/node-irc/issues/208) 275 | - identify command? [\#205](https://github.com/martynsmith/node-irc/issues/205) 276 | - looking for a maintainer? [\#197](https://github.com/martynsmith/node-irc/issues/197) 277 | - pm only works with mirc clients? [\#196](https://github.com/martynsmith/node-irc/issues/196) 278 | - message time [\#195](https://github.com/martynsmith/node-irc/issues/195) 279 | - Ping Pong idea [\#194](https://github.com/martynsmith/node-irc/issues/194) 280 | - examples not working [\#193](https://github.com/martynsmith/node-irc/issues/193) 281 | - Code reuse and license compliance [\#192](https://github.com/martynsmith/node-irc/issues/192) 282 | - Pull requests building up in backlog [\#189](https://github.com/martynsmith/node-irc/issues/189) 283 | - Bold text [\#185](https://github.com/martynsmith/node-irc/issues/185) 284 | - Support for server-time extension [\#184](https://github.com/martynsmith/node-irc/issues/184) 285 | - client.removeListener [\#180](https://github.com/martynsmith/node-irc/issues/180) 286 | - Adding callback to say\(\) method of Node IRC client [\#179](https://github.com/martynsmith/node-irc/issues/179) 287 | - Getting "Assertion failed" error with secure:true flag [\#178](https://github.com/martynsmith/node-irc/issues/178) 288 | - PRIVMSG that starts with : causes crash [\#173](https://github.com/martynsmith/node-irc/issues/173) 289 | - MODE change resulting in constant crash [\#171](https://github.com/martynsmith/node-irc/issues/171) 290 | - client.addListener\("message\#Channel" bug [\#169](https://github.com/martynsmith/node-irc/issues/169) 291 | - Reconnection fails because of nick modification [\#168](https://github.com/martynsmith/node-irc/issues/168) 292 | - When sending NICK command, the channel returned is lowercase [\#167](https://github.com/martynsmith/node-irc/issues/167) 293 | - Crash when using NAMES command [\#163](https://github.com/martynsmith/node-irc/issues/163) 294 | - Incompatible with Node 0.10.x with `secure` is `true` [\#160](https://github.com/martynsmith/node-irc/issues/160) 295 | - Handling ISO-8859-1 characters [\#157](https://github.com/martynsmith/node-irc/issues/157) 296 | - Cannot login to twitch irc [\#156](https://github.com/martynsmith/node-irc/issues/156) 297 | - Problem with connecting to Inspircd server [\#154](https://github.com/martynsmith/node-irc/issues/154) 298 | - Method for specifying the user's hostname [\#153](https://github.com/martynsmith/node-irc/issues/153) 299 | - Limit output [\#152](https://github.com/martynsmith/node-irc/issues/152) 300 | - Change nick at runtime? [\#149](https://github.com/martynsmith/node-irc/issues/149) 301 | - how to connect with a server password for twitchtv/justintv? [\#148](https://github.com/martynsmith/node-irc/issues/148) 302 | - please delete it [\#141](https://github.com/martynsmith/node-irc/issues/141) 303 | - Ensure QUIT message is processed correctly when using flood protection [\#138](https://github.com/martynsmith/node-irc/issues/138) 304 | - add connection parameters to include userName and realName [\#135](https://github.com/martynsmith/node-irc/issues/135) 305 | - Add an 'action' event [\#134](https://github.com/martynsmith/node-irc/issues/134) 306 | - chat server connection errors [\#127](https://github.com/martynsmith/node-irc/issues/127) 307 | - CTCP event should provide message object \(similar to message\# event\) [\#126](https://github.com/martynsmith/node-irc/issues/126) 308 | - new npm release? [\#124](https://github.com/martynsmith/node-irc/issues/124) 309 | - MODE messages don't appear to work correctly with JustinTV/TwitchTV chat. [\#123](https://github.com/martynsmith/node-irc/issues/123) 310 | - Colons in user messages cause issues [\#122](https://github.com/martynsmith/node-irc/issues/122) 311 | - rpl\_channelmodeis messages are not parsed correctly [\#120](https://github.com/martynsmith/node-irc/issues/120) 312 | - Issue with Non-ASCII Nick [\#104](https://github.com/martynsmith/node-irc/issues/104) 313 | 314 | **Merged pull requests:** 315 | 316 | - Fixes \#235 type error where channel does not exist [\#236](https://github.com/martynsmith/node-irc/pull/236) ([qq99](https://github.com/qq99)) 317 | - support for use\_strict [\#228](https://github.com/martynsmith/node-irc/pull/228) ([tedgoddard](https://github.com/tedgoddard)) 318 | - Fixed irc not connecting to selfsigned servers [\#201](https://github.com/martynsmith/node-irc/pull/201) ([antonva](https://github.com/antonva)) 319 | - added 'err\_erroneusnickname' message case' [\#191](https://github.com/martynsmith/node-irc/pull/191) ([redshark1802](https://github.com/redshark1802)) 320 | - fix\(package.json\): Add ansi-color to the package dependencies. [\#188](https://github.com/martynsmith/node-irc/pull/188) ([jirwin](https://github.com/jirwin)) 321 | - fix\(lib/irc\): Use protected loops when iterating channels to remove users [\#187](https://github.com/martynsmith/node-irc/pull/187) ([jirwin](https://github.com/jirwin)) 322 | - Fix the color wrap function [\#186](https://github.com/martynsmith/node-irc/pull/186) ([cattode](https://github.com/cattode)) 323 | - Hide 'Sending irc NICK/User' debug msg [\#183](https://github.com/martynsmith/node-irc/pull/183) ([porjo](https://github.com/porjo)) 324 | - Added bold/underline "colors" [\#170](https://github.com/martynsmith/node-irc/pull/170) ([BenjaminRH](https://github.com/BenjaminRH)) 325 | - Fix Cient.join: when user specify a password [\#166](https://github.com/martynsmith/node-irc/pull/166) ([macpie](https://github.com/macpie)) 326 | - Fix a crash bug when a zero length message is received [\#165](https://github.com/martynsmith/node-irc/pull/165) ([shiwano](https://github.com/shiwano)) 327 | - Change to be a non-existing server/channel [\#162](https://github.com/martynsmith/node-irc/pull/162) ([chilts](https://github.com/chilts)) 328 | - Add support for client certificates in connection handling [\#161](https://github.com/martynsmith/node-irc/pull/161) ([squeeks](https://github.com/squeeks)) 329 | - fixed a small typo for util.log\(\) on MODE change [\#158](https://github.com/martynsmith/node-irc/pull/158) ([JohnMaguire](https://github.com/JohnMaguire)) 330 | - Fix: codes variable is leaked to global scope [\#155](https://github.com/martynsmith/node-irc/pull/155) ([garyc40](https://github.com/garyc40)) 331 | - Added user message support for PART [\#140](https://github.com/martynsmith/node-irc/pull/140) ([qsheets](https://github.com/qsheets)) 332 | - Fix for receiving messages with colons [\#137](https://github.com/martynsmith/node-irc/pull/137) ([qsheets](https://github.com/qsheets)) 333 | - add names for five numerics; fix handling of 002 and 003 [\#131](https://github.com/martynsmith/node-irc/pull/131) ([rwg](https://github.com/rwg)) 334 | - provide message object to ctcp events [\#130](https://github.com/martynsmith/node-irc/pull/130) ([damianb](https://github.com/damianb)) 335 | - add names\#channel event [\#129](https://github.com/martynsmith/node-irc/pull/129) ([ydnax](https://github.com/ydnax)) 336 | - Added SASL support [\#125](https://github.com/martynsmith/node-irc/pull/125) ([gsf](https://github.com/gsf)) 337 | 338 | ## [v0.3.5](https://github.com/martynsmith/node-irc/tree/v0.3.5) (2013-01-01) 339 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.3.3...v0.3.5) 340 | 341 | **Closed issues:** 342 | 343 | - How to handle disconnects? [\#117](https://github.com/martynsmith/node-irc/issues/117) 344 | - opping and kicking on freenode.net [\#110](https://github.com/martynsmith/node-irc/issues/110) 345 | - 'LOAD' event issue? [\#108](https://github.com/martynsmith/node-irc/issues/108) 346 | - TOPIC command doesn't play nicely with send function [\#98](https://github.com/martynsmith/node-irc/issues/98) 347 | - Add support for more channel types [\#97](https://github.com/martynsmith/node-irc/issues/97) 348 | - Passing a large array of channels causes flood kick [\#96](https://github.com/martynsmith/node-irc/issues/96) 349 | - Issuing NICK during session breaks JOIN [\#93](https://github.com/martynsmith/node-irc/issues/93) 350 | - Make Client writable/readable stream? [\#92](https://github.com/martynsmith/node-irc/issues/92) 351 | - whois + host with IPv6 leads to info.host = '0' [\#90](https://github.com/martynsmith/node-irc/issues/90) 352 | - Sending PASS to password protected irc server prepends : [\#87](https://github.com/martynsmith/node-irc/issues/87) 353 | - 'invite' not emitted [\#86](https://github.com/martynsmith/node-irc/issues/86) 354 | - uncaught error on data listener [\#83](https://github.com/martynsmith/node-irc/issues/83) 355 | - Disable stdout for server events without specific listeners [\#78](https://github.com/martynsmith/node-irc/issues/78) 356 | - Handle server connection failures gracefully [\#74](https://github.com/martynsmith/node-irc/issues/74) 357 | - Emit mode change events [\#70](https://github.com/martynsmith/node-irc/issues/70) 358 | - Emit channels for "kill" like for "quit" [\#69](https://github.com/martynsmith/node-irc/issues/69) 359 | - Handle errors when parsing message [\#64](https://github.com/martynsmith/node-irc/issues/64) 360 | - Event on successful pm [\#56](https://github.com/martynsmith/node-irc/issues/56) 361 | - \[Feature request\] Automatic flood protection [\#36](https://github.com/martynsmith/node-irc/issues/36) 362 | - refactor node-irc to emit only one 'event' object [\#18](https://github.com/martynsmith/node-irc/issues/18) 363 | 364 | **Merged pull requests:** 365 | 366 | - Added library support for the RPL\_ISUPPORT server reply [\#114](https://github.com/martynsmith/node-irc/pull/114) ([qsheets](https://github.com/qsheets)) 367 | - Fixed the message splitting on Client.say [\#112](https://github.com/martynsmith/node-irc/pull/112) ([Pumpuli](https://github.com/Pumpuli)) 368 | - Fixed the message object being modified on MODE command. [\#111](https://github.com/martynsmith/node-irc/pull/111) ([Pumpuli](https://github.com/Pumpuli)) 369 | - Add option to split long messages into multiple PRIVMSG calls [\#106](https://github.com/martynsmith/node-irc/pull/106) ([PherricOxide](https://github.com/PherricOxide)) 370 | - Restore Fix for: Handle unverifiable self-signed certificates. [\#102](https://github.com/martynsmith/node-irc/pull/102) ([4poc](https://github.com/4poc)) 371 | - If needed, update self.nick when NICK is received [\#94](https://github.com/martynsmith/node-irc/pull/94) ([toolness](https://github.com/toolness)) 372 | - This fixes the IPv6-Issue \#90 for me. [\#91](https://github.com/martynsmith/node-irc/pull/91) ([ccoenen](https://github.com/ccoenen)) 373 | - Make flood protection timeout setting configurable. [\#84](https://github.com/martynsmith/node-irc/pull/84) ([lewinski](https://github.com/lewinski)) 374 | - Event emiter for bad connection [\#77](https://github.com/martynsmith/node-irc/pull/77) ([akavlie](https://github.com/akavlie)) 375 | - Include channels user was in when 'kill' is emitted [\#72](https://github.com/martynsmith/node-irc/pull/72) ([alexwhitman](https://github.com/alexwhitman)) 376 | - Emit +mode and -mode on mode changes [\#71](https://github.com/martynsmith/node-irc/pull/71) ([alexwhitman](https://github.com/alexwhitman)) 377 | - Fix problem with 'QUIT' command [\#68](https://github.com/martynsmith/node-irc/pull/68) ([tapichu](https://github.com/tapichu)) 378 | - Emit 'message\#' for a channel message [\#67](https://github.com/martynsmith/node-irc/pull/67) ([alexwhitman](https://github.com/alexwhitman)) 379 | - Include message object with emits [\#66](https://github.com/martynsmith/node-irc/pull/66) ([alexwhitman](https://github.com/alexwhitman)) 380 | - add some simple CTCP support [\#58](https://github.com/martynsmith/node-irc/pull/58) ([thejh](https://github.com/thejh)) 381 | - Updating the certExpired option [\#53](https://github.com/martynsmith/node-irc/pull/53) ([jonrohan](https://github.com/jonrohan)) 382 | 383 | ## [v0.3.3](https://github.com/martynsmith/node-irc/tree/v0.3.3) (2011-11-16) 384 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.3.2...v0.3.3) 385 | 386 | **Closed issues:** 387 | 388 | - Race condition, mode+- before mode= seems to cause crash [\#65](https://github.com/martynsmith/node-irc/issues/65) 389 | - SSL Failed to connect [\#60](https://github.com/martynsmith/node-irc/issues/60) 390 | - "kill" emits no event [\#59](https://github.com/martynsmith/node-irc/issues/59) 391 | - NAMES command crashes client on InspIRCd-2.0 servers [\#55](https://github.com/martynsmith/node-irc/issues/55) 392 | - Traceback after joining network in 0.3.1 [\#50](https://github.com/martynsmith/node-irc/issues/50) 393 | - Handle erroneous commands gracefully [\#48](https://github.com/martynsmith/node-irc/issues/48) 394 | - Automatic NickServ /IDENTIFYcation? [\#47](https://github.com/martynsmith/node-irc/issues/47) 395 | 396 | **Merged pull requests:** 397 | 398 | - Handle errors in rpl\_namreply. [\#62](https://github.com/martynsmith/node-irc/pull/62) ([schwuk](https://github.com/schwuk)) 399 | - Handle unverifiable self-signed certificates. [\#61](https://github.com/martynsmith/node-irc/pull/61) ([schwuk](https://github.com/schwuk)) 400 | - Password support [\#51](https://github.com/martynsmith/node-irc/pull/51) ([wraithan](https://github.com/wraithan)) 401 | 402 | ## [v0.3.2](https://github.com/martynsmith/node-irc/tree/v0.3.2) (2011-10-30) 403 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.3.1...v0.3.2) 404 | 405 | ## [v0.3.1](https://github.com/martynsmith/node-irc/tree/v0.3.1) (2011-10-29) 406 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.3.0...v0.3.1) 407 | 408 | ## [v0.3.0](https://github.com/martynsmith/node-irc/tree/v0.3.0) (2011-10-28) 409 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.2.1...v0.3.0) 410 | 411 | **Closed issues:** 412 | 413 | - Add command for listing channels [\#42](https://github.com/martynsmith/node-irc/issues/42) 414 | - Parse /version instead of hardcoded symbols in MODE [\#40](https://github.com/martynsmith/node-irc/issues/40) 415 | - Channel letter-case crashes client [\#39](https://github.com/martynsmith/node-irc/issues/39) 416 | - Cannot read property 'users' of undefined [\#38](https://github.com/martynsmith/node-irc/issues/38) 417 | 418 | **Merged pull requests:** 419 | 420 | - Update package.json [\#45](https://github.com/martynsmith/node-irc/pull/45) ([chilts](https://github.com/chilts)) 421 | - Added optional callbacks to connect and disconnect [\#44](https://github.com/martynsmith/node-irc/pull/44) ([fent](https://github.com/fent)) 422 | - stripColors option [\#43](https://github.com/martynsmith/node-irc/pull/43) ([Excedrin](https://github.com/Excedrin)) 423 | - Fixed missing nick in TOPIC socketio event [\#41](https://github.com/martynsmith/node-irc/pull/41) ([alexmingoia](https://github.com/alexmingoia)) 424 | - Document internal functions and variables, activateFloodProtection [\#37](https://github.com/martynsmith/node-irc/pull/37) ([Hello71](https://github.com/Hello71)) 425 | - first pass at sphinx docs [\#35](https://github.com/martynsmith/node-irc/pull/35) ([wraithan](https://github.com/wraithan)) 426 | - adding colors [\#33](https://github.com/martynsmith/node-irc/pull/33) ([wraithan](https://github.com/wraithan)) 427 | - split out irc codes from client code. [\#31](https://github.com/martynsmith/node-irc/pull/31) ([wraithan](https://github.com/wraithan)) 428 | 429 | ## [v0.2.1](https://github.com/martynsmith/node-irc/tree/v0.2.1) (2011-10-01) 430 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.2.0...v0.2.1) 431 | 432 | **Closed issues:** 433 | 434 | - \[Path [\#22](https://github.com/martynsmith/node-irc/issues/22) 435 | - Should sending messages also emit a 'message' signal? [\#17](https://github.com/martynsmith/node-irc/issues/17) 436 | - provide a way to access the current nick [\#12](https://github.com/martynsmith/node-irc/issues/12) 437 | 438 | **Merged pull requests:** 439 | 440 | - Self signed SSL certificates [\#27](https://github.com/martynsmith/node-irc/pull/27) ([stigi](https://github.com/stigi)) 441 | - Adds 'selfMessage' event [\#25](https://github.com/martynsmith/node-irc/pull/25) ([AvianFlu](https://github.com/AvianFlu)) 442 | - Added support for flood protection [\#23](https://github.com/martynsmith/node-irc/pull/23) ([epeli](https://github.com/epeli)) 443 | - Fixed bug when sending empty strings or several lines to say [\#21](https://github.com/martynsmith/node-irc/pull/21) ([eirikb](https://github.com/eirikb)) 444 | - append notice method to Client.prototype. [\#20](https://github.com/martynsmith/node-irc/pull/20) ([futoase](https://github.com/futoase)) 445 | - Parsing out ~ & and % in the channel user list [\#15](https://github.com/martynsmith/node-irc/pull/15) ([pusherman](https://github.com/pusherman)) 446 | - Reconnect all rooms upon server reconnect. [\#14](https://github.com/martynsmith/node-irc/pull/14) ([lloyd](https://github.com/lloyd)) 447 | - listen for the socket 'close' event rather than 'end'. 'end' is triggere [\#13](https://github.com/martynsmith/node-irc/pull/13) ([lloyd](https://github.com/lloyd)) 448 | - Bug fix in join/part/kick events [\#11](https://github.com/martynsmith/node-irc/pull/11) ([luscoma](https://github.com/luscoma)) 449 | 450 | ## [v0.2.0](https://github.com/martynsmith/node-irc/tree/v0.2.0) (2011-04-29) 451 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.1.2...v0.2.0) 452 | 453 | **Merged pull requests:** 454 | 455 | - Add an event for `/invite` [\#10](https://github.com/martynsmith/node-irc/pull/10) ([jsocol](https://github.com/jsocol)) 456 | - Documented the Client.Disconnect method [\#9](https://github.com/martynsmith/node-irc/pull/9) ([mdwrigh2](https://github.com/mdwrigh2)) 457 | - Updated ssl support to work with tsl [\#8](https://github.com/martynsmith/node-irc/pull/8) ([indiefan](https://github.com/indiefan)) 458 | - QUIT and NICK events [\#7](https://github.com/martynsmith/node-irc/pull/7) ([Mortal](https://github.com/Mortal)) 459 | - autoConnect Client option [\#6](https://github.com/martynsmith/node-irc/pull/6) ([Oshuma](https://github.com/Oshuma)) 460 | - added a "notice" event [\#5](https://github.com/martynsmith/node-irc/pull/5) ([thejh](https://github.com/thejh)) 461 | - Properly handle changing user mode [\#4](https://github.com/martynsmith/node-irc/pull/4) ([justinabrahms](https://github.com/justinabrahms)) 462 | - fix unwritable stream error after disconnected [\#3](https://github.com/martynsmith/node-irc/pull/3) ([sublee](https://github.com/sublee)) 463 | 464 | ## [v0.1.2](https://github.com/martynsmith/node-irc/tree/v0.1.2) (2010-05-19) 465 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.1.1...v0.1.2) 466 | 467 | ## [v0.1.1](https://github.com/martynsmith/node-irc/tree/v0.1.1) (2010-05-15) 468 | [Full Changelog](https://github.com/martynsmith/node-irc/compare/v0.1.0...v0.1.1) 469 | 470 | ## [v0.1.0](https://github.com/martynsmith/node-irc/tree/v0.1.0) (2010-05-14) 471 | 472 | 473 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /lib/irc.js: -------------------------------------------------------------------------------- 1 | /* 2 | irc.js - Node JS IRC client library 3 | 4 | (C) Copyright Martyn Smith 2010 5 | 6 | This library is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this library. If not, see . 18 | */ 19 | 20 | exports.Client = Client; 21 | var net = require('net'); 22 | var tls = require('tls'); 23 | var util = require('util'); 24 | var EventEmitter = require('events').EventEmitter; 25 | 26 | var colors = require('./colors'); 27 | var parseMessage = require('./parse_message'); 28 | exports.colors = colors; 29 | var CyclingPingTimer = require('./cycling_ping_timer.js'); 30 | 31 | var lineDelimiter = new RegExp('\r\n|\r|\n') 32 | 33 | function Client(server, nick, opt) { 34 | var self = this; 35 | self.opt = { 36 | server: server, 37 | nick: nick, 38 | password: null, 39 | userName: 'nodebot', 40 | realName: 'nodeJS IRC client', 41 | port: 6667, 42 | localAddress: null, 43 | debug: false, 44 | showErrors: false, 45 | autoRejoin: false, 46 | autoConnect: true, 47 | channels: [], 48 | retryCount: null, 49 | retryDelay: 2000, 50 | secure: false, 51 | selfSigned: false, 52 | certExpired: false, 53 | floodProtection: false, 54 | floodProtectionDelay: 1000, 55 | sasl: false, 56 | stripColors: false, 57 | channelPrefixes: '&#', 58 | messageSplit: 512, 59 | encoding: false, 60 | webirc: { 61 | pass: '', 62 | ip: '', 63 | host: '' 64 | }, 65 | millisecondsOfSilenceBeforePingSent: 15 * 1000, 66 | millisecondsBeforePingTimeout: 8 * 1000 67 | }; 68 | 69 | // Features supported by the server 70 | // (initial values are RFC 1459 defaults. Zeros signify 71 | // no default or unlimited value) 72 | self.supported = { 73 | channel: { 74 | idlength: [], 75 | length: 200, 76 | limit: [], 77 | modes: { a: '', b: '', c: '', d: ''}, 78 | types: self.opt.channelPrefixes 79 | }, 80 | kicklength: 0, 81 | maxlist: [], 82 | maxtargets: [], 83 | modes: 3, 84 | nicklength: 9, 85 | topiclength: 0, 86 | usermodes: '' 87 | }; 88 | 89 | if (typeof arguments[2] == 'object') { 90 | var keys = Object.keys(self.opt); 91 | for (var i = 0; i < keys.length; i++) { 92 | var k = keys[i]; 93 | if (arguments[2][k] !== undefined) 94 | self.opt[k] = arguments[2][k]; 95 | } 96 | } 97 | 98 | if (self.opt.floodProtection) { 99 | self.activateFloodProtection(); 100 | } 101 | 102 | self.hostMask = ''; 103 | 104 | // TODO - fail if nick or server missing 105 | // TODO - fail if username has a space in it 106 | if (self.opt.autoConnect === true) { 107 | self.connect(); 108 | } 109 | 110 | self.addListener('raw', function(message) { 111 | var channels = [], 112 | channel, 113 | nick, 114 | from, 115 | text, 116 | to; 117 | 118 | switch (message.command) { 119 | case 'rpl_welcome': 120 | // Set nick to whatever the server decided it really is 121 | // (normally this is because you chose something too long and 122 | // the server has shortened it 123 | self.nick = message.args[0]; 124 | // Note our hostmask to use it in splitting long messages. 125 | // We don't send our hostmask when issuing PRIVMSGs or NOTICEs, 126 | // of course, but rather the servers on the other side will 127 | // include it in messages and will truncate what we send if 128 | // the string is too long. Therefore, we need to be considerate 129 | // neighbors and truncate our messages accordingly. 130 | var welcomeStringWords = message.args[1].split(/\s+/); 131 | self.hostMask = welcomeStringWords[welcomeStringWords.length - 1]; 132 | self._updateMaxLineLength(); 133 | self.emit('registered', message); 134 | self.whois(self.nick, function(args) { 135 | self.nick = args.nick; 136 | self.hostMask = args.user + '@' + args.host; 137 | self._updateMaxLineLength(); 138 | }); 139 | break; 140 | case 'rpl_myinfo': 141 | self.supported.usermodes = message.args[3]; 142 | break; 143 | case 'rpl_isupport': 144 | message.args.forEach(function(arg) { 145 | var match; 146 | match = arg.match(/([A-Z]+)=(.*)/); 147 | if (match) { 148 | var param = match[1]; 149 | var value = match[2]; 150 | switch (param) { 151 | case 'CHANLIMIT': 152 | value.split(',').forEach(function(val) { 153 | val = val.split(':'); 154 | self.supported.channel.limit[val[0]] = parseInt(val[1]); 155 | }); 156 | break; 157 | case 'CHANMODES': 158 | value = value.split(','); 159 | var type = ['a', 'b', 'c', 'd']; 160 | for (var i = 0; i < type.length; i++) { 161 | self.supported.channel.modes[type[i]] += value[i]; 162 | } 163 | break; 164 | case 'CHANTYPES': 165 | self.supported.channel.types = value; 166 | break; 167 | case 'CHANNELLEN': 168 | self.supported.channel.length = parseInt(value); 169 | break; 170 | case 'IDCHAN': 171 | value.split(',').forEach(function(val) { 172 | val = val.split(':'); 173 | self.supported.channel.idlength[val[0]] = val[1]; 174 | }); 175 | break; 176 | case 'KICKLEN': 177 | self.supported.kicklength = value; 178 | break; 179 | case 'MAXLIST': 180 | value.split(',').forEach(function(val) { 181 | val = val.split(':'); 182 | self.supported.maxlist[val[0]] = parseInt(val[1]); 183 | }); 184 | break; 185 | case 'NICKLEN': 186 | self.supported.nicklength = parseInt(value); 187 | break; 188 | case 'PREFIX': 189 | match = value.match(/\((.*?)\)(.*)/); 190 | if (match) { 191 | match[1] = match[1].split(''); 192 | match[2] = match[2].split(''); 193 | while (match[1].length) { 194 | self.modeForPrefix[match[2][0]] = match[1][0]; 195 | self.supported.channel.modes.b += match[1][0]; 196 | self.prefixForMode[match[1].shift()] = match[2].shift(); 197 | } 198 | } 199 | break; 200 | case 'STATUSMSG': 201 | break; 202 | case 'TARGMAX': 203 | value.split(',').forEach(function(val) { 204 | val = val.split(':'); 205 | val[1] = (!val[1]) ? 0 : parseInt(val[1]); 206 | self.supported.maxtargets[val[0]] = val[1]; 207 | }); 208 | break; 209 | case 'TOPICLEN': 210 | self.supported.topiclength = parseInt(value); 211 | break; 212 | } 213 | } 214 | }); 215 | break; 216 | case 'rpl_yourhost': 217 | case 'rpl_created': 218 | case 'rpl_luserclient': 219 | case 'rpl_luserop': 220 | case 'rpl_luserchannels': 221 | case 'rpl_luserme': 222 | case 'rpl_localusers': 223 | case 'rpl_globalusers': 224 | case 'rpl_statsconn': 225 | case 'rpl_luserunknown': 226 | case '396': 227 | case '042': 228 | // Random welcome crap, ignoring 229 | break; 230 | case 'err_nicknameinuse': 231 | if (typeof (self.opt.nickMod) == 'undefined') 232 | self.opt.nickMod = 0; 233 | self.opt.nickMod++; 234 | self.send('NICK', self.opt.nick + self.opt.nickMod); 235 | self.nick = self.opt.nick + self.opt.nickMod; 236 | self._updateMaxLineLength(); 237 | break; 238 | case 'PING': 239 | self.send('PONG', message.args[0]); 240 | self.emit('ping', message.args[0]); 241 | break; 242 | case 'PONG': 243 | self.emit('pong', message.args[0]); 244 | break; 245 | case 'NOTICE': 246 | from = message.nick; 247 | to = message.args[0]; 248 | if (!to) { 249 | to = null; 250 | } 251 | text = message.args[1] || ''; 252 | if (text[0] === '\u0001' && text.lastIndexOf('\u0001') > 0) { 253 | self._handleCTCP(from, to, text, 'notice', message); 254 | break; 255 | } 256 | self.emit('notice', from, to, text, message); 257 | 258 | if (self.opt.debug && to == self.nick) 259 | util.log('GOT NOTICE from ' + (from ? '"' + from + '"' : 'the server') + ': "' + text + '"'); 260 | break; 261 | case 'MODE': 262 | if (self.opt.debug) 263 | util.log('MODE: ' + message.args[0] + ' sets mode: ' + message.args[1]); 264 | 265 | channel = self.chanData(message.args[0]); 266 | if (!channel) break; 267 | var modeList = message.args[1].split(''); 268 | var adding = true; 269 | var modeArgs = message.args.slice(2); 270 | modeList.forEach(function(mode) { 271 | if (mode == '+') { 272 | adding = true; 273 | return; 274 | } 275 | if (mode == '-') { 276 | adding = false; 277 | return; 278 | } 279 | 280 | var eventName = (adding ? '+' : '-') + 'mode'; 281 | var supported = self.supported.channel.modes; 282 | var modeArg; 283 | var chanModes = function(mode, param) { 284 | var arr = param && Array.isArray(param); 285 | if (adding) { 286 | if (channel.mode.indexOf(mode) == -1) { 287 | channel.mode += mode; 288 | } 289 | if (param === undefined) { 290 | channel.modeParams[mode] = []; 291 | } else if (arr) { 292 | channel.modeParams[mode] = channel.modeParams[mode] ? 293 | channel.modeParams[mode].concat(param) : param; 294 | } else { 295 | channel.modeParams[mode] = [param]; 296 | } 297 | } else { 298 | if (arr) { 299 | channel.modeParams[mode] = channel.modeParams[mode] 300 | .filter(function(v) { return v !== param[0]; }); 301 | } 302 | if (!arr || channel.modeParams[mode].length === 0) { 303 | channel.mode = channel.mode.replace(mode, ''); 304 | delete channel.modeParams[mode]; 305 | } 306 | } 307 | }; 308 | if (mode in self.prefixForMode) { 309 | modeArg = modeArgs.shift(); 310 | if (channel.users.hasOwnProperty(modeArg)) { 311 | if (adding) { 312 | if (channel.users[modeArg].indexOf(self.prefixForMode[mode]) === -1) 313 | channel.users[modeArg] += self.prefixForMode[mode]; 314 | } else channel.users[modeArg] = channel.users[modeArg].replace(self.prefixForMode[mode], ''); 315 | } 316 | self.emit(eventName, message.args[0], message.nick, mode, modeArg, message); 317 | } else if (supported.a.indexOf(mode) !== -1) { 318 | modeArg = modeArgs.shift(); 319 | chanModes(mode, [modeArg]); 320 | self.emit(eventName, message.args[0], message.nick, mode, modeArg, message); 321 | } else if (supported.b.indexOf(mode) !== -1) { 322 | modeArg = modeArgs.shift(); 323 | chanModes(mode, modeArg); 324 | self.emit(eventName, message.args[0], message.nick, mode, modeArg, message); 325 | } else if (supported.c.indexOf(mode) !== -1) { 326 | if (adding) modeArg = modeArgs.shift(); 327 | else modeArg = undefined; 328 | chanModes(mode, modeArg); 329 | self.emit(eventName, message.args[0], message.nick, mode, modeArg, message); 330 | } else if (supported.d.indexOf(mode) !== -1) { 331 | chanModes(mode); 332 | self.emit(eventName, message.args[0], message.nick, mode, undefined, message); 333 | } 334 | }); 335 | break; 336 | case 'NICK': 337 | if (message.nick == self.nick) { 338 | // the user just changed their own nick 339 | self.nick = message.args[0]; 340 | self._updateMaxLineLength(); 341 | } 342 | 343 | if (self.opt.debug) 344 | util.log('NICK: ' + message.nick + ' changes nick to ' + message.args[0]); 345 | 346 | channels = []; 347 | 348 | // TODO better way of finding what channels a user is in? 349 | Object.keys(self.chans).forEach(function(channame) { 350 | var channel = self.chans[channame]; 351 | channel.users[message.args[0]] = channel.users[message.nick]; 352 | delete channel.users[message.nick]; 353 | channels.push(channame); 354 | }); 355 | 356 | // old nick, new nick, channels 357 | self.emit('nick', message.nick, message.args[0], channels, message); 358 | break; 359 | case 'rpl_motdstart': 360 | self.motd = message.args[1] + '\n'; 361 | break; 362 | case 'rpl_motd': 363 | self.motd += message.args[1] + '\n'; 364 | break; 365 | case 'rpl_endofmotd': 366 | case 'err_nomotd': 367 | self.motd += message.args[1] + '\n'; 368 | self.emit('motd', self.motd); 369 | break; 370 | case 'rpl_namreply': 371 | channel = self.chanData(message.args[2]); 372 | var users = message.args[3].trim().split(/ +/); 373 | if (channel) { 374 | users.forEach(function(user) { 375 | var match = user.match(/^(.)(.*)$/); 376 | if (match) { 377 | if (match[1] in self.modeForPrefix) { 378 | channel.users[match[2]] = match[1]; 379 | } 380 | else { 381 | channel.users[match[1] + match[2]] = ''; 382 | } 383 | } 384 | }); 385 | } 386 | break; 387 | case 'rpl_endofnames': 388 | channel = self.chanData(message.args[1]); 389 | if (channel) { 390 | self.emit('names', message.args[1], channel.users); 391 | self.emit('names' + message.args[1], channel.users); 392 | self.send('MODE', message.args[1]); 393 | } 394 | break; 395 | case 'rpl_topic': 396 | channel = self.chanData(message.args[1]); 397 | if (channel) { 398 | channel.topic = message.args[2]; 399 | } 400 | break; 401 | case 'rpl_away': 402 | self._addWhoisData(message.args[1], 'away', message.args[2], true); 403 | break; 404 | case 'rpl_whoisuser': 405 | self._addWhoisData(message.args[1], 'user', message.args[2]); 406 | self._addWhoisData(message.args[1], 'host', message.args[3]); 407 | self._addWhoisData(message.args[1], 'realname', message.args[5]); 408 | break; 409 | case 'rpl_whoisidle': 410 | self._addWhoisData(message.args[1], 'idle', message.args[2]); 411 | break; 412 | case 'rpl_whoischannels': 413 | // TODO - clean this up? 414 | self._addWhoisData(message.args[1], 'channels', message.args[2].trim().split(/\s+/)); 415 | break; 416 | case 'rpl_whoisserver': 417 | self._addWhoisData(message.args[1], 'server', message.args[2]); 418 | self._addWhoisData(message.args[1], 'serverinfo', message.args[3]); 419 | break; 420 | case 'rpl_whoisoperator': 421 | self._addWhoisData(message.args[1], 'operator', message.args[2]); 422 | break; 423 | case '330': // rpl_whoisaccount? 424 | self._addWhoisData(message.args[1], 'account', message.args[2]); 425 | self._addWhoisData(message.args[1], 'accountinfo', message.args[3]); 426 | break; 427 | case 'rpl_endofwhois': 428 | self.emit('whois', self._clearWhoisData(message.args[1])); 429 | break; 430 | case 'rpl_whoreply': 431 | self._addWhoisData(message.args[5], 'user', message.args[2]); 432 | self._addWhoisData(message.args[5], 'host', message.args[3]); 433 | self._addWhoisData(message.args[5], 'server', message.args[4]); 434 | self._addWhoisData(message.args[5], 'realname', /[0-9]+\s*(.+)/g.exec(message.args[7])[1]); 435 | // emit right away because rpl_endofwho doesn't contain nick 436 | self.emit('whois', self._clearWhoisData(message.args[5])); 437 | break; 438 | case 'rpl_liststart': 439 | self.channellist = []; 440 | self.emit('channellist_start'); 441 | break; 442 | case 'rpl_list': 443 | channel = { 444 | name: message.args[1], 445 | users: message.args[2], 446 | topic: message.args[3] 447 | }; 448 | self.emit('channellist_item', channel); 449 | self.channellist.push(channel); 450 | break; 451 | case 'rpl_listend': 452 | self.emit('channellist', self.channellist); 453 | break; 454 | case 'rpl_topicwhotime': 455 | channel = self.chanData(message.args[1]); 456 | if (channel) { 457 | channel.topicBy = message.args[2]; 458 | // channel, topic, nick 459 | self.emit('topic', message.args[1], channel.topic, channel.topicBy, message); 460 | } 461 | break; 462 | case 'TOPIC': 463 | // channel, topic, nick 464 | self.emit('topic', message.args[0], message.args[1], message.nick, message); 465 | 466 | channel = self.chanData(message.args[0]); 467 | if (channel) { 468 | channel.topic = message.args[1]; 469 | channel.topicBy = message.nick; 470 | } 471 | break; 472 | case 'rpl_channelmodeis': 473 | channel = self.chanData(message.args[1]); 474 | if (channel) { 475 | channel.mode = message.args[2]; 476 | } 477 | break; 478 | case 'rpl_creationtime': 479 | channel = self.chanData(message.args[1]); 480 | if (channel) { 481 | channel.created = message.args[2]; 482 | } 483 | break; 484 | case 'JOIN': 485 | // channel, who 486 | if (self.nick == message.nick) { 487 | self.chanData(message.args[0], true); 488 | } 489 | else { 490 | channel = self.chanData(message.args[0]); 491 | if (channel && channel.users) { 492 | channel.users[message.nick] = ''; 493 | } 494 | } 495 | self.emit('join', message.args[0], message.nick, message); 496 | self.emit('join' + message.args[0], message.nick, message); 497 | if (message.args[0] != message.args[0].toLowerCase()) { 498 | self.emit('join' + message.args[0].toLowerCase(), message.nick, message); 499 | } 500 | break; 501 | case 'PART': 502 | // channel, who, reason 503 | self.emit('part', message.args[0], message.nick, message.args[1], message); 504 | self.emit('part' + message.args[0], message.nick, message.args[1], message); 505 | if (message.args[0] != message.args[0].toLowerCase()) { 506 | self.emit('part' + message.args[0].toLowerCase(), message.nick, message.args[1], message); 507 | } 508 | if (self.nick == message.nick) { 509 | channel = self.chanData(message.args[0]); 510 | delete self.chans[channel.key]; 511 | } 512 | else { 513 | channel = self.chanData(message.args[0]); 514 | if (channel && channel.users) { 515 | delete channel.users[message.nick]; 516 | } 517 | } 518 | break; 519 | case 'KICK': 520 | // channel, who, by, reason 521 | self.emit('kick', message.args[0], message.args[1], message.nick, message.args[2], message); 522 | self.emit('kick' + message.args[0], message.args[1], message.nick, message.args[2], message); 523 | if (message.args[0] != message.args[0].toLowerCase()) { 524 | self.emit('kick' + message.args[0].toLowerCase(), 525 | message.args[1], message.nick, message.args[2], message); 526 | } 527 | 528 | if (self.nick == message.args[1]) { 529 | channel = self.chanData(message.args[0]); 530 | delete self.chans[channel.key]; 531 | } 532 | else { 533 | channel = self.chanData(message.args[0]); 534 | if (channel && channel.users) { 535 | delete channel.users[message.args[1]]; 536 | } 537 | } 538 | break; 539 | case 'KILL': 540 | nick = message.args[0]; 541 | channels = []; 542 | Object.keys(self.chans).forEach(function(channame) { 543 | var channel = self.chans[channame]; 544 | channels.push(channame); 545 | delete channel.users[nick]; 546 | }); 547 | self.emit('kill', nick, message.args[1], channels, message); 548 | break; 549 | case 'PRIVMSG': 550 | from = message.nick; 551 | to = message.args[0]; 552 | text = message.args[1] || ''; 553 | if (text[0] === '\u0001' && text.lastIndexOf('\u0001') > 0) { 554 | self._handleCTCP(from, to, text, 'privmsg', message); 555 | break; 556 | } 557 | self.emit('message', from, to, text, message); 558 | if (self.supported.channel.types.indexOf(to.charAt(0)) !== -1) { 559 | self.emit('message#', from, to, text, message); 560 | self.emit('message' + to, from, text, message); 561 | if (to != to.toLowerCase()) { 562 | self.emit('message' + to.toLowerCase(), from, text, message); 563 | } 564 | } 565 | if (to.toUpperCase() === self.nick.toUpperCase()) self.emit('pm', from, text, message); 566 | 567 | if (self.opt.debug && to == self.nick) 568 | util.log('GOT MESSAGE from ' + from + ': ' + text); 569 | break; 570 | case 'INVITE': 571 | from = message.nick; 572 | to = message.args[0]; 573 | channel = message.args[1]; 574 | self.emit('invite', channel, from, message); 575 | break; 576 | case 'QUIT': 577 | if (self.opt.debug) 578 | util.log('QUIT: ' + message.prefix + ' ' + message.args.join(' ')); 579 | if (self.nick == message.nick) { 580 | // TODO handle? 581 | break; 582 | } 583 | // handle other people quitting 584 | 585 | channels = []; 586 | 587 | // TODO better way of finding what channels a user is in? 588 | Object.keys(self.chans).forEach(function(channame) { 589 | var channel = self.chans[channame]; 590 | delete channel.users[message.nick]; 591 | channels.push(channame); 592 | }); 593 | 594 | // who, reason, channels 595 | self.emit('quit', message.nick, message.args[0], channels, message); 596 | break; 597 | 598 | // for sasl 599 | case 'CAP': 600 | if (message.args[0] === '*' && 601 | message.args[1] === 'ACK' && 602 | message.args[2] === 'sasl ') // there's a space after sasl 603 | self.send('AUTHENTICATE', 'PLAIN'); 604 | break; 605 | case 'AUTHENTICATE': 606 | if (message.args[0] === '+') self.send('AUTHENTICATE', 607 | new Buffer( 608 | self.opt.nick + '\0' + 609 | self.opt.userName + '\0' + 610 | self.opt.password 611 | ).toString('base64')); 612 | break; 613 | case '903': 614 | self.send('CAP', 'END'); 615 | break; 616 | 617 | case 'err_umodeunknownflag': 618 | if (self.opt.showErrors) 619 | util.log('\u001b[01;31mERROR: ' + util.inspect(message) + '\u001b[0m'); 620 | break; 621 | 622 | case 'err_erroneusnickname': 623 | if (self.opt.showErrors) 624 | util.log('\u001b[01;31mERROR: ' + util.inspect(message) + '\u001b[0m'); 625 | self.emit('error', message); 626 | break; 627 | 628 | // Commands relating to OPER 629 | case 'err_nooperhost': 630 | if (self.opt.showErrors) { 631 | self.emit('error', message); 632 | if (self.opt.showErrors) 633 | util.log('\u001b[01;31mERROR: ' + util.inspect(message) + '\u001b[0m'); 634 | } 635 | break; 636 | 637 | case 'rpl_youreoper': 638 | self.emit('opered'); 639 | break; 640 | 641 | default: 642 | if (message.commandType == 'error') { 643 | self.emit('error', message); 644 | if (self.opt.showErrors) 645 | util.log('\u001b[01;31mERROR: ' + util.inspect(message) + '\u001b[0m'); 646 | } 647 | else { 648 | if (self.opt.debug) 649 | util.log('\u001b[01;31mUnhandled message: ' + util.inspect(message) + '\u001b[0m'); 650 | break; 651 | } 652 | } 653 | }); 654 | 655 | self.addListener('kick', function(channel, who, by, reason) { 656 | if (self.opt.autoRejoin) 657 | self.send.apply(self, ['JOIN'].concat(channel.split(' '))); 658 | }); 659 | self.addListener('motd', function(motd) { 660 | self.opt.channels.forEach(function(channel) { 661 | self.send.apply(self, ['JOIN'].concat(channel.split(' '))); 662 | }); 663 | }); 664 | 665 | EventEmitter.call(this); 666 | } 667 | util.inherits(Client, EventEmitter); 668 | 669 | Client.prototype.conn = null; 670 | Client.prototype.prefixForMode = {}; 671 | Client.prototype.modeForPrefix = {}; 672 | Client.prototype.chans = {}; 673 | Client.prototype._whoisData = {}; 674 | 675 | Client.prototype.connectionTimedOut = function(conn) { 676 | var self = this; 677 | if (conn !== self.conn) { 678 | // Only care about a timeout event if it came from the connection 679 | // that is most current. 680 | return; 681 | } 682 | self.end(); 683 | }; 684 | 685 | (function() { 686 | var pingCounter = 1; 687 | Client.prototype.connectionWantsPing = function(conn) { 688 | var self = this; 689 | if (conn !== self.conn) { 690 | // Only care about a wantPing event if it came from the connection 691 | // that is most current. 692 | return; 693 | } 694 | self.send('PING', (pingCounter++).toString()); 695 | }; 696 | }()); 697 | 698 | Client.prototype.chanData = function(name, create) { 699 | var key = name.toLowerCase(); 700 | if (create) { 701 | this.chans[key] = this.chans[key] || { 702 | key: key, 703 | serverName: name, 704 | users: {}, 705 | modeParams: {}, 706 | mode: '' 707 | }; 708 | } 709 | 710 | return this.chans[key]; 711 | }; 712 | 713 | Client.prototype._connectionHandler = function() { 714 | if (this.opt.webirc.ip && this.opt.webirc.pass && this.opt.webirc.host) { 715 | this.send('WEBIRC', this.opt.webirc.pass, this.opt.userName, this.opt.webirc.host, this.opt.webirc.ip); 716 | } 717 | if (this.opt.sasl) { 718 | // see http://ircv3.atheme.org/extensions/sasl-3.1 719 | this.send('CAP REQ', 'sasl'); 720 | } else if (this.opt.password) { 721 | this.send('PASS', this.opt.password); 722 | } 723 | if (this.opt.debug) 724 | util.log('Sending irc NICK/USER'); 725 | this.send('NICK', this.opt.nick); 726 | this.nick = this.opt.nick; 727 | this._updateMaxLineLength(); 728 | this.send('USER', this.opt.userName, 8, '*', this.opt.realName); 729 | 730 | this.conn.cyclingPingTimer.start(); 731 | 732 | this.emit('connect'); 733 | }; 734 | 735 | Client.prototype.connect = function(retryCount, callback) { 736 | if (typeof (retryCount) === 'function') { 737 | callback = retryCount; 738 | retryCount = undefined; 739 | } 740 | retryCount = retryCount || 0; 741 | if (typeof (callback) === 'function') { 742 | this.once('registered', callback); 743 | } 744 | var self = this; 745 | self.chans = {}; 746 | 747 | // socket opts 748 | var connectionOpts = { 749 | host: self.opt.server, 750 | port: self.opt.port 751 | }; 752 | 753 | // local address to bind to 754 | if (self.opt.localAddress) 755 | connectionOpts.localAddress = self.opt.localAddress; 756 | 757 | // try to connect to the server 758 | if (self.opt.secure) { 759 | connectionOpts.rejectUnauthorized = !self.opt.selfSigned; 760 | 761 | if (typeof self.opt.secure == 'object') { 762 | // copy "secure" opts to options passed to connect() 763 | for (var f in self.opt.secure) { 764 | connectionOpts[f] = self.opt.secure[f]; 765 | } 766 | } 767 | 768 | self.conn = tls.connect(connectionOpts, function() { 769 | // callback called only after successful socket connection 770 | self.conn.connected = true; 771 | if (self.conn.authorized || 772 | (self.opt.selfSigned && 773 | (self.conn.authorizationError === 'DEPTH_ZERO_SELF_SIGNED_CERT' || 774 | self.conn.authorizationError === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' || 775 | self.conn.authorizationError === 'SELF_SIGNED_CERT_IN_CHAIN')) || 776 | (self.opt.certExpired && 777 | self.conn.authorizationError === 'CERT_HAS_EXPIRED')) { 778 | // authorization successful 779 | 780 | if (!self.opt.encoding) { 781 | self.conn.setEncoding('utf-8'); 782 | } 783 | 784 | if (self.opt.certExpired && 785 | self.conn.authorizationError === 'CERT_HAS_EXPIRED') { 786 | util.log('Connecting to server with expired certificate'); 787 | } 788 | 789 | self._connectionHandler(); 790 | } else { 791 | // authorization failed 792 | util.log(self.conn.authorizationError); 793 | } 794 | }); 795 | } else { 796 | self.conn = net.createConnection(connectionOpts, self._connectionHandler.bind(self)); 797 | } 798 | self.conn.requestedDisconnect = false; 799 | self.conn.setTimeout(0); 800 | 801 | // Each connection gets its own CyclingPingTimer. The connection forwards the timer's 'timeout' and 'wantPing' events 802 | // to the client object via calling the connectionTimedOut() and connectionWantsPing() functions. 803 | // 804 | // Since the client's "current connection" value changes over time because of retry functionality, 805 | // the client should ignore timeout/wantPing events that come from old connections. 806 | self.conn.cyclingPingTimer = new CyclingPingTimer(self); 807 | (function(conn) { 808 | conn.cyclingPingTimer.on('pingTimeout', function() { 809 | self.connectionTimedOut(conn); 810 | }); 811 | conn.cyclingPingTimer.on('wantPing', function() { 812 | self.connectionWantsPing(conn); 813 | }); 814 | }(self.conn)); 815 | 816 | if (!self.opt.encoding) { 817 | self.conn.setEncoding('utf8'); 818 | } 819 | 820 | var buffer = new Buffer(''); 821 | 822 | function handleData(chunk) { 823 | self.conn.cyclingPingTimer.notifyOfActivity(); 824 | 825 | if (typeof (chunk) === 'string') { 826 | buffer += chunk; 827 | } else { 828 | buffer = Buffer.concat([buffer, chunk]); 829 | } 830 | 831 | var lines = self.convertEncoding(buffer).toString().split(lineDelimiter); 832 | 833 | if (lines.pop()) { 834 | // if buffer is not ended with \r\n, there's more chunks. 835 | return; 836 | } else { 837 | // else, initialize the buffer. 838 | buffer = new Buffer(''); 839 | } 840 | 841 | lines.forEach(function iterator(line) { 842 | if (line.length) { 843 | var message = parseMessage(line, self.opt.stripColors); 844 | 845 | try { 846 | self.emit('raw', message); 847 | } catch (err) { 848 | if (!self.conn.requestedDisconnect) { 849 | throw err; 850 | } 851 | } 852 | } 853 | }); 854 | } 855 | 856 | self.conn.addListener('data', handleData); 857 | self.conn.addListener('end', function() { 858 | if (self.opt.debug) 859 | util.log('Connection got "end" event'); 860 | }); 861 | self.conn.addListener('close', function() { 862 | if (self.opt.debug) 863 | util.log('Connection got "close" event'); 864 | 865 | if (self.conn && self.conn.requestedDisconnect) 866 | return; 867 | if (self.opt.debug) 868 | util.log('Disconnected: reconnecting'); 869 | if (self.opt.retryCount !== null && retryCount >= self.opt.retryCount) { 870 | if (self.opt.debug) { 871 | util.log('Maximum retry count (' + self.opt.retryCount + ') reached. Aborting'); 872 | } 873 | self.emit('abort', self.opt.retryCount); 874 | return; 875 | } 876 | 877 | if (self.opt.debug) { 878 | util.log('Waiting ' + self.opt.retryDelay + 'ms before retrying'); 879 | } 880 | setTimeout(function() { 881 | self.connect(retryCount + 1); 882 | }, self.opt.retryDelay); 883 | }); 884 | self.conn.addListener('error', function(exception) { 885 | self.emit('netError', exception); 886 | if (self.opt.debug) { 887 | util.log('Network error: ' + exception); 888 | } 889 | }); 890 | }; 891 | 892 | Client.prototype.end = function() { 893 | if (this.conn) { 894 | this.conn.cyclingPingTimer.stop(); 895 | this.conn.destroy(); 896 | } 897 | this.conn = null; 898 | }; 899 | 900 | Client.prototype.disconnect = function(message, callback) { 901 | if (typeof (message) === 'function') { 902 | callback = message; 903 | message = undefined; 904 | } 905 | message = message || 'node-irc says goodbye'; 906 | var self = this; 907 | if (self.conn.readyState == 'open') { 908 | var sendFunction; 909 | if (self.opt.floodProtection) { 910 | sendFunction = self._sendImmediate; 911 | self._clearCmdQueue(); 912 | } else { 913 | sendFunction = self.send; 914 | } 915 | sendFunction.call(self, 'QUIT', message); 916 | } 917 | self.conn.requestedDisconnect = true; 918 | if (typeof (callback) === 'function') { 919 | self.conn.once('end', callback); 920 | } 921 | self.conn.end(); 922 | }; 923 | 924 | Client.prototype.send = function(command) { 925 | var args = Array.prototype.slice.call(arguments); 926 | 927 | // Note that the command arg is included in the args array as the first element 928 | 929 | if (args[args.length - 1].match(/\s/) || args[args.length - 1].match(/^:/) || args[args.length - 1] === '') { 930 | args[args.length - 1] = ':' + args[args.length - 1]; 931 | } 932 | 933 | if (this.opt.debug) 934 | util.log('SEND: ' + args.join(' ')); 935 | 936 | if (!this.conn.requestedDisconnect) { 937 | this.conn.write(args.join(' ') + '\r\n'); 938 | } 939 | }; 940 | 941 | Client.prototype.activateFloodProtection = function(interval) { 942 | 943 | var cmdQueue = [], 944 | safeInterval = interval || this.opt.floodProtectionDelay, 945 | self = this, 946 | origSend = this.send, 947 | dequeue; 948 | 949 | // Wrapper for the original function. Just put everything to on central 950 | // queue. 951 | this.send = function() { 952 | cmdQueue.push(arguments); 953 | }; 954 | 955 | this._sendImmediate = function() { 956 | origSend.apply(self, arguments); 957 | }; 958 | 959 | this._clearCmdQueue = function() { 960 | cmdQueue = []; 961 | }; 962 | 963 | dequeue = function() { 964 | var args = cmdQueue.shift(); 965 | if (args) { 966 | origSend.apply(self, args); 967 | } 968 | }; 969 | 970 | // Slowly unpack the queue without flooding. 971 | setInterval(dequeue, safeInterval); 972 | dequeue(); 973 | }; 974 | 975 | Client.prototype.join = function(channel, callback) { 976 | var channelName = channel.split(' ')[0]; 977 | this.once('join' + channelName, function() { 978 | // if join is successful, add this channel to opts.channels 979 | // so that it will be re-joined upon reconnect (as channels 980 | // specified in options are) 981 | if (this.opt.channels.indexOf(channel) == -1) { 982 | this.opt.channels.push(channel); 983 | } 984 | 985 | if (typeof (callback) == 'function') { 986 | return callback.apply(this, arguments); 987 | } 988 | }); 989 | this.send.apply(this, ['JOIN'].concat(channel.split(' '))); 990 | }; 991 | 992 | Client.prototype.part = function(channel, message, callback) { 993 | if (typeof (message) === 'function') { 994 | callback = message; 995 | message = undefined; 996 | } 997 | if (typeof (callback) == 'function') { 998 | this.once('part' + channel, callback); 999 | } 1000 | 1001 | // remove this channel from this.opt.channels so we won't rejoin 1002 | // upon reconnect 1003 | if (this.opt.channels.indexOf(channel) != -1) { 1004 | this.opt.channels.splice(this.opt.channels.indexOf(channel), 1); 1005 | } 1006 | 1007 | if (message) { 1008 | this.send('PART', channel, message); 1009 | } else { 1010 | this.send('PART', channel); 1011 | } 1012 | }; 1013 | 1014 | Client.prototype.action = function(channel, text) { 1015 | var self = this; 1016 | if (typeof text !== 'undefined') { 1017 | text.toString().split(/\r?\n/).filter(function(line) { 1018 | return line.length > 0; 1019 | }).forEach(function(line) { 1020 | self.say(channel, '\u0001ACTION ' + line + '\u0001'); 1021 | }); 1022 | } 1023 | }; 1024 | 1025 | Client.prototype._splitLongLines = function(words, maxLength, destination) { 1026 | maxLength = maxLength || 450; // If maxLength hasn't been initialized yet, prefer an arbitrarily low line length over crashing. 1027 | if (words.length == 0) { 1028 | return destination; 1029 | } 1030 | if (words.length <= maxLength) { 1031 | destination.push(words); 1032 | return destination; 1033 | } 1034 | var c = words[maxLength]; 1035 | var cutPos; 1036 | var wsLength = 1; 1037 | if (c.match(/\s/)) { 1038 | cutPos = maxLength; 1039 | } else { 1040 | var offset = 1; 1041 | while ((maxLength - offset) > 0) { 1042 | var c = words[maxLength - offset]; 1043 | if (c.match(/\s/)) { 1044 | cutPos = maxLength - offset; 1045 | break; 1046 | } 1047 | offset++; 1048 | } 1049 | if (maxLength - offset <= 0) { 1050 | cutPos = maxLength; 1051 | wsLength = 0; 1052 | } 1053 | } 1054 | var part = words.substring(0, cutPos); 1055 | destination.push(part); 1056 | return this._splitLongLines(words.substring(cutPos + wsLength, words.length), maxLength, destination); 1057 | }; 1058 | 1059 | Client.prototype.say = function(target, text) { 1060 | this._speak('PRIVMSG', target, text); 1061 | }; 1062 | 1063 | Client.prototype.notice = function(target, text) { 1064 | this._speak('NOTICE', target, text); 1065 | }; 1066 | 1067 | Client.prototype._speak = function(kind, target, text) { 1068 | var self = this; 1069 | var maxLength = Math.min(this.maxLineLength - target.length, this.opt.messageSplit); 1070 | if (typeof text !== 'undefined') { 1071 | text.toString().split(/\r?\n/).filter(function(line) { 1072 | return line.length > 0; 1073 | }).forEach(function(line) { 1074 | var linesToSend = self._splitLongLines(line, maxLength, []); 1075 | linesToSend.forEach(function(toSend) { 1076 | self.send(kind, target, toSend); 1077 | if (kind == 'PRIVMSG') { 1078 | self.emit('selfMessage', target, toSend); 1079 | } 1080 | }); 1081 | }); 1082 | } 1083 | }; 1084 | 1085 | Client.prototype.whois = function(nick, callback) { 1086 | if (typeof callback === 'function') { 1087 | var callbackWrapper = function(info) { 1088 | if (info.nick.toLowerCase() == nick.toLowerCase()) { 1089 | this.removeListener('whois', callbackWrapper); 1090 | return callback.apply(this, arguments); 1091 | } 1092 | }; 1093 | this.addListener('whois', callbackWrapper); 1094 | } 1095 | this.send('WHOIS', nick); 1096 | }; 1097 | 1098 | Client.prototype.list = function() { 1099 | var args = Array.prototype.slice.call(arguments, 0); 1100 | args.unshift('LIST'); 1101 | this.send.apply(this, args); 1102 | }; 1103 | 1104 | Client.prototype._addWhoisData = function(nick, key, value, onlyIfExists) { 1105 | if (onlyIfExists && !this._whoisData[nick]) return; 1106 | this._whoisData[nick] = this._whoisData[nick] || {nick: nick}; 1107 | this._whoisData[nick][key] = value; 1108 | }; 1109 | 1110 | Client.prototype._clearWhoisData = function(nick) { 1111 | // Ensure that at least the nick exists before trying to return 1112 | this._addWhoisData(nick, 'nick', nick); 1113 | var data = this._whoisData[nick]; 1114 | delete this._whoisData[nick]; 1115 | return data; 1116 | }; 1117 | 1118 | Client.prototype._handleCTCP = function(from, to, text, type, message) { 1119 | text = text.slice(1); 1120 | text = text.slice(0, text.indexOf('\u0001')); 1121 | var parts = text.split(' '); 1122 | this.emit('ctcp', from, to, text, type, message); 1123 | this.emit('ctcp-' + type, from, to, text, message); 1124 | if (type === 'privmsg' && text === 'VERSION') 1125 | this.emit('ctcp-version', from, to, message); 1126 | if (parts[0] === 'ACTION' && parts.length > 1) 1127 | this.emit('action', from, to, parts.slice(1).join(' '), message); 1128 | if (parts[0] === 'PING' && type === 'privmsg' && parts.length > 1) 1129 | this.ctcp(from, 'notice', text); 1130 | }; 1131 | 1132 | Client.prototype.ctcp = function(to, type, text) { 1133 | return this[type === 'privmsg' ? 'say' : 'notice'](to, '\u0001' + text + '\u0001'); 1134 | }; 1135 | 1136 | Client.prototype.convertEncoding = function(str) { 1137 | var self = this, out = str; 1138 | 1139 | if (self.opt.encoding) { 1140 | try { 1141 | var charsetDetector = require('node-icu-charset-detector'); 1142 | var Iconv = require('iconv').Iconv; 1143 | var charset = charsetDetector.detectCharset(str); 1144 | var converter = new Iconv(charset.toString(), self.opt.encoding); 1145 | 1146 | out = converter.convert(str); 1147 | } catch (err) { 1148 | if (self.opt.debug) { 1149 | util.log('\u001b[01;31mERROR: ' + err + '\u001b[0m'); 1150 | util.inspect({ str: str, charset: charset }); 1151 | } 1152 | } 1153 | } 1154 | 1155 | return out; 1156 | }; 1157 | // blatantly stolen from irssi's splitlong.pl. Thanks, Bjoern Krombholz! 1158 | Client.prototype._updateMaxLineLength = function() { 1159 | // 497 = 510 - (":" + "!" + " PRIVMSG " + " :").length; 1160 | // target is determined in _speak() and subtracted there 1161 | this.maxLineLength = 497 - this.nick.length - this.hostMask.length; 1162 | }; 1163 | --------------------------------------------------------------------------------