making it librejs compliant
[KiwiIRC.git] / server / irc / commands.js
CommitLineData
3ec786bc
JA
1var _ = require('lodash'),
2 irc_numerics,
3ec786bc
JA
3 unknownCommand;
4
5irc_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 154function 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
166IrcCommandsHandler.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
182IrcCommandsHandler.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
190IrcCommandsHandler.prototype.addNumeric = function (numeric, handler_name) {
3ec786bc 191 irc_numerics[numeric + ''] = handler_name +'';
c08717da
D
192};
193
3d753975 194
138d8d17 195IrcCommandsHandler.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 203IrcCommandsHandler.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
221IrcCommandsHandler.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 232IrcCommandsHandler.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
285function 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
297IrcCommand.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
338function 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
366module.exports.Handler = IrcCommandsHandler;
367module.exports.Command = IrcCommand;