Commit | Line | Data |
---|---|---|
3ec786bc JA |
1 | var _ = require('lodash'), |
2 | irc_numerics, | |
3ec786bc JA |
3 | unknownCommand; |
4 | ||
5 | irc_numerics = { | |
6 | '001': 'RPL_WELCOME', | |
f26a988a CC |
7 | '002': 'RPL_YOURHOST', |
8 | '003': 'RPL_CREATED', | |
9 | '004': 'RPL_MYINFO', | |
3ec786bc JA |
10 | '005': 'RPL_ISUPPORT', |
11 | '006': 'RPL_MAPMORE', | |
12 | '007': 'RPL_MAPEND', | |
f26a988a | 13 | '008': 'RPL_SNOMASK', |
94e2cb82 CC |
14 | '015': 'RPL_MAP', |
15 | '017': 'RPL_MAPEND', | |
f26a988a CC |
16 | '200': 'RPL_TRACELINK', |
17 | '201': 'RPL_TRACECONNECTING', | |
18 | '202': 'RPL_TRACEHANDSHAKE', | |
19 | '203': 'RPL_TRACEUNKNOWN', | |
20 | '204': 'RPL_TRACEOPERATOR', | |
21 | '205': 'RPL_TRACEUSER', | |
22 | '206': 'RPL_TRACESERVER', | |
23 | '207': 'RPL_TRACESERVICE', | |
24 | '208': 'RPL_TRACENEWTYPE', | |
25 | '209': 'RPL_TRACECLASS', | |
26 | '210': 'RPL_TRACERECONNECT', | |
27 | '211': 'RPL_STATSLINKINFO', | |
28 | '212': 'RPL_STATSCOMMANDS', | |
29 | '213': 'RPL_STATSCLINE', | |
30 | '214': 'RPL_STATSNLINE', | |
31 | '215': 'RPL_STATSILINE', | |
32 | '216': 'RPL_STATSKLINE', | |
33 | '217': 'RPL_STATSPLINE', | |
34 | '218': 'RPL_STATSYLINE', | |
35 | '219': 'RPL_ENDOFSTATS', | |
36 | '220': 'RPL_STATSBLINE', | |
37 | '221': 'RPL_UMODEIS', | |
38 | '222': 'RPL_SQLINE_NICK', | |
39 | '223': 'RPL_STATS_E', | |
40 | '224': 'RPL_STATS_D', | |
41 | '229': 'RPL_SPAMFILTER', | |
42 | '231': 'RPL_SERVICEINFO', | |
43 | '232': 'RPL_ENDOFSERVICES', | |
44 | '233': 'RPL_SERVICE', | |
45 | '234': 'RPL_SERVLIST', | |
46 | '235': 'RPL_SERVLISTEND', | |
47 | '241': 'RPL_STATSLLINE', | |
48 | '242': 'RPL_STATSUPTIME', | |
49 | '243': 'RPL_STATSOLINE', | |
50 | '244': 'RPL_STATSHLINE', | |
51 | '245': 'RPL_STATSSLINE', | |
52 | '246': 'RPL_STATSGLINE', | |
53 | '247': 'RPL_STATSXLINE', | |
54 | '248': 'RPL_STATSULINE', | |
55 | '249': 'RPL_STATSDEBUG', | |
3ec786bc JA |
56 | '250': 'RPL_STATSCONN', |
57 | '251': 'RPL_LUSERCLIENT', | |
58 | '252': 'RPL_LUSEROP', | |
59 | '253': 'RPL_LUSERUNKNOWN', | |
60 | '254': 'RPL_LUSERCHANNELS', | |
61 | '255': 'RPL_LUSERME', | |
f26a988a CC |
62 | '256': 'RPL_ADMINME', |
63 | '257': 'RPL_ADMINLOC1', | |
64 | '258': 'RPL_ADMINLOC2', | |
65 | '259': 'RPL_ADMINEMAIL', | |
3ec786bc JA |
66 | '265': 'RPL_LOCALUSERS', |
67 | '266': 'RPL_GLOBALUSERS', | |
f26a988a CC |
68 | '290': 'RPL_HELPHDR', |
69 | '291': 'RPL_HELPOP', | |
70 | '292': 'RPL_HELPTLR', | |
3ec786bc | 71 | '301': 'RPL_AWAY', |
f26a988a | 72 | '304': 'RPL_ZIPSTATS', |
3ec786bc | 73 | '307': 'RPL_WHOISREGNICK', |
94e2cb82 | 74 | '310': 'RPL_WHOISHELPOP', |
3ec786bc JA |
75 | '311': 'RPL_WHOISUSER', |
76 | '312': 'RPL_WHOISSERVER', | |
77 | '313': 'RPL_WHOISOPERATOR', | |
5989f3f4 | 78 | '314': 'RPL_WHOWASUSER', |
3ab39028 | 79 | '315': 'RPL_ENDOFWHO', |
3ec786bc JA |
80 | '317': 'RPL_WHOISIDLE', |
81 | '318': 'RPL_ENDOFWHOIS', | |
82 | '319': 'RPL_WHOISCHANNELS', | |
f26a988a | 83 | '320': 'RPL_WHOISSPECIAL', |
3ec786bc JA |
84 | '321': 'RPL_LISTSTART', |
85 | '322': 'RPL_LIST', | |
86 | '323': 'RPL_LISTEND', | |
72db27e4 D |
87 | '324': 'RPL_CHANNELMODEIS', |
88 | '328': 'RPL_CHANNEL_URL', | |
89 | '329': 'RPL_CREATIONTIME', | |
3d753975 | 90 | '330': 'RPL_WHOISACCOUNT', |
3ec786bc JA |
91 | '331': 'RPL_NOTOPIC', |
92 | '332': 'RPL_TOPIC', | |
93 | '333': 'RPL_TOPICWHOTIME', | |
951cd81e | 94 | '335': 'RPL_WHOISBOT', |
f26a988a | 95 | '338': 'RPL_WHOISACTUALLY', |
9a1da609 | 96 | '341': 'RPL_INVITING', |
3ab39028 | 97 | '352': 'RPL_WHOREPLY', |
3ec786bc JA |
98 | '353': 'RPL_NAMEREPLY', |
99 | '364': 'RPL_LINKS', | |
100 | '365': 'RPL_ENDOFLINKS', | |
101 | '366': 'RPL_ENDOFNAMES', | |
102 | '367': 'RPL_BANLIST', | |
103 | '368': 'RPL_ENDOFBANLIST', | |
5989f3f4 | 104 | '369': 'RPL_ENDOFWHOWAS', |
f26a988a | 105 | '371': 'RPL_INFO', |
3ec786bc | 106 | '372': 'RPL_MOTD', |
f26a988a | 107 | '374': 'RPL_ENDINFO', |
3ec786bc JA |
108 | '375': 'RPL_MOTDSTART', |
109 | '376': 'RPL_ENDOFMOTD', | |
3d753975 | 110 | '378': 'RPL_WHOISHOST', |
3ec786bc | 111 | '379': 'RPL_WHOISMODES', |
f26a988a | 112 | '381': 'RPL_NOWOPER', |
b4f0a085 | 113 | '396': 'RPL_HOSTCLOACKING', |
3ec786bc | 114 | '401': 'ERR_NOSUCHNICK', |
f26a988a | 115 | '402': 'ERR_NOSUCHSERVER', |
3ec786bc JA |
116 | '404': 'ERR_CANNOTSENDTOCHAN', |
117 | '405': 'ERR_TOOMANYCHANNELS', | |
5989f3f4 | 118 | '406': 'ERR_WASNOSUCHNICK', |
3ec786bc JA |
119 | '421': 'ERR_UNKNOWNCOMMAND', |
120 | '422': 'ERR_NOMOTD', | |
f26a988a | 121 | '423': 'ERR_NOADMININFO', |
66c980d3 | 122 | '432': 'ERR_ERRONEUSNICKNAME', |
3ec786bc JA |
123 | '433': 'ERR_NICKNAMEINUSE', |
124 | '441': 'ERR_USERNOTINCHANNEL', | |
125 | '442': 'ERR_NOTONCHANNEL', | |
3d753975 | 126 | '443': 'ERR_USERONCHANNEL', |
3ec786bc | 127 | '451': 'ERR_NOTREGISTERED', |
514955a3 | 128 | '461': 'ERR_NOTENOUGHPARAMS', |
3ec786bc JA |
129 | '464': 'ERR_PASSWDMISMATCH', |
130 | '470': 'ERR_LINKCHANNEL', | |
131 | '471': 'ERR_CHANNELISFULL', | |
8d192fd2 | 132 | '472': 'ERR_UNKNOWNMODE', |
3ec786bc JA |
133 | '473': 'ERR_INVITEONLYCHAN', |
134 | '474': 'ERR_BANNEDFROMCHAN', | |
135 | '475': 'ERR_BADCHANNELKEY', | |
136 | '481': 'ERR_NOPRIVILEGES', | |
137 | '482': 'ERR_CHANOPRIVSNEEDED', | |
8d192fd2 CC |
138 | '483': 'ERR_CANTKILLSERVER', |
139 | '484': 'ERR_ISCHANSERVICE', | |
140 | '485': 'ERR_ISREALSERVICE', | |
141 | '491': 'ERR_NOOPERHOST', | |
3ec786bc | 142 | '670': 'RPL_STARTTLS', |
3d753975 | 143 | '671': 'RPL_WHOISSECURE', |
3ec786bc JA |
144 | '900': 'RPL_SASLAUTHENTICATED', |
145 | '903': 'RPL_SASLLOGGEDIN', | |
146 | '904': 'ERR_SASLNOTAUTHORISED', | |
147 | '906': 'ERR_SASLABORTED', | |
94e2cb82 | 148 | '907': 'ERR_SASLALREADYAUTHED', |
8d192fd2 | 149 | '972': 'ERR_CANNOTDOCOMMAND', |
514955a3 | 150 | 'WALLOPS': 'RPL_WALLOPS' |
a8bf3ea4 JA |
151 | }; |
152 | ||
153 | ||
138d8d17 | 154 | function IrcCommandsHandler (irc_connection) { |
a8bf3ea4 | 155 | this.irc_connection = irc_connection; |
138d8d17 D |
156 | this.handlers = []; |
157 | ||
158 | require('./commands/registration')(this); | |
159 | require('./commands/channel')(this); | |
160 | require('./commands/user')(this); | |
161 | require('./commands/messaging')(this); | |
162 | require('./commands/misc')(this); | |
163 | } | |
164 | ||
165 | ||
166 | IrcCommandsHandler.prototype.dispatch = function (irc_command) { | |
167 | var command_name = irc_command.command; | |
a8bf3ea4 | 168 | |
138d8d17 D |
169 | // Check if we have a numeric->command name- mapping for this command |
170 | if (irc_numerics[irc_command.command.toUpperCase()]) { | |
171 | command_name = irc_numerics[irc_command.command.toUpperCase()]; | |
3ec786bc | 172 | } |
138d8d17 D |
173 | |
174 | if (this.handlers[command_name]) { | |
175 | this.handlers[command_name].call(this, irc_command); | |
3ec786bc | 176 | } else { |
138d8d17 | 177 | this.emitUnknownCommand(irc_command); |
3ec786bc JA |
178 | } |
179 | }; | |
2a8e95d1 | 180 | |
138d8d17 D |
181 | |
182 | IrcCommandsHandler.prototype.addHandler = function (command, handler) { | |
3ec786bc JA |
183 | if (typeof handler !== 'function') { |
184 | return false; | |
185 | } | |
138d8d17 | 186 | this.handlers[command] = handler; |
a8bf3ea4 JA |
187 | }; |
188 | ||
138d8d17 D |
189 | |
190 | IrcCommandsHandler.prototype.addNumeric = function (numeric, handler_name) { | |
3ec786bc | 191 | irc_numerics[numeric + ''] = handler_name +''; |
c08717da D |
192 | }; |
193 | ||
3d753975 | 194 | |
138d8d17 | 195 | IrcCommandsHandler.prototype.emitUnknownCommand = function (command) { |
3d753975 | 196 | this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' unknown_command', { |
138d8d17 D |
197 | command: command.command, |
198 | params: command.params | |
3d753975 | 199 | }); |
17dfa698 | 200 | }; |
2a8e95d1 D |
201 | |
202 | ||
138d8d17 | 203 | IrcCommandsHandler.prototype.emitGenericNotice = function (command, msg, is_error) { |
cea1f25b | 204 | // Default to being an error |
138d8d17 | 205 | if (typeof is_error !== 'boolean') { |
cea1f25b | 206 | is_error = true; |
138d8d17 | 207 | } |
cea1f25b | 208 | |
cd5b1558 | 209 | this.irc_connection.emit('user ' + command.prefix + ' notice', { |
514955a3 CC |
210 | from_server: true, |
211 | nick: command.prefix, | |
cea1f25b D |
212 | ident: '', |
213 | hostname: '', | |
214 | target: command.params[0], | |
215 | msg: msg, | |
216 | numeric: parseInt(command.command, 10) | |
217 | }); | |
138d8d17 D |
218 | }; |
219 | ||
220 | ||
221 | IrcCommandsHandler.prototype.emit = function() { | |
222 | return this.irc_connection.emit.apply(this.irc_connection, arguments); | |
223 | }; | |
0d14bcf8 | 224 | |
50263ea0 | 225 | |
72db27e4 D |
226 | /** |
227 | * Convert a mode string such as '+k pass', or '-i' to a readable | |
228 | * format. | |
229 | * [ { mode: '+k', param: 'pass' } ] | |
230 | * [ { mode: '-i', param: null } ] | |
231 | */ | |
138d8d17 | 232 | IrcCommandsHandler.prototype.parseModeList = function (mode_string, mode_params) { |
72db27e4 D |
233 | var chanmodes = this.irc_connection.options.CHANMODES || [], |
234 | prefixes = this.irc_connection.options.PREFIX || [], | |
235 | always_param = (chanmodes[0] || '').concat((chanmodes[1] || '')), | |
236 | modes = [], | |
237 | has_param, i, j, add; | |
238 | ||
239 | prefixes = _.reduce(prefixes, function (list, prefix) { | |
240 | list.push(prefix.mode); | |
241 | return list; | |
242 | }, []); | |
243 | always_param = always_param.split('').concat(prefixes); | |
244 | ||
245 | has_param = function (mode, add) { | |
246 | if (_.find(always_param, function (m) { | |
247 | return m === mode; | |
248 | })) { | |
249 | return true; | |
250 | } else if (add && _.find((chanmodes[2] || '').split(''), function (m) { | |
251 | return m === mode; | |
252 | })) { | |
253 | return true; | |
254 | } else { | |
255 | return false; | |
256 | } | |
257 | }; | |
258 | ||
259 | j = 0; | |
260 | for (i = 0; i < mode_string.length; i++) { | |
261 | switch (mode_string[i]) { | |
262 | case '+': | |
263 | add = true; | |
264 | break; | |
265 | case '-': | |
266 | add = false; | |
267 | break; | |
268 | default: | |
269 | if (has_param(mode_string[i], add)) { | |
270 | modes.push({mode: (add ? '+' : '-') + mode_string[i], param: mode_params[j]}); | |
271 | j++; | |
272 | } else { | |
273 | modes.push({mode: (add ? '+' : '-') + mode_string[i], param: null}); | |
274 | } | |
275 | } | |
276 | } | |
277 | ||
278 | return modes; | |
279 | } | |
280 | ||
281 | ||
138d8d17 D |
282 | |
283 | ||
284 | ||
285 | function IrcCommand(command, data) { | |
286 | this.command = command += ''; | |
287 | this.params = _.clone(data.params); | |
288 | this.tags = _.clone(data.tags); | |
289 | ||
290 | this.prefix = data.prefix; | |
291 | this.nick = data.nick; | |
292 | this.ident = data.ident; | |
293 | this.hostname = data.hostname; | |
294 | } | |
295 | ||
296 | ||
297 | IrcCommand.prototype.getServerTime = function() { | |
50263ea0 D |
298 | var time; |
299 | ||
300 | // No tags? No times. | |
138d8d17 D |
301 | if (!this.tags || this.tags.length === 0) { |
302 | return; | |
50263ea0 D |
303 | } |
304 | ||
138d8d17 D |
305 | time = _.find(this.tags, function (tag) { |
306 | return tag.tag === 'time'; | |
307 | }); | |
50263ea0 | 308 | |
138d8d17 D |
309 | if (time) { |
310 | time = time.value; | |
311 | } | |
50263ea0 | 312 | |
138d8d17 D |
313 | // Convert the time value to a unixtimestamp |
314 | if (typeof time === 'string') { | |
315 | if (time.indexOf('T') > -1) { | |
316 | time = parseISO8601(time); | |
50263ea0 | 317 | |
138d8d17 D |
318 | } else if(time.match(/^[0-9.]+$/)) { |
319 | // A string formatted unix timestamp | |
50263ea0 | 320 | time = new Date(time * 1000); |
50263ea0 | 321 | } |
138d8d17 D |
322 | |
323 | time = time.getTime(); | |
324 | ||
325 | } else if (typeof time === 'number') { | |
326 | time = new Date(time * 1000); | |
327 | time = time.getTime(); | |
50263ea0 D |
328 | } |
329 | ||
330 | return time; | |
138d8d17 D |
331 | }; |
332 | ||
333 | ||
334 | ||
335 | ||
336 | ||
50263ea0 D |
337 | // Code based on http://anentropic.wordpress.com/2009/06/25/javascript-iso8601-parser-and-pretty-dates/#comment-154 |
338 | function parseISO8601(str) { | |
339 | if (Date.prototype.toISOString) { | |
340 | return new Date(str); | |
341 | } else { | |
342 | var parts = str.split('T'), | |
343 | dateParts = parts[0].split('-'), | |
344 | timeParts = parts[1].split('Z'), | |
345 | timeSubParts = timeParts[0].split(':'), | |
346 | timeSecParts = timeSubParts[2].split('.'), | |
347 | timeHours = Number(timeSubParts[0]), | |
348 | _date = new Date(); | |
349 | ||
350 | _date.setUTCFullYear(Number(dateParts[0])); | |
351 | _date.setUTCDate(1); | |
352 | _date.setUTCMonth(Number(dateParts[1])-1); | |
353 | _date.setUTCDate(Number(dateParts[2])); | |
354 | _date.setUTCHours(Number(timeHours)); | |
355 | _date.setUTCMinutes(Number(timeSubParts[1])); | |
356 | _date.setUTCSeconds(Number(timeSecParts[0])); | |
357 | if (timeSecParts[1]) { | |
358 | _date.setUTCMilliseconds(Number(timeSecParts[1])); | |
359 | } | |
360 | ||
361 | return _date; | |
362 | } | |
363 | } | |
138d8d17 D |
364 | |
365 | ||
366 | module.exports.Handler = IrcCommandsHandler; | |
367 | module.exports.Command = IrcCommand; |