squelch-client
Version:
An IRC client for Node.js
494 lines (456 loc) • 16.9 kB
JavaScript
// Generated by CoffeeScript 1.10.0
(function() {
var Client, Emitter, Promise, color, debug, debugError, defaultOpt, getReplyCode, getReplyName, getSender, ircMsg, net, path, ref, streamMap, tls,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty,
slice = [].slice;
net = require('net');
tls = require('tls');
path = require('path');
Emitter = require('@rahatarmanahmed/event-kit').Emitter;
ircMsg = require('irc-message');
Promise = require('bluebird');
streamMap = require('through2-map');
color = require('irc-colors');
debug = require('debug')('squelch-client');
debugError = require('debug')('squelch-client:error');
getSender = require('./util').getSender;
ref = require('./replies'), getReplyCode = ref.getReplyCode, getReplyName = ref.getReplyName;
defaultOpt = {
port: 6667,
nick: 'NodeIRCClient',
username: 'NodeIRCClient',
realname: 'NodeIRCClient',
channels: [],
autoNickChange: true,
autoRejoin: false,
autoConnect: true,
autoSplitMessage: true,
messageDelay: 1000,
stripColors: true,
stripStyles: true,
autoReconnect: true,
autoReconnectTries: 3,
reconnectDelay: 5000,
ssl: false,
selfSigned: false,
certificateExpired: false,
timeout: 120000,
triggerEventsForOwnMessages: false
};
Client = (function(superClass) {
extend(Client, superClass);
function Client(opt) {
this.dequeue = bind(this.dequeue, this);
var key, value;
Client.__super__.constructor.call(this);
this._ = {
internalEmitter: new Emitter(),
numRetries: 0,
connected: false,
connecting: false,
disconnecting: false,
messageQueue: [],
iSupport: {
CHANTYPES: '&#'
},
greeting: {},
prefix: {
o: '@',
v: '+'
},
chanmodes: ['beI', 'k', 'l', 'aimnpqsrt']
};
if (opt == null) {
throw new Error('No options argument given.');
}
if (typeof opt === 'string') {
opt = require(path.resolve(opt));
}
this.opt = {};
for (key in defaultOpt) {
value = defaultOpt[key];
this.opt[key] = value;
}
for (key in opt) {
value = opt[key];
this.opt[key] = value;
}
if (this.opt.server == null) {
throw new Error('No server specified.');
}
this.use(require('./plugins/core/msg')());
this.use(require('./plugins/core/notice')());
this.use(require('./plugins/core/nick')());
this.use(require('./plugins/core/join')());
this.use(require('./plugins/core/part')());
this.use(require('./plugins/core/kick')());
this.use(require('./plugins/core/invite')());
this.use(require('./plugins/core/mode')());
this.use(require('./plugins/core/motd')());
this.use(require('./plugins/channel')());
if (this.opt.autoConnect) {
this.connect();
}
}
Client.prototype.emit = function() {
var args, ref1;
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
if (args[0] !== 'error') {
(ref1 = this._.internalEmitter).emit.apply(ref1, args);
}
return Client.__super__.emit.apply(this, args);
};
Client.prototype.cbNoop = function(err) {
if (err) {
return debugError(err);
}
};
Client.prototype.connect = function(tries, cb) {
if (tries == null) {
tries = 1;
}
return new Promise((function(_this) {
return function(resolve, reject) {
var errorListener, onConnect, tlsOptions;
_this._.connecting = true;
_this.emit('connecting', {
port: _this.opt.port,
server: _this.opt.server
});
debug('Connecting...');
if (tries instanceof Function) {
cb = tries;
tries = 1;
}
tries--;
errorListener = function(err) {
_this._.connecting = false;
debugError('Unable to connect.');
debugError(err);
if (tries > 0 || tries === -1) {
_this.emit('reconnecting', {
port: _this.opt.port,
server: _this.opt.server,
delay: _this.opt.reconnectDelay,
triesLeft: tries
});
debugError("Reconnecting in " + (_this.opt.reconnectDelay / 1000) + " seconds... (" + tries + " remaining tries)");
return setTimeout(function() {
return _this.connect(tries, cb);
}, _this.opt.reconnectDelay);
} else {
return reject(err);
}
};
onConnect = function() {
var stream;
_this.conn.setEncoding('utf8');
_this.conn.removeListener('error', errorListener);
_this._.internalEmitter.once('connect', function(arg) {
var nick;
nick = arg.nick;
return resolve({
nick: nick
});
});
debug('Connected');
stream = _this.conn;
if (_this.opt.stripColors) {
stream = stream.pipe(streamMap({
wantStrings: true
}, color.stripColors));
}
if (_this.opt.stripStyles) {
stream = stream.pipe(streamMap({
wantStrings: true
}, color.stripStyle));
}
stream = stream.pipe(ircMsg.createStream({
parsePrefix: true
}));
stream.on('data', function(data) {
if (_this._.timeout) {
clearTimeout(_this._.timeout);
}
_this._.timeout = setTimeout(function() {
var pingTime;
_this.raw('PING :ruthere');
pingTime = new Date().getTime();
return _this._.timeout = setTimeout(function() {
var seconds;
seconds = (new Date().getTime() - pingTime) / 1000;
_this.emit('timeout', {
seconds: seconds
});
return _this.handleReply(ircMsg.parse("ERROR :Ping Timeout (" + seconds + " seconds)"));
}, _this.opt.timeout);
}, _this.opt.timeout);
if (data != null) {
_this.handleReply(data);
return _this.emit('raw', data);
}
});
_this.conn.on('error', function(e) {
debugError('Disconnected by network error.');
_this.handleReply(ircMsg.parse("ERROR :Connection error (" + e.message + ")"));
if (_this.opt.autoReconnect && _this.opt.autoReconnectTries > 0) {
debug("Reconnecting in " + (_this.opt.reconnectDelay / 1000) + " seconds... (" + _this.opt.autoReconnectTries + " remaining tries)");
return setTimeout(function() {
return _this.connect(_this.opt.autoReconnectTries);
}, _this.opt.reconnectDelay);
}
});
_this.emit('connection-established', {
port: _this.opt.port,
server: _this.opt.server
});
if (_this.opt.password != null) {
_this.raw("PASS " + _this.opt.password, false);
}
_this.raw("NICK " + _this.opt.nick, false);
return _this.raw("USER " + _this.opt.username + " 8 * :" + _this.opt.realname, false);
};
if (_this.opt.ssl) {
tlsOptions = _this.opt.ssl instanceof Object ? _this.opt.ssl : {};
if (_this.opt.selfSigned) {
tlsOptions.rejectUnauthorized = false;
}
_this.conn = tls.connect(_this.opt.port, _this.opt.server, tlsOptions, function() {
if (!_this.conn.authorized) {
if (_this.opt.selfSigned && (_this.conn.authorizationError === 'DEPTH_ZERO_SELF_SIGNED_CERT' || _this.conn.authorizationError === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' || _this.conn.authorizationError === 'SELF_SIGNED_CERT_IN_CHAIN')) {
debug('Connecting to server with self signed certificate');
} else if (_this.opt.certificateExpired && _this.conn.authorizationError === 'CERT_HAS_EXPIRED') {
debug('Connecting to server with expired certificate');
} else {
debug("Authorization error: " + _this.conn.authorizationError);
return;
}
}
return onConnect();
});
} else {
_this.conn = net.connect(_this.opt.port, _this.opt.server, onConnect);
}
return _this.conn.once('error', errorListener);
};
})(this)).nodeify(cb || this.cbNoop);
};
Client.prototype.disconnect = function(reason, cb) {
return new Promise((function(_this) {
return function(resolve) {
if (reason instanceof Function) {
cb = reason;
reason = void 0;
}
_this._.disconnecting = true;
if (reason != null) {
_this.raw("QUIT :" + reason, false);
} else {
_this.raw('QUIT', false);
}
return _this._.internalEmitter.once('disconnect', function() {
return resolve();
});
};
})(this)).nodeify(cb || this.cbNoop);
};
Client.prototype.forceQuit = function(reason) {
if (this.isConnected()) {
this.raw('QUIT' + (reason != null ? " :" + reason : ''), false);
}
this._.disconnecting = true;
return this.handleReply(ircMsg.parse('ERROR :Force Quit' + (reason ? " (" + reason + ")" : '')));
};
Client.prototype.raw = function(msg, delay) {
if (delay == null) {
delay = true;
}
if (msg == null) {
throw new Error();
}
if (this.conn == null) {
return;
}
if (!delay || this.opt.messageDelay === 0) {
debug("-> " + msg);
return this.conn.write(msg + '\r\n');
} else {
if (this._.messageQueue.length === 0) {
setTimeout(this.dequeue, 0);
}
return this._.messageQueue.push(msg);
}
};
Client.prototype.dequeue = function() {
var msg;
msg = this._.messageQueue.shift();
if (this.conn != null) {
debug("-> " + msg);
this.conn.write(msg + '\r\n');
}
if (this._.messageQueue.length !== 0) {
return this._.messageQueueTimeout = setTimeout(this.dequeue, this.opt.messageDelay);
}
};
Client.prototype.splitText = function(command, msg, extra) {
var i, limit;
if (extra == null) {
extra = 0;
}
limit = 512 - 3 - this._.nick.length - 9 - 65 - command.length - 2 - 2 - extra;
return (function() {
var j, ref1, ref2, results;
results = [];
for (i = j = 0, ref1 = msg.length, ref2 = limit; ref2 > 0 ? j <= ref1 : j >= ref1; i = j += ref2) {
results.push(msg.slice(i, i + limit));
}
return results;
})();
};
Client.prototype.use = function(plugin) {
plugin(this);
return this;
};
Client.prototype.isConnected = function() {
return this._.connected;
};
Client.prototype.isConnecting = function() {
return this._.connecting;
};
Client.prototype.messageDelay = function(value) {
if (value == null) {
return this.opt.messageDelay;
}
return this.opt.messageDelay = value;
};
Client.prototype.autoSplitMessage = function(enabled) {
if (enabled == null) {
return this.opt.autoSplitMessage;
}
return this.opt.autoSplitMessage = enabled;
};
Client.prototype.autoRejoin = function(enabled) {
if (enabled == null) {
return this.opt.autoRejoin;
}
return this.opt.autoRejoin = enabled;
};
Client.prototype.triggerEventsForOwnMessages = function(enabled) {
if (enabled == null) {
return this.opt.triggerEventsForOwnMessages;
}
return this.opt.triggerEventsForOwnMessages = enabled;
};
Client.prototype.isChannel = function(chan) {
if (chan == null) {
return false;
}
return this._.iSupport['CHANTYPES'].indexOf(chan[0]) !== -1;
};
Client.prototype.modeToPrefix = function(mode) {
return this._.prefix[mode];
};
Client.prototype.handleReply = function(parsedReply) {
var i, item, j, k, len, match, nick, reason, ref1, ref2, results, split;
if (parsedReply == null) {
return;
}
debug('<-' + parsedReply.raw);
switch (parsedReply.command) {
case 'QUIT':
nick = getSender(parsedReply);
reason = parsedReply.params[0];
return this.emit('quit', {
nick: nick,
reason: reason
});
case 'PING':
return this.raw("PONG :" + parsedReply.params[0], false);
case 'ERROR':
this.conn.destroy();
this._.channels = {};
this._.messageQueue = [];
clearTimeout(this._.messageQueueTimeout);
clearTimeout(this._.timeout);
this.conn = null;
this._.connecting = false;
this._.connected = false;
if (!this._.disconnecting) {
this.emit('error', parsedReply);
}
debug('Disconnected from server');
if (!this._.disconnecting && this.opt.autoReconnect && this.opt.autoReconnectTries > 0) {
debug("Reconnecting in " + (this.opt.reconnectDelay / 1000) + " seconds... (" + this.opt.autoReconnectTries + " remaining tries)");
setTimeout((function(_this) {
return function() {
return _this.connect(_this.opt.autoReconnectTries);
};
})(this), this.opt.reconnectDelay);
}
this._.disconnecting = false;
return this.emit('disconnect', {
reason: parsedReply.params[0]
});
case getReplyCode('RPL_WELCOME'):
this._.connecting = false;
this._.connected = true;
this._.nick = parsedReply.params[0];
this.emit('connect', {
nick: this._.nick,
server: this.opt.server,
port: this.opt.port
});
return this.join(this.opt.channels);
case getReplyCode('RPL_YOURHOST'):
return this._.greeting.yourHost = parsedReply.params[1];
case getReplyCode('RPL_CREATED'):
return this._.greeting.created = parsedReply.params[1];
case getReplyCode('RPL_MYINFO'):
return this._.greeting.myInfo = parsedReply.params.slice(1).join(' ');
case getReplyCode('RPL_ISUPPORT'):
ref1 = parsedReply.params.slice(1);
results = [];
for (j = 0, len = ref1.length; j < len; j++) {
item = ref1[j];
if (item.indexOf(' ') !== -1) {
continue;
}
split = item.split('=');
if (split.length === 1) {
this._.iSupport[item] = true;
} else {
this._.iSupport[split[0]] = split[1];
}
switch (split[0]) {
case 'PREFIX':
match = /\((.+)\)(.+)/.exec(split[1]);
this._.prefix = {};
for (i = k = 0, ref2 = match[1].length; 0 <= ref2 ? k < ref2 : k > ref2; i = 0 <= ref2 ? ++k : --k) {
this._.prefix[match[1][i]] = match[2][i];
}
this._.reversePrefix = {};
results.push((function() {
var l, ref3, results1;
results1 = [];
for (i = l = 0, ref3 = match[1].length; 0 <= ref3 ? l < ref3 : l > ref3; i = 0 <= ref3 ? ++l : --l) {
results1.push(this._.reversePrefix[match[2][i]] = match[1][i]);
}
return results1;
}).call(this));
break;
case 'CHANMODES':
results.push(this._.chanmodes = split[1].split(','));
break;
default:
results.push(void 0);
}
}
return results;
}
};
return Client;
})(Emitter);
module.exports = Client;
}).call(this);