Merge branch 'winston' of https://github.com/M2Ys4U/KiwiIRC into winston
[KiwiIRC.git] / server / irc / commands.js
CommitLineData
3ec786bc
JA
1var _ = require('lodash'),
2 irc_numerics,
3 IrcCommands,
4 handlers,
5 unknownCommand;
6
7irc_numerics = {
8 '001': 'RPL_WELCOME',
9 '004': 'RPL_MYINFO',
10 '005': 'RPL_ISUPPORT',
11 '006': 'RPL_MAPMORE',
12 '007': 'RPL_MAPEND',
13 '250': 'RPL_STATSCONN',
14 '251': 'RPL_LUSERCLIENT',
15 '252': 'RPL_LUSEROP',
16 '253': 'RPL_LUSERUNKNOWN',
17 '254': 'RPL_LUSERCHANNELS',
18 '255': 'RPL_LUSERME',
19 '265': 'RPL_LOCALUSERS',
20 '266': 'RPL_GLOBALUSERS',
21 '301': 'RPL_AWAY',
22 '307': 'RPL_WHOISREGNICK',
23 '311': 'RPL_WHOISUSER',
24 '312': 'RPL_WHOISSERVER',
25 '313': 'RPL_WHOISOPERATOR',
5989f3f4 26 '314': 'RPL_WHOWASUSER',
3ab39028 27 '315': 'RPL_ENDOFWHO',
3ec786bc
JA
28 '317': 'RPL_WHOISIDLE',
29 '318': 'RPL_ENDOFWHOIS',
30 '319': 'RPL_WHOISCHANNELS',
31 '321': 'RPL_LISTSTART',
32 '322': 'RPL_LIST',
33 '323': 'RPL_LISTEND',
72db27e4
D
34 '324': 'RPL_CHANNELMODEIS',
35 '328': 'RPL_CHANNEL_URL',
36 '329': 'RPL_CREATIONTIME',
3d753975 37 '330': 'RPL_WHOISACCOUNT',
3ec786bc
JA
38 '331': 'RPL_NOTOPIC',
39 '332': 'RPL_TOPIC',
40 '333': 'RPL_TOPICWHOTIME',
9a1da609 41 '341': 'RPL_INVITING',
3ab39028 42 '352': 'RPL_WHOREPLY',
3ec786bc
JA
43 '353': 'RPL_NAMEREPLY',
44 '364': 'RPL_LINKS',
45 '365': 'RPL_ENDOFLINKS',
46 '366': 'RPL_ENDOFNAMES',
47 '367': 'RPL_BANLIST',
48 '368': 'RPL_ENDOFBANLIST',
5989f3f4 49 '369': 'RPL_ENDOFWHOWAS',
3ec786bc
JA
50 '372': 'RPL_MOTD',
51 '375': 'RPL_MOTDSTART',
52 '376': 'RPL_ENDOFMOTD',
3d753975 53 '378': 'RPL_WHOISHOST',
3ec786bc
JA
54 '379': 'RPL_WHOISMODES',
55 '401': 'ERR_NOSUCHNICK',
56 '404': 'ERR_CANNOTSENDTOCHAN',
57 '405': 'ERR_TOOMANYCHANNELS',
5989f3f4 58 '406': 'ERR_WASNOSUCHNICK',
3ec786bc
JA
59 '421': 'ERR_UNKNOWNCOMMAND',
60 '422': 'ERR_NOMOTD',
66c980d3 61 '432': 'ERR_ERRONEUSNICKNAME',
3ec786bc
JA
62 '433': 'ERR_NICKNAMEINUSE',
63 '441': 'ERR_USERNOTINCHANNEL',
64 '442': 'ERR_NOTONCHANNEL',
3d753975 65 '443': 'ERR_USERONCHANNEL',
3ec786bc
JA
66 '451': 'ERR_NOTREGISTERED',
67 '464': 'ERR_PASSWDMISMATCH',
68 '470': 'ERR_LINKCHANNEL',
69 '471': 'ERR_CHANNELISFULL',
70 '473': 'ERR_INVITEONLYCHAN',
71 '474': 'ERR_BANNEDFROMCHAN',
72 '475': 'ERR_BADCHANNELKEY',
73 '481': 'ERR_NOPRIVILEGES',
74 '482': 'ERR_CHANOPRIVSNEEDED',
75 '670': 'RPL_STARTTLS',
3d753975 76 '671': 'RPL_WHOISSECURE',
3ec786bc
JA
77 '900': 'RPL_SASLAUTHENTICATED',
78 '903': 'RPL_SASLLOGGEDIN',
79 '904': 'ERR_SASLNOTAUTHORISED',
80 '906': 'ERR_SASLABORTED',
81 '907': 'ERR_SASLALREADYAUTHED'
a8bf3ea4
JA
82};
83
84
cced0091 85IrcCommands = function (irc_connection) {
a8bf3ea4 86 this.irc_connection = irc_connection;
a8bf3ea4 87};
2a8e95d1 88module.exports = IrcCommands;
a8bf3ea4 89
3ec786bc
JA
90IrcCommands.prototype.dispatch = function (command, data) {
91 command += '';
92 if (irc_numerics[command]) {
93 command = irc_numerics[command];
94 }
95 if (handlers[command]) {
96 handlers[command].call(this, data);
97 } else {
3d753975 98 unknownCommand.call(this, command, data);
3ec786bc
JA
99 }
100};
2a8e95d1 101
3ec786bc
JA
102IrcCommands.addHandler = function (command, handler) {
103 if (typeof handler !== 'function') {
104 return false;
105 }
106 handlers[command] = handler;
a8bf3ea4
JA
107};
108
3ec786bc
JA
109IrcCommands.addNumeric = function (numeric, handler_name) {
110 irc_numerics[numeric + ''] = handler_name +'';
c08717da
D
111};
112
3ec786bc 113unknownCommand = function (command, data) {
3d753975
D
114 var params = _.clone(data.params);
115
116 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' unknown_command', {
117 command: command,
17dfa698 118 params: params
3d753975 119 });
17dfa698 120};
2a8e95d1
D
121
122
3ec786bc 123handlers = {
a49e0dcf
JA
124 'RPL_WELCOME': function (command) {
125 var nick = command.params[0];
126 this.irc_connection.registered = true;
992e681c 127 this.cap_negotiation = false;
d9285da9 128 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' connect', {
1cc056b8
JA
129 nick: nick
130 });
a49e0dcf 131 },
3ec786bc 132
a49e0dcf
JA
133 'RPL_ISUPPORT': function (command) {
134 var options, i, option, matches, j;
135 options = command.params;
136 for (i = 1; i < options.length; i++) {
137 option = options[i].split("=", 2);
138 option[0] = option[0].toUpperCase();
139 this.irc_connection.options[option[0]] = (typeof option[1] !== 'undefined') ? option[1] : true;
140 if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'CHANMODES', 'NAMESX'], option[0])) {
141 if (option[0] === 'PREFIX') {
142 matches = /\(([^)]*)\)(.*)/.exec(option[1]);
143 if ((matches) && (matches.length === 3)) {
144 this.irc_connection.options.PREFIX = [];
145 for (j = 0; j < matches[2].length; j++) {
146 this.irc_connection.options.PREFIX.push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
a8bf3ea4
JA
147 }
148 }
a49e0dcf
JA
149 } else if (option[0] === 'CHANTYPES') {
150 this.irc_connection.options.CHANTYPES = this.irc_connection.options.CHANTYPES.split('');
151 } else if (option[0] === 'CHANMODES') {
152 this.irc_connection.options.CHANMODES = option[1].split(',');
153 } else if ((option[0] === 'NAMESX') && (!_.contains(this.irc_connection.cap.enabled, 'multi-prefix'))) {
154 this.irc_connection.write('PROTOCTL NAMESX');
a8bf3ea4 155 }
a49e0dcf
JA
156 }
157 }
d9285da9 158 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' options', {
1cc056b8
JA
159 options: this.irc_connection.options,
160 cap: this.irc_connection.cap.enabled
161 });
a49e0dcf 162 },
3ec786bc 163
a49e0dcf 164 'RPL_ENDOFWHOIS': function (command) {
d9285da9 165 this.irc_connection.emit('user ' + command.params[1] + ' endofwhois', {
692163ca 166 nick: command.params[1],
17dfa698 167 msg: command.params[command.params.length - 1]
692163ca 168 });
a49e0dcf 169 },
3ec786bc 170
f701d5ba
D
171 'RPL_AWAY': function (command) {
172 this.irc_connection.emit('user ' + command.params[1] + ' whoisaway', {
173 nick: command.params[1],
17dfa698 174 reason: command.params[command.params.length - 1]
f701d5ba
D
175 });
176 },
3ec786bc 177
a49e0dcf 178 'RPL_WHOISUSER': function (command) {
d9285da9 179 this.irc_connection.emit('user ' + command.params[1] + ' whoisuser', {
692163ca
JA
180 nick: command.params[1],
181 ident: command.params[2],
182 host: command.params[3],
17dfa698 183 msg: command.params[command.params.length - 1]
692163ca 184 });
a49e0dcf 185 },
3ec786bc 186
a49e0dcf 187 'RPL_WHOISSERVER': function (command) {
d9285da9 188 this.irc_connection.emit('user ' + command.params[1] + ' whoisserver', {
692163ca 189 nick: command.params[1],
5989f3f4 190 irc_server: command.params[2],
17dfa698 191 server_info: command.params[command.params.length - 1]
692163ca 192 });
a49e0dcf 193 },
3ec786bc 194
a49e0dcf 195 'RPL_WHOISOPERATOR': function (command) {
d9285da9 196 this.irc_connection.emit('user ' + command.params[1] + ' whoisoperator', {
692163ca 197 nick: command.params[1],
17dfa698 198 msg: command.params[command.params.length - 1]
692163ca 199 });
a49e0dcf 200 },
3ec786bc 201
a49e0dcf 202 'RPL_WHOISCHANNELS': function (command) {
d9285da9 203 this.irc_connection.emit('user ' + command.params[1] + ' whoischannels', {
692163ca 204 nick: command.params[1],
17dfa698 205 chans: command.params[command.params.length - 1]
692163ca 206 });
a49e0dcf 207 },
3ec786bc 208
a49e0dcf 209 'RPL_WHOISMODES': function (command) {
d9285da9 210 this.irc_connection.emit('user ' + command.params[1] + ' whoismodes', {
692163ca 211 nick: command.params[1],
17dfa698 212 msg: command.params[command.params.length - 1]
692163ca 213 });
a49e0dcf 214 },
3ec786bc 215
a49e0dcf 216 'RPL_WHOISIDLE': function (command) {
d9285da9 217 this.irc_connection.emit('user ' + command.params[1] + ' whoisidle', {
692163ca
JA
218 nick: command.params[1],
219 idle: command.params[2],
220 logon: command.params[3] || undefined
221 });
a49e0dcf 222 },
3ec786bc 223
a49e0dcf 224 'RPL_WHOISREGNICK': function (command) {
d9285da9 225 this.irc_connection.emit('user ' + command.params[1] + ' whoisregnick', {
692163ca 226 nick: command.params[1],
17dfa698 227 msg: command.params[command.params.length - 1]
692163ca 228 });
a49e0dcf 229 },
3ec786bc 230
3d753975
D
231 'RPL_WHOISHOST': function (command) {
232 this.irc_connection.emit('user ' + command.params[1] + ' whoishost', {
233 nick: command.params[1],
17dfa698 234 msg: command.params[command.params.length - 1]
3d753975
D
235 });
236 },
237
238 'RPL_WHOISSECURE': function (command) {
239 this.irc_connection.emit('user ' + command.params[1] + ' whoissecure', {
240 nick: command.params[1]
241 });
242 },
243
244 'RPL_WHOISACCOUNT': function (command) {
245 this.irc_connection.emit('user ' + command.params[1] + ' whoisaccount', {
246 nick: command.params[1],
247 account: command.params[2]
248 });
249 },
250
5989f3f4
JA
251 'RPL_WHOWASUSER': function (command) {
252 this.irc_connection.emit('user ' + command.params[1] + ' whowas', {
253 nick: command.params[1],
254 ident: command.params[2],
255 host: command.params[3],
17dfa698 256 real_name: command.params[command.params.length - 1]
5989f3f4
JA
257 });
258 },
259
260 'RPL_ENDOFWHOWAS': function (command) {
261 this.irc_connection.emit('user ' + command.params[1] + ' endofwhowas', {
262 nick: command.params[1]
263 });
264 },
265
266 'ERR_WASNOSUCHNICK': function (command) {
267 this.irc_connection.emit('user ' + command.params[1] + ' wasnosucknick', {
268 nick: command.params[1]
269 });
270 },
271
a49e0dcf 272 'RPL_LISTSTART': function (command) {
d9285da9 273 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' list_start', {});
a49e0dcf 274 },
3ec786bc 275
a49e0dcf 276 'RPL_LISTEND': function (command) {
d9285da9 277 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' list_end', {});
a49e0dcf 278 },
3ec786bc 279
a49e0dcf 280 'RPL_LIST': function (command) {
d9285da9 281 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' list_channel', {
fb1b18bc 282 channel: command.params[1],
25edd441 283 num_users: parseInt(command.params[2], 10),
5301ab32 284 topic: command.params[3] || ''
fb1b18bc 285 });
a49e0dcf 286 },
3ec786bc 287
72db27e4
D
288 'RPL_CHANNELMODEIS': function (command) {
289 var channel = command.params[1],
290 modes = parseModeList.call(this, command.params[2], command.params.slice(3));
291
292 this.irc_connection.emit('channel ' + channel + ' info', {
293 channel: channel,
294 modes: modes
295 });
296 },
297
298 'RPL_CREATIONTIME': function (command) {
299 var channel = command.params[1];
300
301 this.irc_connection.emit('channel ' + channel + ' info', {
302 channel: channel,
303 created_at: parseInt(command.params[2], 10)
304 });
305 },
306
307 'RPL_CHANNEL_URL': function (command) {
308 var channel = command.params[1];
309
310 this.irc_connection.emit('channel ' + channel + ' info', {
311 channel: channel,
17dfa698 312 url: command.params[command.params.length - 1]
72db27e4
D
313 });
314 },
315
a49e0dcf 316 'RPL_MOTD': function (command) {
d9285da9 317 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' motd', {
17dfa698 318 motd: command.params[command.params.length - 1] + '\n'
1cc056b8 319 });
a49e0dcf 320 },
3ec786bc 321
a49e0dcf 322 'RPL_MOTDSTART': function (command) {
d9285da9 323 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' motd_start', {});
a49e0dcf 324 },
3ec786bc 325
a49e0dcf 326 'RPL_ENDOFMOTD': function (command) {
d9285da9 327 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' motd_end', {});
a49e0dcf 328 },
3ec786bc 329
a49e0dcf 330 'RPL_NAMEREPLY': function (command) {
17dfa698 331 var members = command.params[command.params.length - 1].split(' ');
a49e0dcf
JA
332 var member_list = [];
333 var that = this;
a49e0dcf 334 _.each(members, function (member) {
b8d390b0
JA
335 var i = 0,
336 j = 0,
337 modes = [];
30b4d43a
D
338
339 // Make sure we have some prefixes already
340 if (that.irc_connection.options.PREFIX) {
b8d390b0
JA
341 for (j = 0; j < that.irc_connection.options.PREFIX.length; j++) {
342 if (member.charAt(i) === that.irc_connection.options.PREFIX[j].symbol) {
343 modes.push(that.irc_connection.options.PREFIX[j].mode);
344 i++;
a8bf3ea4 345 }
a8bf3ea4 346 }
a49e0dcf 347 }
30b4d43a 348
a49e0dcf 349 member_list.push({nick: member, modes: modes});
a49e0dcf 350 });
e2752476 351
d9285da9 352 this.irc_connection.emit('channel ' + command.params[2] + ' userlist', {
e2752476
D
353 users: member_list,
354 channel: command.params[2]
355 });
a49e0dcf 356 },
e2752476 357
a49e0dcf 358 'RPL_ENDOFNAMES': function (command) {
d9285da9 359 this.irc_connection.emit('channel ' + command.params[1] + ' userlist_end', {
e2752476
D
360 channel: command.params[1]
361 });
a49e0dcf 362 },
e2752476 363
3ab39028
D
364 'RPL_WHOREPLY': function (command) {
365 // For the time being, NOOP this command so they don't get passed
366 // down to the client. Waste of bandwidth since we do not use it yet
367 // TODO: Impliment RPL_WHOREPLY
368 },
369
370 'RPL_ENDOFWHO': function (command) {
371 // For the time being, NOOP this command so they don't get passed
372 // down to the client. Waste of bandwidth since we do not use it yet
373 // TODO: Impliment RPL_ENDOFWHO
374 },
375
a49e0dcf 376 'RPL_BANLIST': function (command) {
d9285da9 377 this.irc_connection.emit('channel ' + command.params[1] + ' banlist', {
059f4918
JA
378 channel: command.params[1],
379 banned: command.params[2],
380 banned_by: command.params[3],
381 banned_at: command.params[4]
382 });
a49e0dcf 383 },
3ec786bc 384
a49e0dcf 385 'RPL_ENDOFBANLIST': function (command) {
d9285da9 386 this.irc_connection.emit('channel ' + command.params[1] + ' banlist_end', {
aa22caa9 387 channel: command.params[1]
059f4918 388 });
a49e0dcf 389 },
3ec786bc 390
a49e0dcf 391 'RPL_TOPIC': function (command) {
d9285da9 392 this.irc_connection.emit('channel ' + command.params[1] + ' topic', {
059f4918 393 channel: command.params[1],
17dfa698 394 topic: command.params[command.params.length - 1]
059f4918 395 });
a49e0dcf 396 },
3ec786bc 397
a49e0dcf 398 'RPL_NOTOPIC': function (command) {
d9285da9 399 this.irc_connection.emit('channel ' + command.params[1] + ' topic', {
059f4918
JA
400 channel: command.params[1],
401 topic: ''
402 });
a49e0dcf 403 },
3ec786bc 404
a49e0dcf 405 'RPL_TOPICWHOTIME': function (command) {
d9285da9 406 this.irc_connection.emit('channel ' + command.params[1] + ' topicsetby', {
059f4918
JA
407 nick: command.params[2],
408 channel: command.params[1],
409 when: command.params[3]
410 });
a49e0dcf 411 },
3ec786bc 412
9a1da609
D
413 'RPL_INVITING': function (command) {
414 this.irc_connection.emit('channel ' + command.params[1] + ' invited', {
415 nick: command.params[0],
416 channel: command.params[1]
417 });
418 },
419
a49e0dcf 420 'PING': function (command) {
17dfa698 421 this.irc_connection.write('PONG ' + command.params[command.params.length - 1]);
a49e0dcf 422 },
e2752476 423
a49e0dcf 424 'JOIN': function (command) {
d10c74a6 425 var channel, time;
17dfa698 426 if (typeof command.params[0] === 'string' && command.params[0] !== '') {
a49e0dcf
JA
427 channel = command.params[0];
428 }
3ec786bc 429
50263ea0
D
430 // Check if we have a server-time
431 time = getServerTime.call(this, command);
d10c74a6 432
d9285da9 433 this.irc_connection.emit('channel ' + channel + ' join', {
e2752476
D
434 nick: command.nick,
435 ident: command.ident,
436 hostname: command.hostname,
d10c74a6
JA
437 channel: channel,
438 time: time
e2752476 439 });
a49e0dcf 440 },
e2752476 441
a49e0dcf 442 'PART': function (command) {
81198d7e 443 var time, channel, message;
d10c74a6 444
50263ea0
D
445 // Check if we have a server-time
446 time = getServerTime.call(this, command);
d10c74a6 447
17dfa698
JA
448 channel = command.params[0];
449 if (command.params.length > 1) {
450 message = command.params[command.params.length - 1];
81198d7e
D
451 }
452
f96620b7 453 this.irc_connection.emit('channel ' + channel + ' part', {
e2752476
D
454 nick: command.nick,
455 ident: command.ident,
456 hostname: command.hostname,
81198d7e
D
457 channel: channel,
458 message: message,
d10c74a6 459 time: time
e2752476 460 });
a49e0dcf 461 },
e2752476 462
a49e0dcf 463 'KICK': function (command) {
d10c74a6
JA
464 var time;
465
50263ea0
D
466 // Check if we have a server-time
467 time = getServerTime.call(this, command);
d10c74a6 468
d9285da9 469 this.irc_connection.emit('channel ' + command.params[0] + ' kick', {
e2752476
D
470 kicked: command.params[1],
471 nick: command.nick,
472 ident: command.ident,
473 hostname: command.hostname,
474 channel: command.params[0],
17dfa698 475 message: command.params[command.params.length - 1],
d10c74a6 476 time: time
e2752476 477 });
a49e0dcf 478 },
e2752476 479
a49e0dcf 480 'QUIT': function (command) {
d10c74a6
JA
481 var time;
482
50263ea0
D
483 // Check if we have a server-time
484 time = getServerTime.call(this, command);
d10c74a6 485
d9285da9 486 this.irc_connection.emit('user ' + command.nick + ' quit', {
e2752476
D
487 nick: command.nick,
488 ident: command.ident,
489 hostname: command.hostname,
17dfa698 490 message: command.params[command.params.length - 1],
d10c74a6 491 time: time
e2752476 492 });
a49e0dcf 493 },
e2752476 494
a49e0dcf 495 'NOTICE': function (command) {
d10c74a6 496 var namespace,
17dfa698
JA
497 time,
498 msg;
d10c74a6 499
50263ea0
D
500 // Check if we have a server-time
501 time = getServerTime.call(this, command);
d10c74a6 502
17dfa698
JA
503 msg = command.params[command.params.length - 1];
504 if ((msg.charAt(0) === String.fromCharCode(1)) && (msg.charAt(msg.length - 1) === String.fromCharCode(1))) {
a49e0dcf 505 // It's a CTCP response
17dfa698 506 namespace = (command.params[0].toLowerCase() === this.irc_connection.nick.toLowerCase()) ? 'user' : 'channel';
d9285da9 507 this.irc_connection.emit(namespace + ' ' + command.params[0] + ' ctcp_response', {
e2752476
D
508 nick: command.nick,
509 ident: command.ident,
510 hostname: command.hostname,
511 channel: command.params[0],
17dfa698 512 msg: msg.substring(1, msg.length - 1),
d10c74a6 513 time: time
e2752476 514 });
a49e0dcf 515 } else {
17dfa698 516 namespace = (command.params[0].toLowerCase() === this.irc_connection.nick.toLowerCase() || command.params[0] === '*') ?
0190c2c9
D
517 'user' :
518 'channel';
519
d9285da9 520 this.irc_connection.emit(namespace + ' ' + command.params[0] + ' notice', {
0190c2c9
D
521 from_server: command.prefix ? true : false,
522 nick: command.nick || command.prefix || undefined,
e2752476
D
523 ident: command.ident,
524 hostname: command.hostname,
525 target: command.params[0],
17dfa698 526 msg: msg,
d10c74a6 527 time: time
e2752476 528 });
a49e0dcf
JA
529 }
530 },
3ec786bc 531
a49e0dcf 532 'NICK': function (command) {
d10c74a6
JA
533 var time;
534
50263ea0
D
535 // Check if we have a server-time
536 time = getServerTime.call(this, command);
d10c74a6 537
d9285da9 538 this.irc_connection.emit('user ' + command.nick + ' nick', {
692163ca
JA
539 nick: command.nick,
540 ident: command.ident,
541 hostname: command.hostname,
17dfa698 542 newnick: command.params[0],
d10c74a6 543 time: time
692163ca 544 });
a49e0dcf 545 },
3ec786bc 546
a49e0dcf 547 'TOPIC': function (command) {
d10c74a6
JA
548 var time;
549
30b4d43a 550 // If we don't have an associated channel, no need to continue
17dfa698
JA
551 if (!command.params[0]) {
552 return;
553 }
30b4d43a 554
50263ea0
D
555 // Check if we have a server-time
556 time = getServerTime.call(this, command);
d10c74a6 557
30b4d43a 558 var channel = command.params[0],
17dfa698 559 topic = command.params[command.params.length - 1] || '';
30b4d43a 560
d9285da9 561 this.irc_connection.emit('channel ' + channel + ' topic', {
f4ec5774
D
562 nick: command.nick,
563 channel: channel,
d10c74a6
JA
564 topic: topic,
565 time: time
f4ec5774 566 });
a49e0dcf 567 },
3ec786bc 568
d9285da9 569 'MODE': function (command) {
72db27e4 570 var modes = [], event, time;
d10c74a6 571
50263ea0
D
572 // Check if we have a server-time
573 time = getServerTime.call(this, command);
3ec786bc 574
72db27e4 575 // Get a JSON representation of the modes
17dfa698 576 modes = parseModeList.call(this, command.params[1], command.params.slice(2));
d9285da9 577 event = (_.contains(this.irc_connection.options.CHANTYPES, command.params[0][0]) ? 'channel ' : 'user ') + command.params[0] + ' mode';
3ec786bc 578
a7973dfb 579 this.irc_connection.emit(event, {
a49e0dcf
JA
580 target: command.params[0],
581 nick: command.nick || command.prefix || '',
d10c74a6
JA
582 modes: modes,
583 time: time
a49e0dcf
JA
584 });
585 },
3ec786bc 586
a49e0dcf 587 'PRIVMSG': function (command) {
17dfa698 588 var tmp, namespace, time, msg;
d10c74a6 589
50263ea0
D
590 // Check if we have a server-time
591 time = getServerTime.call(this, command);
d10c74a6 592
17dfa698
JA
593 msg = command.params[command.params.length - 1];
594 if ((msg.charAt(0) === String.fromCharCode(1)) && (msg.charAt(msg.length - 1) === String.fromCharCode(1))) {
a49e0dcf 595 //CTCP request
17dfa698 596 if (msg.substr(1, 6) === 'ACTION') {
d10c74a6
JA
597 this.irc_connection.clientEvent('action', {
598 nick: command.nick,
599 ident: command.ident,
600 hostname: command.hostname,
601 channel: command.params[0],
17dfa698 602 msg: msg.substring(8, msg.length - 1),
d10c74a6
JA
603 time: time
604 });
17dfa698
JA
605 } else if (msg.substr(1, 4) === 'KIWI') {
606 tmp = msg.substring(6, msg.length - 1);
a49e0dcf 607 namespace = tmp.split(' ', 1)[0];
d10c74a6
JA
608 this.irc_connection.clientEvent('kiwi', {
609 namespace: namespace,
610 data: tmp.substr(namespace.length + 1),
611 time: time
612 });
17dfa698 613 } else if (msg.substr(1, 7) === 'VERSION') {
a49e0dcf 614 this.irc_connection.write('NOTICE ' + command.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1));
17dfa698 615 } else if (msg.substr(1, 6) === 'SOURCE') {
b39bb331 616 this.irc_connection.write('NOTICE ' + command.nick + ' :' + String.fromCharCode(1) + 'SOURCE http://www.kiwiirc.com/' + String.fromCharCode(1));
17dfa698 617 } else if (msg.substr(1, 10) === 'CLIENTINFO') {
b39bb331 618 this.irc_connection.write('NOTICE ' + command.nick + ' :' + String.fromCharCode(1) + 'CLIENTINFO SOURCE VERSION TIME' + String.fromCharCode(1));
a49e0dcf 619 } else {
17dfa698 620 namespace = (command.params[0].toLowerCase() === this.irc_connection.nick.toLowerCase()) ? 'user' : 'channel';
d9285da9 621 this.irc_connection.emit(namespace + ' ' + command.nick + ' ctcp_request', {
b39bb331
D
622 nick: command.nick,
623 ident: command.ident,
624 hostname: command.hostname,
625 target: command.params[0],
17dfa698
JA
626 type: (msg.substring(1, msg.length - 1).split(' ') || [null])[0],
627 msg: msg.substring(1, msg.length - 1),
d10c74a6 628 time: time
b39bb331 629 });
a49e0dcf
JA
630 }
631 } else {
e2752476 632 // A message to a user (private message) or to a channel?
17dfa698 633 namespace = (command.params[0].toLowerCase() === this.irc_connection.nick.toLowerCase()) ? 'user ' + command.nick : 'channel ' + command.params[0];
d9285da9 634 this.irc_connection.emit(namespace + ' privmsg', {
ffbbc70f
D
635 nick: command.nick,
636 ident: command.ident,
637 hostname: command.hostname,
638 channel: command.params[0],
17dfa698 639 msg: msg,
d10c74a6 640 time: time
b09157de 641 });
a49e0dcf
JA
642 }
643 },
3ec786bc 644
a49e0dcf
JA
645 'CAP': function (command) {
646 // TODO: capability modifiers
647 // i.e. - for disable, ~ for requires ACK, = for sticky
17dfa698 648 var capabilities = command.params[command.params.length - 1].replace(/(?:^| )[\-~=]/, '').split(' ');
a49e0dcf 649 var request;
259d9b96
JA
650
651 // Which capabilities we want to enable
50263ea0 652 var want = ['multi-prefix', 'away-notify', 'server-time', 'znc.in/server-time-iso', 'znc.in/server-time'];
259d9b96 653
a49e0dcf
JA
654 if (this.irc_connection.password) {
655 want.push('sasl');
656 }
259d9b96 657
a49e0dcf
JA
658 switch (command.params[1]) {
659 case 'LS':
259d9b96 660 // Compute which of the available capabilities we want and request them
a49e0dcf
JA
661 request = _.intersection(capabilities, want);
662 if (request.length > 0) {
663 this.irc_connection.cap.requested = request;
664 this.irc_connection.write('CAP REQ :' + request.join(' '));
665 } else {
666 this.irc_connection.write('CAP END');
992e681c 667 this.irc_connection.cap_negotiation = false;
a8bf3ea4 668 }
a49e0dcf
JA
669 break;
670 case 'ACK':
671 if (capabilities.length > 0) {
259d9b96 672 // Update list of enabled capabilities
a49e0dcf 673 this.irc_connection.cap.enabled = capabilities;
259d9b96 674 // Update list of capabilities we would like to have but that aren't enabled
a49e0dcf 675 this.irc_connection.cap.requested = _.difference(this.irc_connection.cap.requested, capabilities);
703d2778 676 }
259d9b96 677 if (this.irc_connection.cap.enabled.length > 0) {
a49e0dcf
JA
678 if (_.contains(this.irc_connection.cap.enabled, 'sasl')) {
679 this.irc_connection.sasl = true;
680 this.irc_connection.write('AUTHENTICATE PLAIN');
a8bf3ea4 681 } else {
a49e0dcf 682 this.irc_connection.write('CAP END');
992e681c 683 this.irc_connection.cap_negotiation = false;
a8bf3ea4 684 }
a8bf3ea4 685 }
a49e0dcf
JA
686 break;
687 case 'NAK':
688 if (capabilities.length > 0) {
689 this.irc_connection.cap.requested = _.difference(this.irc_connection.cap.requested, capabilities);
7dfe47c6 690 }
a49e0dcf 691 if (this.irc_connection.cap.requested.length > 0) {
7dfe47c6 692 this.irc_connection.write('CAP END');
992e681c 693 this.irc_connection.cap_negotiation = false;
7dfe47c6 694 }
a49e0dcf
JA
695 break;
696 case 'LIST':
697 // should we do anything here?
698 break;
699 }
700 },
3ec786bc 701
a49e0dcf
JA
702 'AUTHENTICATE': function (command) {
703 var b = new Buffer(this.irc_connection.nick + "\0" + this.irc_connection.nick + "\0" + this.irc_connection.password, 'utf8');
704 var b64 = b.toString('base64');
705 if (command.params[0] === '+') {
706 while (b64.length >= 400) {
707 this.irc_connection.write('AUTHENTICATE ' + b64.slice(0, 399));
708 b64 = b64.slice(399);
a8bf3ea4 709 }
a49e0dcf
JA
710 if (b64.length > 0) {
711 this.irc_connection.write('AUTHENTICATE ' + b64);
712 } else {
713 this.irc_connection.write('AUTHENTICATE +');
714 }
715 } else {
716 this.irc_connection.write('CAP END');
992e681c 717 this.irc_connection.cap_negotiation = false;
a49e0dcf
JA
718 }
719 },
3ec786bc 720
a49e0dcf 721 'AWAY': function (command) {
d10c74a6
JA
722 var time;
723
50263ea0
D
724 // Check if we have a server-time
725 time = getServerTime.call(this, command);
d10c74a6 726
d9285da9 727 this.irc_connection.emit('user ' + command.nick + ' away', {
692163ca 728 nick: command.nick,
17dfa698 729 msg: command.params[command.params.length - 1],
d10c74a6 730 time: time
692163ca 731 });
a49e0dcf 732 },
3ec786bc 733
a49e0dcf
JA
734 'RPL_SASLAUTHENTICATED': function (command) {
735 this.irc_connection.write('CAP END');
992e681c 736 this.irc_connection.cap_negotiation = false;
a49e0dcf
JA
737 this.irc_connection.sasl = true;
738 },
3ec786bc 739
a49e0dcf 740 'RPL_SASLLOGGEDIN': function (command) {
992e681c 741 if (this.irc_connection.cap_negotiation === true) {
a49e0dcf 742 this.irc_connection.write('CAP END');
992e681c 743 this.irc_connection.cap_negotiation = false;
a49e0dcf
JA
744 }
745 },
3ec786bc 746
a49e0dcf 747 'ERR_SASLNOTAUTHORISED': function (command) {
3ec786bc 748 this.irc_connection.write('CAP END');
992e681c 749 this.irc_connection.cap_negotiation = false;
3ec786bc
JA
750 },
751
a49e0dcf
JA
752 'ERR_SASLABORTED': function (command) {
753 this.irc_connection.write('CAP END');
992e681c 754 this.irc_connection.cap_negotiation = false;
a49e0dcf 755 },
3ec786bc 756
a49e0dcf
JA
757 'ERR_SASLALREADYAUTHED': function (command) {
758 // noop
759 },
3ec786bc 760
a49e0dcf 761 'ERROR': function (command) {
d9285da9 762 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' error', {
17dfa698 763 reason: command.params[command.params.length - 1]
1cc056b8 764 });
a49e0dcf 765 },
ebe178d6 766 ERR_PASSWDMISMATCH: function (command) {
d9285da9 767 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' password_mismatch', {});
ebe178d6 768 },
3ec786bc 769
a49e0dcf 770 ERR_LINKCHANNEL: function (command) {
d9285da9 771 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' channel_redirect', {
1cc056b8
JA
772 from: command.params[1],
773 to: command.params[2]
774 });
a49e0dcf 775 },
3ec786bc 776
a49e0dcf 777 ERR_NOSUCHNICK: function (command) {
d9285da9 778 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' no_such_nick', {
1cc056b8 779 nick: command.params[1],
17dfa698 780 reason: command.params[command.params.length - 1]
1cc056b8 781 });
a49e0dcf 782 },
3ec786bc 783
a49e0dcf 784 ERR_CANNOTSENDTOCHAN: function (command) {
d9285da9 785 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' cannot_send_to_chan', {
1cc056b8 786 channel: command.params[1],
17dfa698 787 reason: command.params[command.params.length - 1]
1cc056b8 788 });
a49e0dcf 789 },
3ec786bc 790
a49e0dcf 791 ERR_TOOMANYCHANNELS: function (command) {
d9285da9 792 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' too_many_channels', {
1cc056b8 793 channel: command.params[1],
17dfa698 794 reason: command.params[command.params.length - 1]
1cc056b8 795 });
a49e0dcf 796 },
3ec786bc 797
a49e0dcf 798 ERR_USERNOTINCHANNEL: function (command) {
d9285da9 799 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' user_not_in_channel', {
1cc056b8
JA
800 nick: command.params[0],
801 channel: command.params[1],
17dfa698 802 reason: command.params[command.params.length - 1]
1cc056b8 803 });
a49e0dcf 804 },
3ec786bc 805
a49e0dcf 806 ERR_NOTONCHANNEL: function (command) {
d9285da9 807 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' not_on_channel', {
1cc056b8 808 channel: command.params[1],
17dfa698 809 reason: command.params[command.params.length - 1]
1cc056b8 810 });
a49e0dcf 811 },
3ec786bc 812
3d753975
D
813 ERR_USERONCHANNEL: function (command) {
814 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' user_on_channel', {
815 nick: command.params[1],
816 channel: command.params[2]
817 });
818 },
819
a49e0dcf 820 ERR_CHANNELISFULL: function (command) {
3ec786bc
JA
821 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' channel_is_full', {
822 channel: command.params[1],
17dfa698 823 reason: command.params[command.params.length - 1]
3ec786bc
JA
824 });
825 },
826
a49e0dcf 827 ERR_INVITEONLYCHAN: function (command) {
d9285da9 828 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' invite_only_channel', {
1cc056b8 829 channel: command.params[1],
17dfa698 830 reason: command.params[command.params.length - 1]
1cc056b8 831 });
a49e0dcf 832 },
3ec786bc 833
a49e0dcf 834 ERR_BANNEDFROMCHAN: function (command) {
d9285da9 835 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' banned_from_channel', {
1cc056b8 836 channel: command.params[1],
17dfa698 837 reason: command.params[command.params.length - 1]
1cc056b8 838 });
a49e0dcf 839 },
3ec786bc 840
a49e0dcf 841 ERR_BADCHANNELKEY: function (command) {
d9285da9 842 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' bad_channel_key', {
1cc056b8 843 channel: command.params[1],
17dfa698 844 reason: command.params[command.params.length - 1]
1cc056b8 845 });
a49e0dcf 846 },
3ec786bc 847
a49e0dcf 848 ERR_CHANOPRIVSNEEDED: function (command) {
d9285da9 849 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' chanop_privs_needed', {
1cc056b8 850 channel: command.params[1],
17dfa698 851 reason: command.params[command.params.length - 1]
1cc056b8 852 });
a49e0dcf 853 },
3ec786bc 854
a49e0dcf 855 ERR_NICKNAMEINUSE: function (command) {
d9285da9 856 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' nickname_in_use', {
1cc056b8 857 nick: command.params[1],
17dfa698 858 reason: command.params[command.params.length - 1]
1cc056b8 859 });
a49e0dcf 860 },
3ec786bc 861
66c980d3
JA
862 ERR_ERRONEUSNICKNAME: function(command) {
863 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' erroneus_nickname', {
864 nick: command.params[1],
17dfa698 865 reason: command.params[command.params.length - 1]
66c980d3
JA
866 });
867 },
868
a49e0dcf 869 ERR_NOTREGISTERED: function (command) {
cea1f25b
D
870 },
871
872 RPL_MAPMORE: function (command) {
873 var params = _.clone(command.params);
874 params.shift();
17dfa698 875 genericNotice.call(this, command, params.slice(0, -1).join(', ') + ' ' + command.params[command.params.length - 1]);
cea1f25b 876 },
3ec786bc 877
cea1f25b
D
878 RPL_MAPEND: function (command) {
879 var params = _.clone(command.params);
880 params.shift();
17dfa698 881 genericNotice.call(this, command, params.slice(0, -1).join(', ') + ' ' + command.params[command.params.length - 1]);
cea1f25b
D
882 },
883
884 RPL_LINKS: function (command) {
885 var params = _.clone(command.params);
886 params.shift();
17dfa698 887 genericNotice.call(this, command, params.slice(0, -1).join(', ') + ' ' + command.params[command.params.length - 1]);
cea1f25b 888 },
3ec786bc 889
cea1f25b
D
890 RPL_ENDOFLINKS: function (command) {
891 var params = _.clone(command.params);
892 params.shift();
17dfa698 893 genericNotice.call(this, command, params.slice(0, -1).join(', ') + ' ' + command.params[command.params.length - 1]);
cea1f25b
D
894 },
895
896 ERR_UNKNOWNCOMMAND: function (command) {
897 var params = _.clone(command.params);
898 params.shift();
17dfa698 899 genericNotice.call(this, command, '`' + params.slice(0, -1).join(', ') + '` ' + command.params[command.params.length - 1]);
cea1f25b
D
900 },
901
902 ERR_NOMOTD: function (command) {
903 var params = _.clone(command.params);
904 params.shift();
17dfa698 905 genericNotice.call(this, command, command.params[command.params.length - 1]);
cea1f25b
D
906 },
907
908 ERR_NOPRIVILEGES: function (command) {
909 var params = _.clone(command.params);
910 params.shift();
17dfa698 911 genericNotice.call(this, command, command.params[command.params.length - 1]);
28ce8b75
D
912 },
913
914 RPL_STATSCONN: function (command) {
915 var params = _.clone(command.params);
916 params.shift();
17dfa698 917 genericNotice.call(this, command, params.slice(0, -1).join(', ') + ' ' + command.params[command.params.length - 1]);
28ce8b75
D
918 },
919
920 RPL_LUSERCLIENT: function (command) {
921 var params = _.clone(command.params);
922 params.shift();
17dfa698 923 genericNotice.call(this, command, params.slice(0, -1).join(', ') + ' ' + command.params[command.params.length - 1]);
28ce8b75
D
924 },
925
926 RPL_LUSEROP: function (command) {
927 var params = _.clone(command.params);
928 params.shift();
17dfa698 929 genericNotice.call(this, command, params.slice(0, -1).join(', ') + ' ' + command.params[command.params.length - 1]);
28ce8b75
D
930 },
931
932 RPL_LUSERUNKNOWN: function (command) {
933 var params = _.clone(command.params);
934 params.shift();
17dfa698 935 genericNotice.call(this, command, params.slice(0, -1).join(', ') + ' ' + command.params[command.params.length - 1]);
28ce8b75
D
936 },
937
938 RPL_LUSERCHANNELS: function (command) {
939 var params = _.clone(command.params);
940 params.shift();
17dfa698 941 genericNotice.call(this, command, params.slice(0, -1).join(', ') + ' ' + command.params[command.params.length - 1]);
28ce8b75
D
942 },
943
944 RPL_LUSERME: function (command) {
945 var params = _.clone(command.params);
946 params.shift();
17dfa698 947 genericNotice.call(this, command, params.slice(0, -1).join(', ') + ' ' + command.params[command.params.length - 1]);
28ce8b75
D
948 },
949
950 RPL_LOCALUSERS: function (command) {
951 var params = _.clone(command.params);
952 params.shift();
17dfa698 953 genericNotice.call(this, command, params.slice(0, -1).join(', ') + ' ' + command.params[command.params.length - 1]);
28ce8b75 954 },
3ec786bc 955
28ce8b75
D
956 RPL_GLOBALUSERS: function (command) {
957 var params = _.clone(command.params);
958 params.shift();
17dfa698 959 genericNotice.call(this, command, params.slice(0, -1).join(', ') + ' ' + command.params[command.params.length - 1]);
a49e0dcf 960 }
a8bf3ea4 961};
cea1f25b
D
962
963
964
965
966function genericNotice (command, msg, is_error) {
967 // Default to being an error
968 if (typeof is_error !== 'boolean')
969 is_error = true;
970
cced0091 971 this.irc_connection.clientEvent('notice', {
0190c2c9 972 from_server: true,
cea1f25b
D
973 nick: command.prefix,
974 ident: '',
975 hostname: '',
976 target: command.params[0],
977 msg: msg,
978 numeric: parseInt(command.command, 10)
979 });
aef6e2a7 980}
0d14bcf8 981
50263ea0 982
72db27e4
D
983/**
984 * Convert a mode string such as '+k pass', or '-i' to a readable
985 * format.
986 * [ { mode: '+k', param: 'pass' } ]
987 * [ { mode: '-i', param: null } ]
988 */
989function parseModeList(mode_string, mode_params) {
990 var chanmodes = this.irc_connection.options.CHANMODES || [],
991 prefixes = this.irc_connection.options.PREFIX || [],
992 always_param = (chanmodes[0] || '').concat((chanmodes[1] || '')),
993 modes = [],
994 has_param, i, j, add;
995
996 prefixes = _.reduce(prefixes, function (list, prefix) {
997 list.push(prefix.mode);
998 return list;
999 }, []);
1000 always_param = always_param.split('').concat(prefixes);
1001
1002 has_param = function (mode, add) {
1003 if (_.find(always_param, function (m) {
1004 return m === mode;
1005 })) {
1006 return true;
1007 } else if (add && _.find((chanmodes[2] || '').split(''), function (m) {
1008 return m === mode;
1009 })) {
1010 return true;
1011 } else {
1012 return false;
1013 }
1014 };
1015
1016 j = 0;
1017 for (i = 0; i < mode_string.length; i++) {
1018 switch (mode_string[i]) {
1019 case '+':
1020 add = true;
1021 break;
1022 case '-':
1023 add = false;
1024 break;
1025 default:
1026 if (has_param(mode_string[i], add)) {
1027 modes.push({mode: (add ? '+' : '-') + mode_string[i], param: mode_params[j]});
1028 j++;
1029 } else {
1030 modes.push({mode: (add ? '+' : '-') + mode_string[i], param: null});
1031 }
1032 }
1033 }
1034
1035 return modes;
1036}
1037
1038
50263ea0
D
1039function getServerTime(command) {
1040 var time;
1041
1042 // No tags? No times.
1043 if (!command.tags || command.tags.length === 0) {
1044 return time;
1045 }
1046
1047 if (capContainsAny.call(this, ['server-time', 'znc.in/server-time', 'znc.in/server-time-iso'])) {
1048 time = _.find(command.tags, function (tag) {
1049 return tag.tag === 'time';
1050 });
1051
1052 time = time ? time.value : undefined;
1053
1054 // Convert the time value to a unixtimestamp
1055 if (typeof time === 'string') {
1056 if (time.indexOf('T') > -1) {
75d92a37 1057 time = parseISO8601(time);
50263ea0
D
1058
1059 } else if(time.match(/^[0-9.]+$/)) {
1060 // A string formatted unix timestamp
1061 time = new Date(time * 1000);
1062 }
1063
1064 time = time.getTime();
1065
1066 } else if (typeof time === 'number') {
1067 time = new Date(time * 1000);
1068 time = time.getTime();
1069 }
1070 }
1071
1072 return time;
1073}
1074
1075
0d14bcf8
JA
1076function capContainsAny (caps) {
1077 var intersection;
50263ea0 1078 if (!caps instanceof Array) {
0d14bcf8
JA
1079 caps = [caps];
1080 }
1081 intersection = _.intersection(this.irc_connection.cap.enabled, caps);
1082 return intersection.length > 0;
1083}
50263ea0
D
1084
1085
1086// Code based on http://anentropic.wordpress.com/2009/06/25/javascript-iso8601-parser-and-pretty-dates/#comment-154
1087function parseISO8601(str) {
1088 if (Date.prototype.toISOString) {
1089 return new Date(str);
1090 } else {
1091 var parts = str.split('T'),
1092 dateParts = parts[0].split('-'),
1093 timeParts = parts[1].split('Z'),
1094 timeSubParts = timeParts[0].split(':'),
1095 timeSecParts = timeSubParts[2].split('.'),
1096 timeHours = Number(timeSubParts[0]),
1097 _date = new Date();
1098
1099 _date.setUTCFullYear(Number(dateParts[0]));
1100 _date.setUTCDate(1);
1101 _date.setUTCMonth(Number(dateParts[1])-1);
1102 _date.setUTCDate(Number(dateParts[2]));
1103 _date.setUTCHours(Number(timeHours));
1104 _date.setUTCMinutes(Number(timeSubParts[1]));
1105 _date.setUTCSeconds(Number(timeSecParts[0]));
1106 if (timeSecParts[1]) {
1107 _date.setUTCMilliseconds(Number(timeSecParts[1]));
1108 }
1109
1110 return _date;
1111 }
1112}