UNPKG

34.6 kBJavaScriptView Raw
1'use strict';
2
3function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4require("core-js/modules/es.symbol.js");
5require("core-js/modules/es.symbol.description.js");
6require("core-js/modules/es.symbol.iterator.js");
7require("core-js/modules/es.symbol.to-primitive.js");
8require("core-js/modules/es.array.from.js");
9require("core-js/modules/es.array.iterator.js");
10require("core-js/modules/es.date.to-primitive.js");
11require("core-js/modules/es.function.name.js");
12require("core-js/modules/es.number.constructor.js");
13require("core-js/modules/es.object.create.js");
14require("core-js/modules/es.object.get-prototype-of.js");
15require("core-js/modules/es.reflect.construct.js");
16require("core-js/modules/es.string.iterator.js");
17require("core-js/modules/web.dom-collections.iterator.js");
18require("core-js/modules/es.array.concat.js");
19require("core-js/modules/es.array.filter.js");
20require("core-js/modules/es.array.find.js");
21require("core-js/modules/es.array.for-each.js");
22require("core-js/modules/es.array.index-of.js");
23require("core-js/modules/es.array.is-array.js");
24require("core-js/modules/es.array.join.js");
25require("core-js/modules/es.array.slice.js");
26require("core-js/modules/es.array.splice.js");
27require("core-js/modules/es.date.now.js");
28require("core-js/modules/es.date.to-string.js");
29require("core-js/modules/es.function.bind.js");
30require("core-js/modules/es.object.define-property.js");
31require("core-js/modules/es.object.keys.js");
32require("core-js/modules/es.object.set-prototype-of.js");
33require("core-js/modules/es.object.to-string.js");
34require("core-js/modules/es.regexp.exec.js");
35require("core-js/modules/es.regexp.to-string.js");
36require("core-js/modules/es.string.match.js");
37require("core-js/modules/es.string.split.js");
38require("core-js/modules/es.string.trim.js");
39require("core-js/modules/web.dom-collections.for-each.js");
40function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
41function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
42function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
43function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
44function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
45function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
46function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
47function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
48function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
49function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
50function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
51function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
52function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); }
53function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; }
54function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
55function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }
56function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); }
57function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }
58var _ = {
59 extend: require('lodash/extend'),
60 find: require('lodash/find'),
61 each: require('lodash/each'),
62 defer: require('lodash/defer'),
63 bind: require('lodash/bind')
64};
65var EventEmitter = require('eventemitter3');
66var MiddlewareHandler = require('middleware-handler');
67var IrcCommandHandler = require('./commands/').CommandHandler;
68var IrcMessage = require('./ircmessage');
69var Connection = require('./connection');
70var NetworkInfo = require('./networkinfo');
71var User = require('./user');
72var Channel = require('./channel');
73var _require = require('./linebreak'),
74 lineBreak = _require.lineBreak;
75var MessageTags = require('./messagetags');
76var default_transport = null;
77module.exports = /*#__PURE__*/function (_EventEmitter) {
78 function IrcClient(options) {
79 var _this;
80 _classCallCheck(this, IrcClient);
81 _this = _callSuper(this, IrcClient);
82 _this.request_extra_caps = [];
83 _this.options = options || null;
84 _this.createStructure();
85 return _this;
86 }
87 _inherits(IrcClient, _EventEmitter);
88 return _createClass(IrcClient, [{
89 key: "Message",
90 get: function get() {
91 return IrcMessage;
92 }
93 }, {
94 key: "_applyDefaultOptions",
95 value: function _applyDefaultOptions(user_options) {
96 var defaults = {
97 nick: 'ircbot',
98 username: 'ircbot',
99 gecos: 'ircbot',
100 encoding: 'utf8',
101 version: 'node.js irc-framework',
102 enable_chghost: false,
103 enable_setname: false,
104 enable_echomessage: false,
105 auto_reconnect: true,
106 auto_reconnect_max_wait: 300000,
107 auto_reconnect_max_retries: 3,
108 ping_interval: 30,
109 ping_timeout: 120,
110 message_max_length: 350,
111 sasl_disconnect_on_fail: false,
112 transport: default_transport,
113 websocket_protocol: 'text.ircv3.net'
114 };
115 var props = Object.keys(defaults);
116 for (var i = 0; i < props.length; i++) {
117 if (typeof user_options[props[i]] === 'undefined') {
118 user_options[props[i]] = defaults[props[i]];
119 }
120 }
121 return user_options;
122 }
123 }, {
124 key: "createStructure",
125 value: function createStructure() {
126 var client = this;
127
128 // Provides middleware hooks for either raw IRC commands or the easier to use parsed commands
129 client.raw_middleware = new MiddlewareHandler();
130 client.parsed_middleware = new MiddlewareHandler();
131 client.connection = new Connection(client.options);
132 client.network = new NetworkInfo();
133 client.user = new User();
134 client.command_handler = new IrcCommandHandler(client);
135 client.addCommandHandlerListeners();
136
137 // Proxy some connection events onto this client
138 ['connecting', 'reconnecting', 'close', 'socket close', 'socket error', 'raw socket connected', 'debug', 'raw'].forEach(function (event_name) {
139 client.connection.on(event_name, function () {
140 var args = Array.prototype.slice.call(arguments);
141 client.emit.apply(client, [event_name].concat(args));
142 });
143 });
144 client.connection.on('socket connected', function () {
145 client.emit('socket connected');
146 client.registerToNetwork();
147 client.startPingTimeoutTimer();
148 });
149 client.connection.on('connecting', function () {
150 // Reset cap negotiation on a new connection
151 // This prevents stale state if a connection gets closed during CAP negotiation
152 client.network.cap.negotiating = false;
153 client.network.cap.requested = [];
154 client.network.cap.enabled = [];
155 client.network.cap.available.clear();
156 client.command_handler.resetCache();
157 });
158
159 // IRC command routing
160 client.connection.on('message', function (message, raw_line) {
161 client.raw_middleware.handle([message.command, message, raw_line, client], function (err) {
162 if (err) {
163 console.log(err.stack);
164 return;
165 }
166 client.command_handler.dispatch(message);
167 });
168 });
169 client.on('registered', function (event) {
170 // PING is not a valid command until after registration
171 client.startPeriodicPing();
172 });
173 client.on('away', function (event) {
174 if (client.caseCompare(event.nick, client.user.nick)) {
175 client.user.away = true;
176 }
177 });
178 client.on('back', function (event) {
179 if (client.caseCompare(event.nick, client.user.nick)) {
180 client.user.away = false;
181 }
182 });
183
184 // Proxy the command handler events onto the client object, with some added sugar
185 client.proxyIrcEvents();
186 var whox_token = {
187 value: 0,
188 requests: [],
189 next: function next() {
190 if (whox_token.value >= 999) {
191 // whox token is limited to 3 characters
192 whox_token.value = 0;
193 }
194 var token = ++whox_token.value;
195 whox_token.requests.push(token);
196 return token;
197 },
198 validate: function validate(token) {
199 var idx = whox_token.requests.indexOf(token);
200 if (idx !== -1) {
201 whox_token.requests.splice(idx, 1);
202 return true;
203 }
204 return false;
205 }
206 };
207 client.whox_token = whox_token;
208 Object.defineProperty(client, 'connected', {
209 enumerable: true,
210 get: function get() {
211 return client.connection && client.connection.connected;
212 }
213 });
214 }
215 }, {
216 key: "requestCap",
217 value: function requestCap(cap) {
218 this.request_extra_caps = this.request_extra_caps.concat(cap);
219 }
220 }, {
221 key: "use",
222 value: function use(middleware_fn) {
223 middleware_fn(this, this.raw_middleware, this.parsed_middleware);
224 return this;
225 }
226 }, {
227 key: "connect",
228 value: function connect(options) {
229 var client = this;
230
231 // Use the previous options object if we're calling .connect() again
232 if (!options && !client.options) {
233 throw new Error('Options object missing from IrcClient.connect()');
234 } else if (!options) {
235 options = client.options;
236 } else {
237 client.options = options;
238 }
239 client._applyDefaultOptions(options);
240 if (client.connection && client.connection.connected) {
241 client.debugOut('connect() called when already connected');
242 client.connection.end();
243 }
244 client.user.nick = options.nick;
245 client.user.username = options.username;
246 client.user.gecos = options.gecos;
247 client.command_handler.requestExtraCaps(client.request_extra_caps);
248
249 // Everything is setup and prepared, start connecting
250 client.connection.connect(options);
251 }
252
253 // Proxy the command handler events onto the client object, with some added sugar
254 // Events are handled in order:
255 // 1. Received from the command handler
256 // 2. Checked if any extra properties/methods are to be added to the event + re-emitted
257 // 3. Routed through middleware
258 // 4. Emitted from the client instance
259 }, {
260 key: "proxyIrcEvents",
261 value: function proxyIrcEvents() {
262 var client = this;
263 this.command_handler.on('all', function (event_name, event_arg) {
264 client.resetPingTimeoutTimer();
265
266 // Add a reply() function to selected message events
267 if (['privmsg', 'notice', 'action'].indexOf(event_name) > -1) {
268 event_arg.reply = function (message) {
269 var dest = event_arg.target === client.user.nick ? event_arg.nick : event_arg.target;
270 client.say(dest, message);
271 };
272
273 // These events with .reply() function are all messages. Emit it separately
274 // TODO: Should this consider a notice a message?
275 client.command_handler.emit('message', _.extend({
276 type: event_name
277 }, event_arg));
278 }
279 client.parsed_middleware.handle([event_name, event_arg, client], function (err) {
280 if (err) {
281 console.error(err.stack);
282 return;
283 }
284 client.emit(event_name, event_arg);
285 });
286 });
287 }
288 }, {
289 key: "addCommandHandlerListeners",
290 value: function addCommandHandlerListeners() {
291 var client = this;
292 var commands = this.command_handler;
293 commands.on('nick', function (event) {
294 if (client.user.nick === event.nick) {
295 // nicks starting with numbers are reserved for uuids
296 // we dont want to store these as they cannot be used
297 if (event.new_nick.match(/^\d/)) {
298 return;
299 }
300 client.user.nick = event.new_nick;
301 }
302 });
303 commands.on('mode', function (event) {
304 if (client.user.nick === event.target) {
305 event.modes.forEach(function (mode) {
306 client.user.toggleModes(mode.mode);
307 });
308 }
309 });
310 commands.on('wholist', function (event) {
311 var thisUser = _.find(event.users, {
312 nick: client.user.nick
313 });
314 if (thisUser) {
315 client.user.username = thisUser.ident;
316 client.user.host = thisUser.hostname;
317 }
318 });
319 commands.on('registered', function (event) {
320 client.user.nick = event.nick;
321 client.connection.registeredSuccessfully();
322 client.emit('connected', event);
323 });
324 commands.on('displayed host', function (event) {
325 if (client.user.nick === event.nick) {
326 client.user.host = event.hostname;
327 }
328 });
329
330 // Don't let IRC ERROR command kill the node.js process if unhandled
331 commands.on('error', function (event) {});
332 }
333 }, {
334 key: "registerToNetwork",
335 value: function registerToNetwork() {
336 var webirc = this.options.webirc;
337 if (webirc) {
338 var address = String(webirc.address);
339
340 // Prepend a zero to addresses that begin with colon (like ::1)
341 // as colon is using to denote last argument in IRC
342 if (address[0] === ':') {
343 address = '0' + address;
344 }
345 this.raw('WEBIRC', webirc.password, webirc.username, webirc.hostname, address, MessageTags.encode(webirc.options || {}, ' '));
346 }
347 this.raw('CAP LS 302');
348 if (this.options.password) {
349 this.raw('PASS', this.options.password);
350 }
351 this.raw('NICK', this.user.nick);
352 this.raw('USER', this.options.username, 0, '*', this.user.gecos);
353 }
354 }, {
355 key: "startPeriodicPing",
356 value: function startPeriodicPing() {
357 var client = this;
358 var ping_timer = null;
359 if (client.options.ping_interval <= 0) {
360 return;
361 }
362
363 // Constantly ping the server for lag and time syncing functions
364 function pingServer() {
365 client.ping();
366 }
367 function resetPingTimer() {
368 client.connection.clearTimeout(ping_timer);
369 ping_timer = client.connection.setTimeout(pingServer, client.options.ping_interval * 1000);
370 }
371
372 // Browsers have started throttling looped timeout callbacks
373 // using the pong event to set the next ping breaks this loop
374 client.command_handler.on('pong', resetPingTimer);
375
376 // Socket has disconnected, remove 'pong' listener until next 'registered' event
377 client.connection.once('socket close', function () {
378 client.command_handler.off('pong', resetPingTimer);
379 });
380
381 // Start timer
382 resetPingTimer();
383 }
384 }, {
385 key: "startPingTimeoutTimer",
386 value: function startPingTimeoutTimer() {
387 var client = this;
388 var timeout_timer = null;
389 if (client.options.ping_timeout <= 0) {
390 return;
391 }
392
393 // Data from the server was detected so restart the timeout
394 function resetPingTimeoutTimer() {
395 client.connection.clearTimeout(timeout_timer);
396 timeout_timer = client.connection.setTimeout(pingTimeout, client.options.ping_timeout * 1000);
397 }
398 function pingTimeout() {
399 client.debugOut('Ping timeout (' + client.options.ping_timeout + ' seconds)');
400 client.emit('ping timeout');
401 var end_msg = client.rawString('QUIT', 'Ping timeout (' + client.options.ping_timeout + ' seconds)');
402 client.connection.end(end_msg, true);
403 }
404 this.resetPingTimeoutTimer = resetPingTimeoutTimer;
405 this.resetPingTimeoutTimer();
406 }
407
408 // Gets overridden with a function in startPeriodicPing(). Only set here for completeness.
409 }, {
410 key: "resetPingTimeoutTimer",
411 value: function resetPingTimeoutTimer() {}
412 }, {
413 key: "debugOut",
414 value: function debugOut(out) {
415 this.emit('debug', 'Client ' + out);
416 }
417
418 /**
419 * Client API
420 */
421 }, {
422 key: "raw",
423 value: function raw(input) {
424 if (input instanceof IrcMessage) {
425 this.connection.write(input.to1459());
426 } else {
427 this.connection.write(this.rawString.apply(this, arguments));
428 }
429 }
430 }, {
431 key: "rawString",
432 value: function rawString(input) {
433 var args;
434 if (input.constructor === Array) {
435 args = input;
436 } else {
437 args = Array.prototype.slice.call(arguments, 0);
438 }
439 args = args.filter(function (item) {
440 return typeof item === 'number' || typeof item === 'string';
441 });
442 if (args.length > 1 && args[args.length - 1].match(/^:|\s/)) {
443 args[args.length - 1] = ':' + args[args.length - 1];
444 }
445 return args.join(' ');
446 }
447 }, {
448 key: "quit",
449 value: function quit(message) {
450 this.connection.end(this.rawString('QUIT', message));
451 }
452 }, {
453 key: "ping",
454 value: function ping(message) {
455 this.raw('PING', message || Date.now().toString());
456 }
457 }, {
458 key: "changeNick",
459 value: function changeNick(nick) {
460 this.raw('NICK', nick);
461 }
462 }, {
463 key: "sendMessage",
464 value: function sendMessage(commandName, target, message, tags) {
465 var _this2 = this;
466 var lines = message.split(/\r\n|\n|\r/).filter(function (i) {
467 return i;
468 });
469 lines.forEach(function (line) {
470 // Maximum length of target + message we can send to the IRC server is 500 characters
471 // but we need to leave extra room for the sender prefix so the entire message can
472 // be sent from the IRCd to the target without being truncated.
473 var blocks = _toConsumableArray(lineBreak(line, {
474 bytes: _this2.options.message_max_length,
475 allowBreakingWords: true,
476 allowBreakingGraphemes: true
477 }));
478 blocks.forEach(function (block) {
479 if (tags && Object.keys(tags).length) {
480 var msg = new IrcMessage(commandName, target, block);
481 msg.tags = tags;
482 _this2.raw(msg);
483 } else {
484 _this2.raw(commandName, target, block);
485 }
486 });
487 });
488 }
489 }, {
490 key: "say",
491 value: function say(target, message, tags) {
492 return this.sendMessage('PRIVMSG', target, message, tags);
493 }
494 }, {
495 key: "notice",
496 value: function notice(target, message, tags) {
497 return this.sendMessage('NOTICE', target, message, tags);
498 }
499 }, {
500 key: "tagmsg",
501 value: function tagmsg(target) {
502 var tags = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
503 var msg = new IrcMessage('TAGMSG', target);
504 msg.tags = tags;
505 this.raw(msg);
506 }
507 }, {
508 key: "join",
509 value: function join(channel, key) {
510 var raw = ['JOIN', channel];
511 if (key) {
512 raw.push(key);
513 }
514 this.raw(raw);
515 }
516 }, {
517 key: "part",
518 value: function part(channel, message) {
519 var raw = ['PART', channel];
520 if (message) {
521 raw.push(message);
522 }
523 this.raw(raw);
524 }
525 }, {
526 key: "mode",
527 value: function mode(channel, _mode, extra_args) {
528 var raw = ['MODE', channel, _mode];
529 if (extra_args) {
530 if (Array.isArray(extra_args)) {
531 raw = raw.concat(extra_args);
532 } else {
533 raw.push(extra_args);
534 }
535 }
536 this.raw(raw);
537 }
538 }, {
539 key: "inviteList",
540 value: function inviteList(channel, cb) {
541 var client = this;
542 var invex = this.network.supports('INVEX');
543 var mode = 'I';
544 if (typeof invex === 'string' && invex) {
545 mode = invex;
546 }
547 function onInviteList(event) {
548 if (client.caseCompare(event.channel, channel)) {
549 unbindEvents();
550 if (typeof cb === 'function') {
551 cb(event);
552 }
553 }
554 }
555 function onInviteListErr(event) {
556 if (event.error === 'chanop_privs_needed') {
557 unbindEvents();
558 if (typeof cb === 'function') {
559 cb(null);
560 }
561 }
562 }
563 function bindEvents() {
564 client.on('inviteList', onInviteList);
565 client.on('irc error', onInviteListErr);
566 }
567 function unbindEvents() {
568 client.removeListener('inviteList', onInviteList);
569 client.removeListener('irc error', onInviteListErr);
570 }
571 bindEvents();
572 this.raw(['MODE', channel, mode]);
573 }
574 }, {
575 key: "invite",
576 value: function invite(channel, nick) {
577 var raw = ['INVITE', nick, channel];
578 this.raw(raw);
579 }
580 }, {
581 key: "addInvite",
582 value: function addInvite(channel, mask) {
583 var mode = 'I';
584 var invex = this.network.supports('INVEX');
585 if (typeof invex === 'string') {
586 mode = invex;
587 }
588 var raw = ['MODE', channel, '+' + mode, mask];
589 this.raw(raw);
590 }
591 }, {
592 key: "removeInvite",
593 value: function removeInvite(channel, mask) {
594 var mode = 'I';
595 var invex = this.network.supports('INVEX');
596 if (typeof invex === 'string') {
597 mode = invex;
598 }
599 var raw = ['MODE', channel, '-' + mode, mask];
600 this.raw(raw);
601 }
602 }, {
603 key: "banlist",
604 value: function banlist(channel, cb) {
605 var client = this;
606 var raw = ['MODE', channel, 'b'];
607 this.on('banlist', function onBanlist(event) {
608 if (client.caseCompare(event.channel, channel)) {
609 client.removeListener('banlist', onBanlist);
610 if (typeof cb === 'function') {
611 cb(event);
612 }
613 }
614 });
615 this.raw(raw);
616 }
617 }, {
618 key: "ban",
619 value: function ban(channel, mask) {
620 var raw = ['MODE', channel, '+b', mask];
621 this.raw(raw);
622 }
623 }, {
624 key: "unban",
625 value: function unban(channel, mask) {
626 var raw = ['MODE', channel, '-b', mask];
627 this.raw(raw);
628 }
629 }, {
630 key: "setTopic",
631 value: function setTopic(channel, newTopic) {
632 if (!newTopic || !newTopic.trim()) {
633 // If newTopic is undefined or empty, remove the existing topic
634 // this check is to prevent unexpectedly requesting the current topic
635 // when trying to clear the topic
636 this.clearTopic(channel);
637 return;
638 }
639 this.raw('TOPIC', channel, newTopic);
640 }
641 }, {
642 key: "clearTopic",
643 value: function clearTopic(channel) {
644 // The trailing `:` is required otherwise it would be requesting the topic
645 // and not clearing it
646 this.raw("TOPIC ".concat(channel, " :"));
647 }
648 }, {
649 key: "ctcpRequest",
650 value: function ctcpRequest(target, type /*, paramN */) {
651 var params = Array.prototype.slice.call(arguments, 1);
652
653 // make sure the CTCP type is uppercased
654 params[0] = params[0].toUpperCase();
655 this.raw('PRIVMSG', target, String.fromCharCode(1) + params.join(' ') + String.fromCharCode(1));
656 }
657 }, {
658 key: "ctcpResponse",
659 value: function ctcpResponse(target, type /*, paramN */) {
660 var params = Array.prototype.slice.call(arguments, 1);
661
662 // make sure the CTCP type is uppercased
663 params[0] = params[0].toUpperCase();
664 this.raw('NOTICE', target, String.fromCharCode(1) + params.join(' ') + String.fromCharCode(1));
665 }
666 }, {
667 key: "action",
668 value: function action(target, message) {
669 var that = this;
670
671 // Maximum length of target + message we can send to the IRC server is 500 characters
672 // but we need to leave extra room for the sender prefix so the entire message can
673 // be sent from the IRCd to the target without being truncated.
674
675 // The block length here is the max, but without the non-content characters:
676 // the command name, the space, and the two SOH chars
677
678 var commandName = 'ACTION';
679 var blockLength = this.options.message_max_length - (commandName.length + 3);
680 var blocks = _toConsumableArray(lineBreak(message, {
681 bytes: blockLength,
682 allowBreakingWords: true,
683 allowBreakingGraphemes: true
684 }));
685 blocks.forEach(function (block) {
686 that.ctcpRequest(target, commandName, block);
687 });
688 return blocks;
689 }
690 }, {
691 key: "whois",
692 value: function whois(target, _cb) {
693 var client = this;
694 var cb;
695 var irc_args = ['WHOIS'];
696
697 // Support whois(target, arg1, arg2, argN, cb)
698 _.each(arguments, function (arg) {
699 if (typeof arg === 'function') {
700 cb = arg;
701 } else {
702 irc_args.push(arg);
703 }
704 });
705 this.on('whois', function onWhois(event) {
706 if (client.caseCompare(event.nick, target)) {
707 client.removeListener('whois', onWhois);
708 if (typeof cb === 'function') {
709 cb(event);
710 }
711 }
712 });
713 this.raw(irc_args);
714 }
715 }, {
716 key: "whowas",
717 value: function whowas(target, _cb) {
718 var client = this;
719 var cb;
720 var irc_args = ['WHOWAS'];
721
722 // Support whowas(target, arg1, arg2, argN, cb)
723 _.each(arguments, function (arg) {
724 if (typeof arg === 'function') {
725 cb = arg;
726 } else {
727 irc_args.push(arg);
728 }
729 });
730 this.on('whowas', function onWhowas(event) {
731 if (client.caseCompare(event.nick, target)) {
732 client.removeListener('whowas', onWhowas);
733 if (typeof cb === 'function') {
734 cb(event);
735 }
736 }
737 });
738 this.raw(irc_args);
739 }
740
741 /**
742 * WHO requests are queued up to run serially.
743 * This is mostly because networks will only reply serially and it makes
744 * it easier to include the correct replies to callbacks
745 */
746 }, {
747 key: "who",
748 value: function who(target, cb) {
749 if (!this.who_queue) {
750 this.who_queue = [];
751 }
752 this.who_queue.push([target, cb]);
753 this.processNextWhoQueue();
754 }
755 }, {
756 key: "monitorlist",
757 value: function monitorlist(cb) {
758 var client = this;
759 var raw = ['MONITOR', 'L'];
760 this.on('monitorList', function onMonitorlist(event) {
761 client.removeListener('monitorList', onMonitorlist);
762 if (typeof cb === 'function') {
763 cb(event);
764 }
765 });
766 this.raw(raw);
767 }
768 }, {
769 key: "addMonitor",
770 value: function addMonitor(target) {
771 var raw = ['MONITOR', '+', target];
772 this.raw(raw);
773 }
774 }, {
775 key: "removeMonitor",
776 value: function removeMonitor(target) {
777 var raw = ['MONITOR', '-', target];
778 this.raw(raw);
779 }
780 }, {
781 key: "queryMonitor",
782 value: function queryMonitor() {
783 var raw = ['MONITOR', 'S'];
784 this.raw(raw);
785 }
786 }, {
787 key: "clearMonitor",
788 value: function clearMonitor() {
789 var raw = ['MONITOR', 'C'];
790 this.raw(raw);
791 }
792 }, {
793 key: "processNextWhoQueue",
794 value: function processNextWhoQueue() {
795 var client = this;
796
797 // No items in the queue or the queue is already running?
798 if (client.who_queue.length === 0 || client.who_queue.is_running) {
799 return;
800 }
801 client.who_queue.is_running = true;
802 var this_who = client.who_queue.shift();
803 var target = this_who[0];
804 var cb = this_who[1];
805 if (!target || typeof target !== 'string') {
806 if (typeof cb === 'function') {
807 _.defer(cb, {
808 target: target,
809 users: []
810 });
811 }
812
813 // Start the next queued WHO request
814 client.who_queue.is_running = false;
815 _.defer(_.bind(client.processNextWhoQueue, client));
816 return;
817 }
818 client.on('wholist', function onWho(event) {
819 client.removeListener('wholist', onWho);
820
821 // Start the next queued WHO request
822 client.who_queue.is_running = false;
823 _.defer(_.bind(client.processNextWhoQueue, client));
824 if (typeof cb === 'function') {
825 cb({
826 target: target,
827 users: event.users
828 });
829 }
830 });
831 if (client.network.supports('whox')) {
832 var token = client.whox_token.next();
833 client.raw('WHO', target, "%tcuhsnfdaor,".concat(token));
834 } else {
835 client.raw('WHO', target);
836 }
837 }
838
839 /**
840 * Explicitely start a channel list, avoiding potential issues with broken IRC servers not sending RPL_LISTSTART
841 */
842 }, {
843 key: "list",
844 value: function list(/* paramN */
845 ) {
846 var args = Array.prototype.slice.call(arguments);
847 this.command_handler.cache('chanlist').channels = [];
848 args.unshift('LIST');
849 this.raw(args);
850 }
851 }, {
852 key: "channel",
853 value: function channel(channel_name, key) {
854 return new Channel(this, channel_name, key);
855 }
856 }, {
857 key: "match",
858 value: function match(match_regex, cb, message_type) {
859 var client = this;
860 var onMessage = function onMessage(event) {
861 if (event.message.match(match_regex)) {
862 cb(event);
863 }
864 };
865 this.on(message_type || 'message', onMessage);
866 return {
867 stop: function stop() {
868 client.removeListener(message_type || 'message', onMessage);
869 }
870 };
871 }
872 }, {
873 key: "matchNotice",
874 value: function matchNotice(match_regex, cb) {
875 return this.match(match_regex, cb, 'notice');
876 }
877 }, {
878 key: "matchMessage",
879 value: function matchMessage(match_regex, cb) {
880 return this.match(match_regex, cb, 'privmsg');
881 }
882 }, {
883 key: "matchAction",
884 value: function matchAction(match_regex, cb) {
885 return this.match(match_regex, cb, 'action');
886 }
887 }, {
888 key: "caseCompare",
889 value: function caseCompare(string1, string2) {
890 var length = string1.length;
891 if (length !== string2.length) {
892 return false;
893 }
894 var upperBound = this._getCaseMappingUpperAsciiBound();
895 for (var i = 0; i < length; i++) {
896 var charCode1 = string1.charCodeAt(i);
897 var charCode2 = string2.charCodeAt(i);
898 if (charCode1 >= 65 && charCode1 <= upperBound) {
899 charCode1 += 32;
900 }
901 if (charCode2 >= 65 && charCode2 <= upperBound) {
902 charCode2 += 32;
903 }
904 if (charCode1 !== charCode2) {
905 return false;
906 }
907 }
908 return true;
909 }
910 }, {
911 key: "caseLower",
912 value: function caseLower(string) {
913 var upperBound = this._getCaseMappingUpperAsciiBound();
914 var result = '';
915 for (var i = 0; i < string.length; i++) {
916 var charCode = string.charCodeAt(i);
917
918 // ASCII character from 'A' to upper bound defined above
919 if (charCode >= 65 && charCode <= upperBound) {
920 // All the relevant uppercase characters are exactly
921 // 32 bytes apart from lowercase ones, so we simply add 32
922 // and get the equivalent character in lower case
923 result += String.fromCharCode(charCode + 32);
924 } else {
925 result += string[i];
926 }
927 }
928 return result;
929 }
930 }, {
931 key: "caseUpper",
932 value: function caseUpper(string) {
933 var upperBound = this._getCaseMappingUpperAsciiBound() + 32;
934 var result = '';
935 for (var i = 0; i < string.length; i++) {
936 var charCode = string.charCodeAt(i);
937
938 // ASCII character from 'a' to upper bound defined above
939 if (charCode >= 97 && charCode <= upperBound) {
940 // All the relevant lowercase characters are exactly
941 // 32 bytes apart from lowercase ones, so we simply subtract 32
942 // and get the equivalent character in upper case
943 result += String.fromCharCode(charCode - 32);
944 } else {
945 result += string[i];
946 }
947 }
948 return result;
949 }
950 }, {
951 key: "_getCaseMappingUpperAsciiBound",
952 value: function _getCaseMappingUpperAsciiBound() {
953 if (this.network.options.CASEMAPPING === 'ascii') {
954 return 90; // 'Z'
955 } else if (this.network.options.CASEMAPPING === 'strict-rfc1459') {
956 return 93; // ']'
957 }
958 return 94; // '^' - default casemapping=rfc1459
959 }
960 }], [{
961 key: "setDefaultTransport",
962 value: function setDefaultTransport(transport) {
963 default_transport = transport;
964 }
965 }]);
966}(EventEmitter);
\No newline at end of file