Created
April 13, 2016 22:09
-
-
Save Zoddo/44140b621bf7ec3e85ea7f50236484d9 to your computer and use it in GitHub Desktop.
Modified node-irc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.exports = { | |
'001': { | |
name: 'rpl_welcome', | |
type: 'reply' | |
}, | |
'002': { | |
name: 'rpl_yourhost', | |
type: 'reply' | |
}, | |
'003': { | |
name: 'rpl_created', | |
type: 'reply' | |
}, | |
'004': { | |
name: 'rpl_myinfo', | |
type: 'reply' | |
}, | |
'005': { | |
name: 'rpl_isupport', | |
type: 'reply' | |
}, | |
200: { | |
name: 'rpl_tracelink', | |
type: 'reply' | |
}, | |
201: { | |
name: 'rpl_traceconnecting', | |
type: 'reply' | |
}, | |
202: { | |
name: 'rpl_tracehandshake', | |
type: 'reply' | |
}, | |
203: { | |
name: 'rpl_traceunknown', | |
type: 'reply' | |
}, | |
204: { | |
name: 'rpl_traceoperator', | |
type: 'reply' | |
}, | |
205: { | |
name: 'rpl_traceuser', | |
type: 'reply' | |
}, | |
206: { | |
name: 'rpl_traceserver', | |
type: 'reply' | |
}, | |
208: { | |
name: 'rpl_tracenewtype', | |
type: 'reply' | |
}, | |
211: { | |
name: 'rpl_statslinkinfo', | |
type: 'reply' | |
}, | |
212: { | |
name: 'rpl_statscommands', | |
type: 'reply' | |
}, | |
213: { | |
name: 'rpl_statscline', | |
type: 'reply' | |
}, | |
214: { | |
name: 'rpl_statsnline', | |
type: 'reply' | |
}, | |
215: { | |
name: 'rpl_statsiline', | |
type: 'reply' | |
}, | |
216: { | |
name: 'rpl_statskline', | |
type: 'reply' | |
}, | |
218: { | |
name: 'rpl_statsyline', | |
type: 'reply' | |
}, | |
219: { | |
name: 'rpl_endofstats', | |
type: 'reply' | |
}, | |
221: { | |
name: 'rpl_umodeis', | |
type: 'reply' | |
}, | |
241: { | |
name: 'rpl_statslline', | |
type: 'reply' | |
}, | |
242: { | |
name: 'rpl_statsuptime', | |
type: 'reply' | |
}, | |
243: { | |
name: 'rpl_statsoline', | |
type: 'reply' | |
}, | |
244: { | |
name: 'rpl_statshline', | |
type: 'reply' | |
}, | |
250: { | |
name: 'rpl_statsconn', | |
type: 'reply' | |
}, | |
251: { | |
name: 'rpl_luserclient', | |
type: 'reply' | |
}, | |
252: { | |
name: 'rpl_luserop', | |
type: 'reply' | |
}, | |
253: { | |
name: 'rpl_luserunknown', | |
type: 'reply' | |
}, | |
254: { | |
name: 'rpl_luserchannels', | |
type: 'reply' | |
}, | |
255: { | |
name: 'rpl_luserme', | |
type: 'reply' | |
}, | |
256: { | |
name: 'rpl_adminme', | |
type: 'reply' | |
}, | |
257: { | |
name: 'rpl_adminloc1', | |
type: 'reply' | |
}, | |
258: { | |
name: 'rpl_adminloc2', | |
type: 'reply' | |
}, | |
259: { | |
name: 'rpl_adminemail', | |
type: 'reply' | |
}, | |
261: { | |
name: 'rpl_tracelog', | |
type: 'reply' | |
}, | |
265: { | |
name: 'rpl_localusers', | |
type: 'reply' | |
}, | |
266: { | |
name: 'rpl_globalusers', | |
type: 'reply' | |
}, | |
300: { | |
name: 'rpl_none', | |
type: 'reply' | |
}, | |
301: { | |
name: 'rpl_away', | |
type: 'reply' | |
}, | |
302: { | |
name: 'rpl_userhost', | |
type: 'reply' | |
}, | |
303: { | |
name: 'rpl_ison', | |
type: 'reply' | |
}, | |
305: { | |
name: 'rpl_unaway', | |
type: 'reply' | |
}, | |
306: { | |
name: 'rpl_nowaway', | |
type: 'reply' | |
}, | |
311: { | |
name: 'rpl_whoisuser', | |
type: 'reply' | |
}, | |
312: { | |
name: 'rpl_whoisserver', | |
type: 'reply' | |
}, | |
313: { | |
name: 'rpl_whoisoperator', | |
type: 'reply' | |
}, | |
314: { | |
name: 'rpl_whowasuser', | |
type: 'reply' | |
}, | |
315: { | |
name: 'rpl_endofwho', | |
type: 'reply' | |
}, | |
317: { | |
name: 'rpl_whoisidle', | |
type: 'reply' | |
}, | |
318: { | |
name: 'rpl_endofwhois', | |
type: 'reply' | |
}, | |
319: { | |
name: 'rpl_whoischannels', | |
type: 'reply' | |
}, | |
321: { | |
name: 'rpl_liststart', | |
type: 'reply' | |
}, | |
322: { | |
name: 'rpl_list', | |
type: 'reply' | |
}, | |
323: { | |
name: 'rpl_listend', | |
type: 'reply' | |
}, | |
324: { | |
name: 'rpl_channelmodeis', | |
type: 'reply' | |
}, | |
329: { | |
name: 'rpl_creationtime', | |
type: 'reply' | |
}, | |
331: { | |
name: 'rpl_notopic', | |
type: 'reply' | |
}, | |
332: { | |
name: 'rpl_topic', | |
type: 'reply' | |
}, | |
333: { | |
name: 'rpl_topicwhotime', | |
type: 'reply' | |
}, | |
341: { | |
name: 'rpl_inviting', | |
type: 'reply' | |
}, | |
342: { | |
name: 'rpl_summoning', | |
type: 'reply' | |
}, | |
351: { | |
name: 'rpl_version', | |
type: 'reply' | |
}, | |
352: { | |
name: 'rpl_whoreply', | |
type: 'reply' | |
}, | |
353: { | |
name: 'rpl_namreply', | |
type: 'reply' | |
}, | |
354: { | |
name: 'rpl_whospcrpl', | |
type: 'reply' | |
}, | |
364: { | |
name: 'rpl_links', | |
type: 'reply' | |
}, | |
365: { | |
name: 'rpl_endoflinks', | |
type: 'reply' | |
}, | |
366: { | |
name: 'rpl_endofnames', | |
type: 'reply' | |
}, | |
367: { | |
name: 'rpl_banlist', | |
type: 'reply' | |
}, | |
368: { | |
name: 'rpl_endofbanlist', | |
type: 'reply' | |
}, | |
369: { | |
name: 'rpl_endofwhowas', | |
type: 'reply' | |
}, | |
371: { | |
name: 'rpl_info', | |
type: 'reply' | |
}, | |
372: { | |
name: 'rpl_motd', | |
type: 'reply' | |
}, | |
374: { | |
name: 'rpl_endofinfo', | |
type: 'reply' | |
}, | |
375: { | |
name: 'rpl_motdstart', | |
type: 'reply' | |
}, | |
376: { | |
name: 'rpl_endofmotd', | |
type: 'reply' | |
}, | |
381: { | |
name: 'rpl_youreoper', | |
type: 'reply' | |
}, | |
382: { | |
name: 'rpl_rehashing', | |
type: 'reply' | |
}, | |
391: { | |
name: 'rpl_time', | |
type: 'reply' | |
}, | |
392: { | |
name: 'rpl_usersstart', | |
type: 'reply' | |
}, | |
393: { | |
name: 'rpl_users', | |
type: 'reply' | |
}, | |
394: { | |
name: 'rpl_endofusers', | |
type: 'reply' | |
}, | |
395: { | |
name: 'rpl_nousers', | |
type: 'reply' | |
}, | |
401: { | |
name: 'err_nosuchnick', | |
type: 'error' | |
}, | |
402: { | |
name: 'err_nosuchserver', | |
type: 'error' | |
}, | |
403: { | |
name: 'err_nosuchchannel', | |
type: 'error' | |
}, | |
404: { | |
name: 'err_cannotsendtochan', | |
type: 'error' | |
}, | |
405: { | |
name: 'err_toomanychannels', | |
type: 'error' | |
}, | |
406: { | |
name: 'err_wasnosuchnick', | |
type: 'error' | |
}, | |
407: { | |
name: 'err_toomanytargets', | |
type: 'error' | |
}, | |
409: { | |
name: 'err_noorigin', | |
type: 'error' | |
}, | |
411: { | |
name: 'err_norecipient', | |
type: 'error' | |
}, | |
412: { | |
name: 'err_notexttosend', | |
type: 'error' | |
}, | |
413: { | |
name: 'err_notoplevel', | |
type: 'error' | |
}, | |
414: { | |
name: 'err_wildtoplevel', | |
type: 'error' | |
}, | |
421: { | |
name: 'err_unknowncommand', | |
type: 'error' | |
}, | |
422: { | |
name: 'err_nomotd', | |
type: 'error' | |
}, | |
423: { | |
name: 'err_noadmininfo', | |
type: 'error' | |
}, | |
424: { | |
name: 'err_fileerror', | |
type: 'error' | |
}, | |
431: { | |
name: 'err_nonicknamegiven', | |
type: 'error' | |
}, | |
432: { | |
name: 'err_erroneusnickname', | |
type: 'error' | |
}, | |
433: { | |
name: 'err_nicknameinuse', | |
type: 'error' | |
}, | |
436: { | |
name: 'err_nickcollision', | |
type: 'error' | |
}, | |
441: { | |
name: 'err_usernotinchannel', | |
type: 'error' | |
}, | |
442: { | |
name: 'err_notonchannel', | |
type: 'error' | |
}, | |
443: { | |
name: 'err_useronchannel', | |
type: 'error' | |
}, | |
444: { | |
name: 'err_nologin', | |
type: 'error' | |
}, | |
445: { | |
name: 'err_summondisabled', | |
type: 'error' | |
}, | |
446: { | |
name: 'err_usersdisabled', | |
type: 'error' | |
}, | |
451: { | |
name: 'err_notregistered', | |
type: 'error' | |
}, | |
461: { | |
name: 'err_needmoreparams', | |
type: 'error' | |
}, | |
462: { | |
name: 'err_alreadyregistred', | |
type: 'error' | |
}, | |
463: { | |
name: 'err_nopermforhost', | |
type: 'error' | |
}, | |
464: { | |
name: 'err_passwdmismatch', | |
type: 'error' | |
}, | |
465: { | |
name: 'err_yourebannedcreep', | |
type: 'error' | |
}, | |
467: { | |
name: 'err_keyset', | |
type: 'error' | |
}, | |
471: { | |
name: 'err_channelisfull', | |
type: 'error' | |
}, | |
472: { | |
name: 'err_unknownmode', | |
type: 'error' | |
}, | |
473: { | |
name: 'err_inviteonlychan', | |
type: 'error' | |
}, | |
474: { | |
name: 'err_bannedfromchan', | |
type: 'error' | |
}, | |
475: { | |
name: 'err_badchannelkey', | |
type: 'error' | |
}, | |
481: { | |
name: 'err_noprivileges', | |
type: 'error' | |
}, | |
482: { | |
name: 'err_chanoprivsneeded', | |
type: 'error' | |
}, | |
483: { | |
name: 'err_cantkillserver', | |
type: 'error' | |
}, | |
491: { | |
name: 'err_nooperhost', | |
type: 'error' | |
}, | |
501: { | |
name: 'err_umodeunknownflag', | |
type: 'error' | |
}, | |
502: { | |
name: 'err_usersdontmatch', | |
type: 'error' | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
irc.js - Node JS IRC client library | |
(C) Copyright Martyn Smith 2010 | |
This library is free software: you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation, either version 3 of the License, or | |
(at your option) any later version. | |
This library is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License | |
along with this library. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
exports.Client = Client; | |
var net = require('net'); | |
var tls = require('tls'); | |
var util = require('util'); | |
var EventEmitter = require('events').EventEmitter; | |
var colors = require('./colors'); | |
var parseMessage = require('./parse_message'); | |
exports.colors = colors; | |
var CyclingPingTimer = require('./cycling_ping_timer.js'); | |
var lineDelimiter = new RegExp('\r\n|\r|\n') | |
function Client(server, nick, opt) { | |
var self = this; | |
self.opt = { | |
server: server, | |
nick: nick, | |
password: null, | |
userName: 'nodebot', | |
realName: 'nodeJS IRC client', | |
port: 6667, | |
localAddress: null, | |
debug: false, | |
showErrors: false, | |
autoRejoin: false, | |
autoConnect: true, | |
channels: [], | |
retryCount: null, | |
retryDelay: 2000, | |
secure: false, | |
selfSigned: false, | |
certExpired: false, | |
floodProtection: false, | |
floodProtectionDelay: 1000, | |
sasl: false, | |
stripColors: false, | |
channelPrefixes: '&#', | |
messageSplit: 512, | |
encoding: false, | |
webirc: { | |
pass: '', | |
ip: '', | |
host: '' | |
}, | |
millisecondsOfSilenceBeforePingSent: 15 * 1000, | |
millisecondsBeforePingTimeout: 8 * 1000 | |
}; | |
// Features supported by the server | |
// (initial values are RFC 1459 defaults. Zeros signify | |
// no default or unlimited value) | |
self.supported = { | |
accountnotify: false, | |
channel: { | |
idlength: [], | |
length: 200, | |
limit: [], | |
modes: { a: '', b: '', c: '', d: ''}, | |
types: self.opt.channelPrefixes | |
}, | |
extendedjoin: false, | |
kicklength: 0, | |
maxlist: [], | |
maxtargets: [], | |
modes: 3, | |
multiprefix: false, | |
nicklength: 9, | |
sasl: false, | |
topiclength: 0, | |
usermodes: '', | |
whox: false | |
}; | |
if (typeof arguments[2] == 'object') { | |
var keys = Object.keys(self.opt); | |
for (var i = 0; i < keys.length; i++) { | |
var k = keys[i]; | |
if (arguments[2][k] !== undefined) | |
self.opt[k] = arguments[2][k]; | |
} | |
} | |
if (self.opt.floodProtection) { | |
self.activateFloodProtection(); | |
} | |
self.hostMask = ''; | |
// TODO - fail if nick or server missing | |
// TODO - fail if username has a space in it | |
if (self.opt.autoConnect === true) { | |
self.connect(); | |
} | |
self.addListener('raw', function(message) { | |
var channels = [], | |
channel, | |
user, | |
nick, | |
from, | |
text, | |
to; | |
switch (message.command) { | |
case 'rpl_welcome': | |
// Set nick to whatever the server decided it really is | |
// (normally this is because you chose something too long and | |
// the server has shortened it | |
self.nick = message.args[0]; | |
// Note our hostmask to use it in splitting long messages. | |
// We don't send our hostmask when issuing PRIVMSGs or NOTICEs, | |
// of course, but rather the servers on the other side will | |
// include it in messages and will truncate what we send if | |
// the string is too long. Therefore, we need to be considerate | |
// neighbors and truncate our messages accordingly. | |
var welcomeStringWords = message.args[1].split(/\s+/); | |
self.hostMask = welcomeStringWords[welcomeStringWords.length - 1]; | |
self._updateMaxLineLength(); | |
self.userData(self.nick); | |
self.emit('registered', message); | |
self.whois(self.nick, function(args) { | |
self.nick = args.nick; | |
self.hostMask = args.user + '@' + args.host; | |
self._updateMaxLineLength(); | |
}); | |
break; | |
case 'rpl_myinfo': | |
self.supported.usermodes = message.args[3]; | |
break; | |
case 'rpl_isupport': | |
message.args.forEach(function(arg) { | |
var match; | |
match = arg.match(/([A-Z]+)=(.*)/); | |
if (match) { | |
var param = match[1]; | |
var value = match[2]; | |
switch (param) { | |
case 'CHANLIMIT': | |
value.split(',').forEach(function(val) { | |
val = val.split(':'); | |
self.supported.channel.limit[val[0]] = parseInt(val[1]); | |
}); | |
break; | |
case 'CHANMODES': | |
value = value.split(','); | |
var type = ['a', 'b', 'c', 'd']; | |
for (var i = 0; i < type.length; i++) { | |
self.supported.channel.modes[type[i]] += value[i]; | |
} | |
break; | |
case 'CHANTYPES': | |
self.supported.channel.types = value; | |
break; | |
case 'CHANNELLEN': | |
self.supported.channel.length = parseInt(value); | |
break; | |
case 'IDCHAN': | |
value.split(',').forEach(function(val) { | |
val = val.split(':'); | |
self.supported.channel.idlength[val[0]] = val[1]; | |
}); | |
break; | |
case 'KICKLEN': | |
self.supported.kicklength = value; | |
break; | |
case 'MAXLIST': | |
value.split(',').forEach(function(val) { | |
val = val.split(':'); | |
self.supported.maxlist[val[0]] = parseInt(val[1]); | |
}); | |
break; | |
case 'NICKLEN': | |
self.supported.nicklength = parseInt(value); | |
break; | |
case 'PREFIX': | |
match = value.match(/\((.*?)\)(.*)/); | |
if (match) { | |
match[1] = match[1].split(''); | |
match[2] = match[2].split(''); | |
while (match[1].length) { | |
self.modeForPrefix[match[2][0]] = match[1][0]; | |
self.supported.channel.modes.b += match[1][0]; | |
self.prefixForMode[match[1].shift()] = match[2].shift(); | |
} | |
} | |
break; | |
case 'STATUSMSG': | |
break; | |
case 'TARGMAX': | |
value.split(',').forEach(function(val) { | |
val = val.split(':'); | |
val[1] = (!val[1]) ? 0 : parseInt(val[1]); | |
self.supported.maxtargets[val[0]] = val[1]; | |
}); | |
break; | |
case 'TOPICLEN': | |
self.supported.topiclength = parseInt(value); | |
break; | |
} | |
} else { | |
switch (arg) { | |
case 'WHOX': | |
self.supported.whox = true; | |
break; | |
} | |
} | |
}); | |
break; | |
case 'rpl_yourhost': | |
case 'rpl_created': | |
case 'rpl_luserclient': | |
case 'rpl_luserop': | |
case 'rpl_luserchannels': | |
case 'rpl_luserme': | |
case 'rpl_localusers': | |
case 'rpl_globalusers': | |
case 'rpl_statsconn': | |
case 'rpl_luserunknown': | |
case '396': | |
case '042': | |
// Random welcome crap, ignoring | |
break; | |
case 'err_nicknameinuse': | |
if (typeof (self.opt.nickMod) == 'undefined') | |
self.opt.nickMod = 0; | |
self.opt.nickMod++; | |
self.send('NICK', self.opt.nick + self.opt.nickMod); | |
self.userData(self.opt.nick + self.opt.nickMod); | |
delete self.users[self.nick]; | |
self.nick = self.opt.nick + self.opt.nickMod; | |
self._updateMaxLineLength(); | |
break; | |
case 'PING': | |
self.send('PONG', message.args[0]); | |
self.emit('ping', message.args[0]); | |
break; | |
case 'PONG': | |
self.emit('pong', message.args[0]); | |
break; | |
case 'NOTICE': | |
from = message.nick; | |
to = message.args[0]; | |
if (!to) { | |
to = null; | |
} | |
text = message.args[1] || ''; | |
if (text[0] === '\u0001' && text.lastIndexOf('\u0001') > 0) { | |
self._handleCTCP(from, to, text, 'notice', message); | |
break; | |
} | |
self.emit('notice', from, to, text, message); | |
if (self.opt.debug && to == self.nick) | |
util.log('GOT NOTICE from ' + (from ? '"' + from + '"' : 'the server') + ': "' + text + '"'); | |
break; | |
case 'MODE': | |
if (self.opt.debug) | |
util.log('MODE: ' + message.args[0] + ' sets mode: ' + message.args[1]); | |
channel = self.chanData(message.args[0]); | |
if (!channel) break; | |
var modeList = message.args[1].split(''); | |
var adding = true; | |
var modeArgs = message.args.slice(2); | |
modeList.forEach(function(mode) { | |
if (mode == '+') { | |
adding = true; | |
return; | |
} | |
if (mode == '-') { | |
adding = false; | |
return; | |
} | |
var eventName = (adding ? '+' : '-') + 'mode'; | |
var supported = self.supported.channel.modes; | |
var modeArg; | |
var chanModes = function(mode, param) { | |
var arr = param && Array.isArray(param); | |
if (adding) { | |
if (channel.mode.indexOf(mode) == -1) { | |
channel.mode += mode; | |
} | |
if (param === undefined) { | |
channel.modeParams[mode] = []; | |
} else if (arr) { | |
channel.modeParams[mode] = channel.modeParams[mode] ? | |
channel.modeParams[mode].concat(param) : param; | |
} else { | |
channel.modeParams[mode] = [param]; | |
} | |
} else { | |
if (arr) { | |
channel.modeParams[mode] = channel.modeParams[mode] | |
.filter(function(v) { return v !== param[0]; }); | |
} | |
if (!arr || channel.modeParams[mode].length === 0) { | |
channel.mode = channel.mode.replace(mode, ''); | |
delete channel.modeParams[mode]; | |
} | |
} | |
}; | |
if (mode in self.prefixForMode) { | |
modeArg = modeArgs.shift(); | |
if (channel.users.hasOwnProperty(modeArg)) { | |
if (adding) { | |
if (channel.users[modeArg].indexOf(self.prefixForMode[mode]) === -1) | |
channel.users[modeArg] += self.prefixForMode[mode]; | |
} else channel.users[modeArg] = channel.users[modeArg].replace(self.prefixForMode[mode], ''); | |
} | |
self.emit(eventName, message.args[0], message.nick, mode, modeArg, message); | |
} else if (supported.a.indexOf(mode) !== -1) { | |
modeArg = modeArgs.shift(); | |
chanModes(mode, [modeArg]); | |
self.emit(eventName, message.args[0], message.nick, mode, modeArg, message); | |
} else if (supported.b.indexOf(mode) !== -1) { | |
modeArg = modeArgs.shift(); | |
chanModes(mode, modeArg); | |
self.emit(eventName, message.args[0], message.nick, mode, modeArg, message); | |
} else if (supported.c.indexOf(mode) !== -1) { | |
if (adding) modeArg = modeArgs.shift(); | |
else modeArg = undefined; | |
chanModes(mode, modeArg); | |
self.emit(eventName, message.args[0], message.nick, mode, modeArg, message); | |
} else if (supported.d.indexOf(mode) !== -1) { | |
chanModes(mode); | |
self.emit(eventName, message.args[0], message.nick, mode, undefined, message); | |
} | |
}); | |
break; | |
case 'NICK': | |
if (message.nick == self.nick) { | |
// the user just changed their own nick | |
self.nick = message.args[0]; | |
self._updateMaxLineLength(); | |
} | |
if (self.opt.debug) | |
util.log('NICK: ' + message.nick + ' changes nick to ' + message.args[0]); | |
user = self.userData(message.args[0], message); | |
// Special case: do nothing on caps change | |
if (message.nick.toLowerCase() !== message.args[0].toLowerCase()) { | |
user.realname = self.users[message.nick.toLowerCase()].realname; | |
user.channels = self.users[message.nick.toLowerCase()].channels; | |
user.account = self.users[message.nick.toLowerCase()].account; | |
delete self.users[message.nick.toLowerCase()]; | |
} | |
user.channels.forEach(function(channame) { | |
var channel = self.chans[channame]; | |
channel.users[message.args[0]] = channel.users[message.nick]; | |
delete channel.users[message.nick]; | |
}); | |
// old nick, new nick, channels | |
self.emit('nick', message.nick, message.args[0], user.channels, message); | |
break; | |
case 'ACCOUNT': | |
var account = message.args[0]; | |
if (account == '*') { | |
if (self.opt.debug) | |
util.log('ACCOUNT: ' + message.nick + ' is now logged out.'); | |
account = ''; | |
} | |
else { | |
if (self.opt.debug) | |
util.log('ACCOUNT: ' + message.nick + ' is now identified as ' + message.args[0]); | |
} | |
user = self.userData(message.nick, message); | |
user.account = message.args[0]; | |
// nick, account, channels | |
self.emit('account', message.nick, message.args[0], user.channels, message); | |
break; | |
case 'rpl_motdstart': | |
self.motd = message.args[1] + '\n'; | |
break; | |
case 'rpl_motd': | |
self.motd += message.args[1] + '\n'; | |
break; | |
case 'rpl_endofmotd': | |
case 'err_nomotd': | |
self.motd += message.args[1] + '\n'; | |
self.emit('motd', self.motd); | |
break; | |
case 'rpl_namreply': | |
channel = self.chanData(message.args[2]); | |
var users = message.args[3].trim().split(/ +/); | |
if (channel) { | |
users.forEach(function(username) { | |
var match = username.match('^([' + Object.keys(self.modeForPrefix).join('') + ']*)(.*)$'); | |
if (match) { | |
channel.users[match[2]] = match[1]; | |
user = self.userData(match[2]); | |
if (user.channels.indexOf(channel.key) === -1) | |
user.channels.push(channel.key) | |
} | |
}); | |
} | |
break; | |
case 'rpl_endofnames': | |
channel = self.chanData(message.args[1]); | |
if (channel) { | |
self.emit('names', message.args[1], channel.users); | |
self.emit('names' + message.args[1], channel.users); | |
self.send('MODE', message.args[1]); | |
if (self.supported.whox) | |
self.send('WHO', message.args[1], '%tnuhra,27'); | |
} | |
break; | |
case 'rpl_topic': | |
channel = self.chanData(message.args[1]); | |
if (channel) { | |
channel.topic = message.args[2]; | |
} | |
break; | |
case 'rpl_away': | |
self._addWhoisData(message.args[1], 'away', message.args[2], true); | |
break; | |
case 'rpl_whoisuser': | |
self._addWhoisData(message.args[1], 'user', message.args[2]); | |
self._addWhoisData(message.args[1], 'host', message.args[3]); | |
self._addWhoisData(message.args[1], 'realname', message.args[5]); | |
break; | |
case 'rpl_whoisidle': | |
self._addWhoisData(message.args[1], 'idle', message.args[2]); | |
break; | |
case 'rpl_whoischannels': | |
// TODO - clean this up? | |
self._addWhoisData(message.args[1], 'channels', message.args[2].trim().split(/\s+/)); | |
break; | |
case 'rpl_whoisserver': | |
self._addWhoisData(message.args[1], 'server', message.args[2]); | |
self._addWhoisData(message.args[1], 'serverinfo', message.args[3]); | |
break; | |
case 'rpl_whoisoperator': | |
self._addWhoisData(message.args[1], 'operator', message.args[2]); | |
break; | |
case '330': // rpl_whoisaccount? | |
self._addWhoisData(message.args[1], 'account', message.args[2]); | |
self._addWhoisData(message.args[1], 'accountinfo', message.args[3]); | |
break; | |
case 'rpl_endofwhois': | |
self.emit('whois', self._clearWhoisData(message.args[1])); | |
break; | |
case 'rpl_whoreply': | |
self._addWhoisData(message.args[5], 'user', message.args[2]); | |
self._addWhoisData(message.args[5], 'host', message.args[3]); | |
self._addWhoisData(message.args[5], 'server', message.args[4]); | |
self._addWhoisData(message.args[5], 'realname', /[0-9]+\s*(.+)/g.exec(message.args[7])[1]); | |
// emit right away because rpl_endofwho doesn't contain nick | |
self.emit('whois', self._clearWhoisData(message.args[5])); | |
break; | |
case 'rpl_whospcrpl': | |
if (message.args[1] == '27') | |
{ | |
user = self.userData(message.args[4]); | |
user.username = message.args[2]; | |
user.hostname = message.args[3]; | |
user.account = (message.args[5] != '0') ? message.args[5] : ''; | |
user.realname = message.args[6]; | |
} | |
break; | |
case 'rpl_liststart': | |
self.channellist = []; | |
self.emit('channellist_start'); | |
break; | |
case 'rpl_list': | |
channel = { | |
name: message.args[1], | |
users: message.args[2], | |
topic: message.args[3] | |
}; | |
self.emit('channellist_item', channel); | |
self.channellist.push(channel); | |
break; | |
case 'rpl_listend': | |
self.emit('channellist', self.channellist); | |
break; | |
case 'rpl_topicwhotime': | |
channel = self.chanData(message.args[1]); | |
if (channel) { | |
channel.topicBy = message.args[2]; | |
// channel, topic, nick | |
self.emit('topic', message.args[1], channel.topic, channel.topicBy, message); | |
} | |
break; | |
case 'TOPIC': | |
// channel, topic, nick | |
self.emit('topic', message.args[0], message.args[1], message.nick, message); | |
channel = self.chanData(message.args[0]); | |
if (channel) { | |
channel.topic = message.args[1]; | |
channel.topicBy = message.nick; | |
} | |
break; | |
case 'rpl_channelmodeis': | |
channel = self.chanData(message.args[1]); | |
if (channel) { | |
channel.mode = message.args[2]; | |
} | |
break; | |
case 'rpl_creationtime': | |
channel = self.chanData(message.args[1]); | |
if (channel) { | |
channel.created = message.args[2]; | |
} | |
break; | |
case 'JOIN': | |
// channel, who | |
if (self.nick == message.nick) { | |
channel = self.chanData(message.args[0], true); | |
} | |
else { | |
channel = self.chanData(message.args[0]); | |
if (channel && channel.users) { | |
channel.users[message.nick] = ''; | |
} | |
} | |
user = self.userData(message.nick, message); | |
if (user.channels.indexOf(channel.key) === -1) | |
user.channels.push(channel.key); | |
if (self.supported.extendedjoin) { | |
user.account = (message.args[1] != '*') ? message.args[1] : ''; | |
user.realname = message.args[2]; | |
} | |
self.emit('join', message.args[0], message.nick, message); | |
self.emit('join' + message.args[0], message.nick, message); | |
if (message.args[0] != message.args[0].toLowerCase()) { | |
self.emit('join' + message.args[0].toLowerCase(), message.nick, message); | |
} | |
break; | |
case 'PART': | |
// channel, who, reason | |
self.emit('part', message.args[0], message.nick, message.args[1], message); | |
self.emit('part' + message.args[0], message.nick, message.args[1], message); | |
if (message.args[0] != message.args[0].toLowerCase()) { | |
self.emit('part' + message.args[0].toLowerCase(), message.nick, message.args[1], message); | |
} | |
if (self.nick == message.nick) { | |
channel = self.chanData(message.args[0]); | |
Object.keys(channel.users).forEach(function (username) { | |
user = self.userData(username); | |
if (user) { | |
var channel_index = user.channels.indexOf(channel.key); | |
if (channel_index > -1) | |
user.channels.splice(channel_index, 1); | |
if (user.channels.length < 1) | |
delete self.users[user.key]; | |
} | |
}); | |
delete self.chans[channel.key]; | |
} | |
else { | |
channel = self.chanData(message.args[0]); | |
user = self.userData(message.nick, message); | |
if (channel && channel.users) { | |
delete channel.users[message.nick]; | |
} | |
if (user) { | |
var channel_index = user.channels.indexOf(channel.key); | |
if (channel_index > -1) | |
user.channels.splice(channel_index, 1); | |
if (user.channels.length < 1) | |
delete self.users[user.key]; | |
} | |
} | |
break; | |
case 'KICK': | |
// channel, who, by, reason | |
self.emit('kick', message.args[0], message.args[1], message.nick, message.args[2], message); | |
self.emit('kick' + message.args[0], message.args[1], message.nick, message.args[2], message); | |
if (message.args[0] != message.args[0].toLowerCase()) { | |
self.emit('kick' + message.args[0].toLowerCase(), | |
message.args[1], message.nick, message.args[2], message); | |
} | |
if (self.nick == message.args[1]) { | |
channel = self.chanData(message.args[0]); | |
Object.keys(channel.users).forEach(function (username) { | |
user = self.userData(username); | |
if (user) { | |
var channel_index = user.channels.indexOf(channel.key); | |
if (channel_index > -1) | |
user.channels.splice(channel_index, 1); | |
if (user.channels.length < 1) | |
delete self.users[user.key]; | |
} | |
}); | |
delete self.chans[channel.key]; | |
} | |
else { | |
channel = self.chanData(message.args[0]); | |
user = self.userData(message.args[1]); | |
if (channel && channel.users) { | |
delete channel.users[message.args[1]]; | |
} | |
if (user) { | |
var channel_index = user.channels.indexOf(channel.key); | |
if (channel_index > -1) | |
user.channels.splice(channel_index, 1); | |
if (user.channels.length < 1) | |
delete self.users[user.key]; | |
} | |
} | |
break; | |
case 'KILL': | |
nick = message.args[0]; | |
channels = []; | |
user = self.userData(message.args[1]); | |
user.channels.forEach(function(channame) { | |
var channel = self.chans[channame]; | |
channels.push(channame); | |
delete channel.users[nick]; | |
}); | |
delete self.users[user.key]; | |
self.emit('kill', nick, message.args[1], channels, message); | |
break; | |
case 'PRIVMSG': | |
from = message.nick; | |
to = message.args[0]; | |
text = message.args[1] || ''; | |
if (text[0] === '\u0001' && text.lastIndexOf('\u0001') > 0) { | |
self._handleCTCP(from, to, text, 'privmsg', message); | |
break; | |
} | |
self.emit('message', from, to, text, message); | |
if (self.supported.channel.types.indexOf(to.charAt(0)) !== -1) { | |
self.emit('message#', from, to, text, message); | |
self.emit('message' + to, from, text, message); | |
if (to != to.toLowerCase()) { | |
self.emit('message' + to.toLowerCase(), from, text, message); | |
} | |
} | |
if (to.toUpperCase() === self.nick.toUpperCase()) self.emit('pm', from, text, message); | |
if (self.opt.debug && to == self.nick) | |
util.log('GOT MESSAGE from ' + from + ': ' + text); | |
break; | |
case 'INVITE': | |
from = message.nick; | |
to = message.args[0]; | |
channel = message.args[1]; | |
self.emit('invite', channel, from, message); | |
break; | |
case 'QUIT': | |
if (self.opt.debug) | |
util.log('QUIT: ' + message.prefix + ' ' + message.args.join(' ')); | |
if (self.nick == message.nick) { | |
// TODO handle? | |
break; | |
} | |
// handle other people quitting | |
user = self.userData(message.nick); | |
// who, reason, channels | |
self.emit('quit', message.nick, message.args[0], user.channels, message); | |
user.channels.forEach(function(channame) { | |
var channel = self.chans[channame]; | |
delete channel.users[message.nick]; | |
}); | |
delete self.users[user.key]; | |
break; | |
case 'CAP': | |
switch (message.args[1]) | |
{ | |
case 'LS': | |
message.args[2].split(' ').forEach(function (cap) { | |
switch (cap) { | |
case 'account-notify': | |
self.supported.accountnotify = true; | |
self.send('CAP REQ', 'account-notify'); | |
break; | |
case 'extended-join': | |
self.supported.extendedjoin = true; | |
self.send('CAP REQ', 'extended-join'); | |
break; | |
case 'multi-prefix': | |
self.supported.multiprefix = true; | |
self.send('CAP REQ', 'multi-prefix'); | |
break; | |
case 'sasl': | |
self.supported.sasl = true; | |
break; | |
} | |
}); | |
if (self.opt.sasl && self.supported.sasl) { | |
// see http://ircv3.atheme.org/extensions/sasl-3.1 | |
self.send('CAP REQ', 'sasl'); | |
} else { | |
self.send('CAP', 'END'); | |
} | |
break; | |
case 'ACK': | |
if (message.args[2].trim() === 'sasl') // there can be a space after sasl | |
self.send('AUTHENTICATE', 'PLAIN'); | |
break; | |
} | |
break; | |
case 'AUTHENTICATE': | |
if (message.args[0] === '+') self.send('AUTHENTICATE', | |
new Buffer( | |
self.opt.nick + '\0' + | |
self.opt.userName + '\0' + | |
self.opt.password | |
).toString('base64')); | |
break; | |
case '903': | |
self.send('CAP', 'END'); | |
break; | |
case 'err_umodeunknownflag': | |
if (self.opt.showErrors) | |
util.log('\u001b[01;31mERROR: ' + util.inspect(message) + '\u001b[0m'); | |
break; | |
case 'err_erroneusnickname': | |
if (self.opt.showErrors) | |
util.log('\u001b[01;31mERROR: ' + util.inspect(message) + '\u001b[0m'); | |
self.emit('error', message); | |
break; | |
// Commands relating to OPER | |
case 'err_nooperhost': | |
if (self.opt.showErrors) { | |
self.emit('error', message); | |
if (self.opt.showErrors) | |
util.log('\u001b[01;31mERROR: ' + util.inspect(message) + '\u001b[0m'); | |
} | |
break; | |
case 'rpl_youreoper': | |
self.emit('opered'); | |
break; | |
default: | |
if (message.commandType == 'error') { | |
self.emit('error', message); | |
if (self.opt.showErrors) | |
util.log('\u001b[01;31mERROR: ' + util.inspect(message) + '\u001b[0m'); | |
} | |
else { | |
if (self.opt.debug) | |
util.log('\u001b[01;31mUnhandled message: ' + util.inspect(message) + '\u001b[0m'); | |
break; | |
} | |
} | |
}); | |
self.addListener('kick', function(channel, who, by, reason) { | |
if (self.nick == who && self.opt.autoRejoin) | |
self.send.apply(self, ['JOIN'].concat(channel.split(' '))); | |
}); | |
self.addListener('motd', function(motd) { | |
self.opt.channels.forEach(function(channel) { | |
self.send.apply(self, ['JOIN'].concat(channel.split(' '))); | |
}); | |
}); | |
EventEmitter.call(this); | |
} | |
util.inherits(Client, EventEmitter); | |
Client.prototype.conn = null; | |
Client.prototype.prefixForMode = {}; | |
Client.prototype.modeForPrefix = {}; | |
Client.prototype.chans = {}; | |
Client.prototype.users = {}; | |
Client.prototype._whoisData = {}; | |
Client.prototype.connectionTimedOut = function(conn) { | |
var self = this; | |
if (conn !== self.conn) { | |
// Only care about a timeout event if it came from the connection | |
// that is most current. | |
return; | |
} | |
self.end(); | |
}; | |
(function() { | |
var pingCounter = 1; | |
Client.prototype.connectionWantsPing = function(conn) { | |
var self = this; | |
if (conn !== self.conn) { | |
// Only care about a wantPing event if it came from the connection | |
// that is most current. | |
return; | |
} | |
self.send('PING', (pingCounter++).toString()); | |
}; | |
}()); | |
Client.prototype.chanData = function(name, create) { | |
var key = name.toLowerCase(); | |
if (create) { | |
this.chans[key] = this.chans[key] || { | |
key: key, | |
serverName: name, | |
users: {}, | |
modeParams: {}, | |
mode: '' | |
}; | |
} | |
return this.chans[key]; | |
}; | |
Client.prototype.userData = function(name, message) { | |
var key = name.toLowerCase(); | |
this.users[key] = this.users[key] || { | |
key: key, | |
username: '', | |
hostname: '', | |
realname: '', | |
channels: [], | |
account: '' | |
}; | |
if (typeof message !== 'undefined') { | |
this.users[key].username = message.user; | |
this.users[key].hostname = message.host; | |
} | |
return this.users[key]; | |
}; | |
Client.prototype._connectionHandler = function() { | |
if (this.opt.webirc.ip && this.opt.webirc.pass && this.opt.webirc.host) { | |
this.send('WEBIRC', this.opt.webirc.pass, this.opt.userName, this.opt.webirc.host, this.opt.webirc.ip); | |
} | |
this.send('CAP', 'LS'); | |
if (this.opt.password && !this.opt.sasl) { | |
this.send('PASS', this.opt.password); | |
} | |
if (this.opt.debug) | |
util.log('Sending irc NICK/USER'); | |
this.send('NICK', this.opt.nick); | |
this.nick = this.opt.nick; | |
this._updateMaxLineLength(); | |
this.send('USER', this.opt.userName, 8, '*', this.opt.realName); | |
this.conn.cyclingPingTimer.start(); | |
this.emit('connect'); | |
}; | |
Client.prototype.connect = function(retryCount, callback) { | |
if (typeof (retryCount) === 'function') { | |
callback = retryCount; | |
retryCount = undefined; | |
} | |
retryCount = retryCount || 0; | |
if (typeof (callback) === 'function') { | |
this.once('registered', callback); | |
} | |
var self = this; | |
self.chans = {}; | |
self.users = {}; | |
// socket opts | |
var connectionOpts = { | |
host: self.opt.server, | |
port: self.opt.port | |
}; | |
// local address to bind to | |
if (self.opt.localAddress) | |
connectionOpts.localAddress = self.opt.localAddress; | |
// try to connect to the server | |
if (self.opt.secure) { | |
connectionOpts.rejectUnauthorized = !self.opt.selfSigned; | |
if (typeof self.opt.secure == 'object') { | |
// copy "secure" opts to options passed to connect() | |
for (var f in self.opt.secure) { | |
connectionOpts[f] = self.opt.secure[f]; | |
} | |
} | |
self.conn = tls.connect(connectionOpts, function() { | |
// callback called only after successful socket connection | |
self.conn.connected = true; | |
if (self.conn.authorized || | |
(self.opt.selfSigned && | |
(self.conn.authorizationError === 'DEPTH_ZERO_SELF_SIGNED_CERT' || | |
self.conn.authorizationError === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' || | |
self.conn.authorizationError === 'SELF_SIGNED_CERT_IN_CHAIN')) || | |
(self.opt.certExpired && | |
self.conn.authorizationError === 'CERT_HAS_EXPIRED')) { | |
// authorization successful | |
if (!self.opt.encoding) { | |
self.conn.setEncoding('utf-8'); | |
} | |
if (self.opt.certExpired && | |
self.conn.authorizationError === 'CERT_HAS_EXPIRED') { | |
util.log('Connecting to server with expired certificate'); | |
} | |
self._connectionHandler(); | |
} else { | |
// authorization failed | |
util.log(self.conn.authorizationError); | |
} | |
}); | |
} else { | |
self.conn = net.createConnection(connectionOpts, self._connectionHandler.bind(self)); | |
} | |
self.conn.requestedDisconnect = false; | |
self.conn.setTimeout(0); | |
// Each connection gets its own CyclingPingTimer. The connection forwards the timer's 'timeout' and 'wantPing' events | |
// to the client object via calling the connectionTimedOut() and connectionWantsPing() functions. | |
// | |
// Since the client's "current connection" value changes over time because of retry functionality, | |
// the client should ignore timeout/wantPing events that come from old connections. | |
self.conn.cyclingPingTimer = new CyclingPingTimer(self); | |
(function(conn) { | |
conn.cyclingPingTimer.on('pingTimeout', function() { | |
self.connectionTimedOut(conn); | |
}); | |
conn.cyclingPingTimer.on('wantPing', function() { | |
self.connectionWantsPing(conn); | |
}); | |
}(self.conn)); | |
if (!self.opt.encoding) { | |
self.conn.setEncoding('utf8'); | |
} | |
var buffer = new Buffer(''); | |
function handleData(chunk) { | |
self.conn.cyclingPingTimer.notifyOfActivity(); | |
if (typeof (chunk) === 'string') { | |
buffer += chunk; | |
} else { | |
buffer = Buffer.concat([buffer, chunk]); | |
} | |
var lines = self.convertEncoding(buffer).toString().split(lineDelimiter); | |
if (lines.pop()) { | |
// if buffer is not ended with \r\n, there's more chunks. | |
return; | |
} else { | |
// else, initialize the buffer. | |
buffer = new Buffer(''); | |
} | |
lines.forEach(function iterator(line) { | |
if (line.length) { | |
var message = parseMessage(line, self.opt.stripColors); | |
try { | |
self.emit('raw', message); | |
} catch (err) { | |
if (!self.conn.requestedDisconnect) { | |
throw err; | |
} | |
} | |
} | |
}); | |
} | |
self.conn.addListener('data', handleData); | |
self.conn.addListener('end', function() { | |
if (self.opt.debug) | |
util.log('Connection got "end" event'); | |
}); | |
self.conn.addListener('close', function() { | |
if (self.opt.debug) | |
util.log('Connection got "close" event'); | |
if (self.conn && self.conn.requestedDisconnect) | |
return; | |
if (self.opt.debug) | |
util.log('Disconnected: reconnecting'); | |
if (self.opt.retryCount !== null && retryCount >= self.opt.retryCount) { | |
if (self.opt.debug) { | |
util.log('Maximum retry count (' + self.opt.retryCount + ') reached. Aborting'); | |
} | |
self.emit('abort', self.opt.retryCount); | |
return; | |
} | |
if (self.opt.debug) { | |
util.log('Waiting ' + self.opt.retryDelay + 'ms before retrying'); | |
} | |
setTimeout(function() { | |
self.connect(retryCount + 1); | |
}, self.opt.retryDelay); | |
}); | |
self.conn.addListener('error', function(exception) { | |
self.emit('netError', exception); | |
if (self.opt.debug) { | |
util.log('Network error: ' + exception); | |
} | |
}); | |
}; | |
Client.prototype.end = function() { | |
if (this.conn) { | |
this.conn.cyclingPingTimer.stop(); | |
this.conn.destroy(); | |
} | |
this.conn = null; | |
}; | |
Client.prototype.disconnect = function(message, callback) { | |
if (typeof (message) === 'function') { | |
callback = message; | |
message = undefined; | |
} | |
message = message || 'node-irc says goodbye'; | |
var self = this; | |
if (self.conn.readyState == 'open') { | |
var sendFunction; | |
if (self.opt.floodProtection) { | |
sendFunction = self._sendImmediate; | |
self._clearCmdQueue(); | |
} else { | |
sendFunction = self.send; | |
} | |
sendFunction.call(self, 'QUIT', message); | |
} | |
self.conn.requestedDisconnect = true; | |
if (typeof (callback) === 'function') { | |
self.conn.once('end', callback); | |
} | |
self.conn.end(); | |
}; | |
Client.prototype.send = function(command) { | |
var args = Array.prototype.slice.call(arguments); | |
// Note that the command arg is included in the args array as the first element | |
if (args[args.length - 1].match(/\s/) || args[args.length - 1].match(/^:/) || args[args.length - 1] === '') { | |
args[args.length - 1] = ':' + args[args.length - 1]; | |
} | |
if (this.opt.debug) | |
util.log('SEND: ' + args.join(' ')); | |
if (!this.conn.requestedDisconnect) { | |
this.conn.write(args.join(' ') + '\r\n'); | |
} | |
}; | |
Client.prototype.activateFloodProtection = function(interval) { | |
var cmdQueue = [], | |
safeInterval = interval || this.opt.floodProtectionDelay, | |
self = this, | |
origSend = this.send, | |
dequeue; | |
// Wrapper for the original function. Just put everything to on central | |
// queue. | |
this.send = function() { | |
cmdQueue.push(arguments); | |
}; | |
this._sendImmediate = function() { | |
origSend.apply(self, arguments); | |
}; | |
this._clearCmdQueue = function() { | |
cmdQueue = []; | |
}; | |
dequeue = function() { | |
var args = cmdQueue.shift(); | |
if (args) { | |
origSend.apply(self, args); | |
} | |
}; | |
// Slowly unpack the queue without flooding. | |
setInterval(dequeue, safeInterval); | |
dequeue(); | |
}; | |
Client.prototype.join = function(channel, callback) { | |
var channelName = channel.split(' ')[0]; | |
this.once('join' + channelName, function() { | |
// if join is successful, add this channel to opts.channels | |
// so that it will be re-joined upon reconnect (as channels | |
// specified in options are) | |
if (this.opt.channels.indexOf(channel) == -1) { | |
this.opt.channels.push(channel); | |
} | |
if (typeof (callback) == 'function') { | |
return callback.apply(this, arguments); | |
} | |
}); | |
this.send.apply(this, ['JOIN'].concat(channel.split(' '))); | |
}; | |
Client.prototype.part = function(channel, message, callback) { | |
if (typeof (message) === 'function') { | |
callback = message; | |
message = undefined; | |
} | |
if (typeof (callback) == 'function') { | |
this.once('part' + channel, callback); | |
} | |
// remove this channel from this.opt.channels so we won't rejoin | |
// upon reconnect | |
if (this.opt.channels.indexOf(channel) != -1) { | |
this.opt.channels.splice(this.opt.channels.indexOf(channel), 1); | |
} | |
if (message) { | |
this.send('PART', channel, message); | |
} else { | |
this.send('PART', channel); | |
} | |
}; | |
Client.prototype.action = function(channel, text) { | |
var self = this; | |
if (typeof text !== 'undefined') { | |
text.toString().split(/\r?\n/).filter(function(line) { | |
return line.length > 0; | |
}).forEach(function(line) { | |
self.say(channel, '\u0001ACTION ' + line + '\u0001'); | |
}); | |
} | |
}; | |
Client.prototype._splitLongLines = function(words, maxLength, destination) { | |
maxLength = maxLength || 450; // If maxLength hasn't been initialized yet, prefer an arbitrarily low line length over crashing. | |
if (words.length == 0) { | |
return destination; | |
} | |
if (words.length <= maxLength) { | |
destination.push(words); | |
return destination; | |
} | |
var c = words[maxLength]; | |
var cutPos; | |
var wsLength = 1; | |
if (c.match(/\s/)) { | |
cutPos = maxLength; | |
} else { | |
var offset = 1; | |
while ((maxLength - offset) > 0) { | |
var c = words[maxLength - offset]; | |
if (c.match(/\s/)) { | |
cutPos = maxLength - offset; | |
break; | |
} | |
offset++; | |
} | |
if (maxLength - offset <= 0) { | |
cutPos = maxLength; | |
wsLength = 0; | |
} | |
} | |
var part = words.substring(0, cutPos); | |
destination.push(part); | |
return this._splitLongLines(words.substring(cutPos + wsLength, words.length), maxLength, destination); | |
}; | |
Client.prototype.say = function(target, text) { | |
this._speak('PRIVMSG', target, text); | |
}; | |
Client.prototype.notice = function(target, text) { | |
this._speak('NOTICE', target, text); | |
}; | |
Client.prototype._speak = function(kind, target, text) { | |
var self = this; | |
var maxLength = this.maxLineLength - target.length; | |
if (typeof text !== 'undefined') { | |
text.toString().split(/\r?\n/).filter(function(line) { | |
return line.length > 0; | |
}).forEach(function(line) { | |
var linesToSend = self._splitLongLines(line, maxLength, []); | |
linesToSend.forEach(function(toSend) { | |
self.send(kind, target, toSend); | |
if (kind == 'PRIVMSG') { | |
self.emit('selfMessage', target, toSend); | |
} | |
}); | |
}); | |
} | |
}; | |
Client.prototype.whois = function(nick, callback) { | |
if (typeof callback === 'function') { | |
var callbackWrapper = function(info) { | |
if (info.nick.toLowerCase() == nick.toLowerCase()) { | |
this.removeListener('whois', callbackWrapper); | |
return callback.apply(this, arguments); | |
} | |
}; | |
this.addListener('whois', callbackWrapper); | |
} | |
this.send('WHOIS', nick); | |
}; | |
Client.prototype.list = function() { | |
var args = Array.prototype.slice.call(arguments, 0); | |
args.unshift('LIST'); | |
this.send.apply(this, args); | |
}; | |
Client.prototype._addWhoisData = function(nick, key, value, onlyIfExists) { | |
if (onlyIfExists && !this._whoisData[nick]) return; | |
this._whoisData[nick] = this._whoisData[nick] || {nick: nick}; | |
this._whoisData[nick][key] = value; | |
}; | |
Client.prototype._clearWhoisData = function(nick) { | |
// Ensure that at least the nick exists before trying to return | |
this._addWhoisData(nick, 'nick', nick); | |
var data = this._whoisData[nick]; | |
delete this._whoisData[nick]; | |
return data; | |
}; | |
Client.prototype._handleCTCP = function(from, to, text, type, message) { | |
text = text.slice(1); | |
text = text.slice(0, text.indexOf('\u0001')); | |
var parts = text.split(' '); | |
this.emit('ctcp', from, to, text, type, message); | |
this.emit('ctcp-' + type, from, to, text, message); | |
if (type === 'privmsg' && text === 'VERSION') | |
this.emit('ctcp-version', from, to, message); | |
if (parts[0] === 'ACTION' && parts.length > 1) | |
this.emit('action', from, to, parts.slice(1).join(' '), message); | |
if (parts[0] === 'PING' && type === 'privmsg' && parts.length > 1) | |
this.ctcp(from, 'notice', text); | |
}; | |
Client.prototype.ctcp = function(to, type, text) { | |
return this[type === 'privmsg' ? 'say' : 'notice'](to, '\u0001' + text + '\u0001'); | |
}; | |
Client.prototype.convertEncoding = function(str) { | |
var self = this, out = str; | |
if (self.opt.encoding) { | |
try { | |
var charsetDetector = require('node-icu-charset-detector'); | |
var Iconv = require('iconv').Iconv; | |
var charset = charsetDetector.detectCharset(str); | |
var converter = new Iconv(charset.toString(), self.opt.encoding); | |
out = converter.convert(str); | |
} catch (err) { | |
if (self.opt.debug) { | |
util.log('\u001b[01;31mERROR: ' + err + '\u001b[0m'); | |
util.inspect({ str: str, charset: charset }); | |
} | |
} | |
} | |
return out; | |
}; | |
// blatantly stolen from irssi's splitlong.pl. Thanks, Bjoern Krombholz! | |
Client.prototype._updateMaxLineLength = function() { | |
// 497 = 510 - (":" + "!" + " PRIVMSG " + " :").length; | |
// target is determined in _speak() and subtracted there | |
this.maxLineLength = 497 - this.nick.length - this.hostMask.length; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment