teeworlds-econ
Version:
Teeworlds external console
372 lines (338 loc) • 12.9 kB
JavaScript
var EconConnectionError, EconError, EventEmitter, Socket, TeeworldsEcon, Transaction, debug, escape, handlers, parseStatus, parseTextValue, ref, ref1, split, splitText,
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;
Socket = require('net').Socket;
EventEmitter = require('events').EventEmitter;
ref = require('./errors'), EconError = ref.EconError, EconConnectionError = ref.EconConnectionError;
ref1 = require('./utils'), split = ref1.split, splitText = ref1.splitText, escape = ref1.escape, parseStatus = ref1.parseStatus, parseTextValue = ref1.parseTextValue, debug = ref1.debug;
Transaction = require('./transaction');
handlers = require('./handlers');
TeeworldsEcon = (function(superClass) {
extend(TeeworldsEcon, superClass);
TeeworldsEcon.prototype.name = '';
TeeworldsEcon.prototype.clients = [];
function TeeworldsEcon() {
var args, host, password, port, ref2;
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
this.disconnect = bind(this.disconnect, this);
this.reconnect = bind(this.reconnect, this);
this.handleMessage = bind(this.handleMessage, this);
TeeworldsEcon.__super__.constructor.apply(this, arguments);
if (typeof args[0] === 'object') {
ref2 = args[0], host = ref2.host, port = ref2.port, password = ref2.password;
} else {
host = args[0], port = args[1], password = args[2];
}
if (!host) {
throw new EconError('Undefined host');
}
if (!port) {
throw new EconError('Undefined port');
}
if (!password) {
throw new EconError('Undefined password');
}
this.server = {
host: host,
port: port,
password: password
};
this.connection = null;
this.connected = false;
this.retryDelay = null;
this.retryCount = null;
this.retryTimer = null;
this.resetTransactions();
this.resetHandlers();
this.addHandler(handlers.handleEnterMessage);
this.addHandler(handlers.handleLeaveMessage);
this.addHandler(handlers.handlePickupMessage);
this.addHandler(handlers.handleChatMessage);
this.addHandler(handlers.handleKillMessage);
this.addHandler(handlers.handleFlagGrabMessage);
this.addHandler(handlers.handleFlagReturnMessage);
this.addHandler(handlers.handleCaptureMessage);
this.addHandler(handlers.handleNetBanMessage);
}
TeeworldsEcon.prototype.exec = function(command, arg) {
var err, promise, ref2, timeout, transaction;
timeout = (ref2 = (arg != null ? arg : {}).timeout) != null ? ref2 : 30000;
if (!this.isConnected(false)) {
err = new EconConnectionError('Not connected');
debug.connection('%s:%s econ error: %s', this.server.host, this.server.port, err.message);
this.emit('error', err);
return Promise.reject(err);
}
transaction = new Transaction(command, {
timeout: timeout
});
promise = new Promise((function(_this) {
return function(resolve, reject) {
transaction.on('end', function(arg1) {
var id, result;
id = arg1.id, result = arg1.result;
resolve(result);
debug.connection('%s:%s %s transaction complete', _this.server.host, _this.server.port, id);
transaction.removeAllListeners('end');
transaction.removeAllListeners('error');
return _this.removeTransaction(transaction);
});
return transaction.on('error', function(err) {
reject(err);
debug.connection('%s:%s transaction error: %s', _this.server.host, _this.server.port, err.message);
transaction.removeAllListeners('end');
transaction.removeAllListeners('error');
return _this.removeTransaction(transaction);
});
};
})(this));
this.scheduleTransaction(transaction);
return promise;
};
TeeworldsEcon.prototype.say = function(message, limit) {
var chunk, chunks, i, len, results;
if (limit == null) {
limit = 256;
}
if (limit > 256) {
limit = 256;
}
chunks = message.split('\n').map(escape).map(function(line) {
return splitText(line, limit);
}).reduce(function(a, b) {
return a.concat(b);
});
results = [];
for (i = 0, len = chunks.length; i < len; i++) {
chunk = chunks[i];
results.push(this.exec("say \"" + chunk + "\""));
}
return results;
};
TeeworldsEcon.prototype.status = function() {
return this.exec('status').then(parseStatus);
};
TeeworldsEcon.prototype.svMotd = function(message) {
if (message) {
return this.exec("sv_motd \"" + (escape(message)) + "\"");
}
return this.exec('sv_motd').then(parseTextValue);
};
TeeworldsEcon.prototype.svName = function(name) {
if (name) {
return this.exec("sv_name \"" + (escape(name)) + "\"");
}
return this.exec('sv_name').then(parseTextValue);
};
TeeworldsEcon.prototype.write = function(message) {
if (!(this.connection && this.connection.writable)) {
return;
}
debug.connection('writing to %s:%s econ: %s', this.server.host, this.server.port, message);
return this.connection.write(message + '\n');
};
TeeworldsEcon.prototype.addHandler = function(handler) {
return this.handlers.push(handler);
};
TeeworldsEcon.prototype.removeHandler = function(handler) {
var index;
index = this.handlers.find(function(item) {
return handler === item;
});
if (index !== -1) {
return this.handlers.splice(index, 1);
}
};
TeeworldsEcon.prototype.resetHandlers = function() {
return this.handlers = [];
};
TeeworldsEcon.prototype.scheduleTransaction = function(transaction) {
this.transactions.push(transaction);
return this.write(transaction.getCommand());
};
TeeworldsEcon.prototype.removeTransaction = function(transaction) {
var index;
index = this.transactions.find(function(item) {
return transaction === item;
});
if (index !== -1) {
return this.transactions.splice(index, 1);
}
};
TeeworldsEcon.prototype.resetTransactions = function() {
return this.transactions = [];
};
TeeworldsEcon.prototype.handleMessage = function(message) {
var err, handler, i, len, ref2, result, results;
debug.connection('reading from %s:%s econ: %s', this.server.host, this.server.port, message);
(function(_this) {
return (function(message) {
var cid, client, matches;
if (matches = /^\[server\]: player has entered the game. ClientID=([0-9a-f]+) addr=(.+?):([0-9]+)$/.exec(message)) {
cid = parseInt(matches[1], 16);
client = matches[2] + ":" + matches[3];
debug.connection('new client (%s) with ip:port %s on %s:%s ', cid, client, _this.server.host, _this.server.port);
return _this.setClientInfo(cid, client);
}
});
})(this)(message);
(function(_this) {
return (function(message) {
var cid, matches;
if (matches = /^\[game\]: leave player='([0-9]+):.*'$/.exec(message)) {
cid = parseInt(matches[1]);
debug.connection('client disconnect (%s) on %s:%s ', cid, _this.server.host, _this.server.port);
return _this.removeClientInfo(cid);
}
});
})(this)(message);
if (message === 'Enter password:') {
debug.connection('%s:%s password request', this.server.host, this.server.port);
this.write(this.server.password);
return;
}
if (message === 'Authentication successful. External console access granted.') {
Promise.all([this.svName(), this.status()]).then((function(_this) {
return function(arg) {
var i, len, name, player, status;
name = arg[0], status = arg[1];
_this.name = name;
if (status) {
for (i = 0, len = status.length; i < len; i++) {
player = status[i];
_this.setClientInfo(player.cid, player.client);
}
}
if (!_this.connected) {
_this.connected = true;
debug.connection('%s:%s connected', _this.server.host, _this.server.port);
return _this.emit('online');
} else {
debug.connection('%s:%s reconnected', _this.server.host, _this.server.port);
return _this.emit('reconnected');
}
};
})(this))["catch"]((function(_this) {
return function(err) {
debug.connection('%s:%s econ error: %s', _this.server.host, _this.server.port, err.message);
return _this.emit('error', err);
};
})(this));
return;
}
if (/^Wrong password [0-9\/]+.$/.exec(message)) {
err = new EconConnectionError(message + " Disconnecting");
debug.connection('%s:%s econ error: %s', this.server.host, this.server.port, err.message);
this.emit('error', err);
this.disconnect();
this.emit('end');
return;
}
if (message === 'authentication timeout') {
err = new EconConnectionError('Authentication timeout. Disconnecting');
debug.connection('%s:%s econ error: %s', this.server.host, this.server.port, err.message);
this.emit('error', err);
this.disconnect();
this.emit('end');
return;
}
this.transactions.forEach(function(transaction) {
return transaction.handleMessage(message);
});
if (!this.isConnected()) {
return;
}
ref2 = this.handlers;
results = [];
for (i = 0, len = ref2.length; i < len; i++) {
handler = ref2[i];
result = handler.call(this, this, message);
if (result === false) {
break;
} else {
results.push(void 0);
}
}
return results;
};
TeeworldsEcon.prototype.setClientInfo = function(cid, client) {
return this.clients[cid] = client;
};
TeeworldsEcon.prototype.removeClientInfo = function(cid) {
return delete this.clients[cid];
};
TeeworldsEcon.prototype.getClientInfo = function(cid) {
var ref2;
return (ref2 = this.clients[cid]) != null ? ref2 : null;
};
TeeworldsEcon.prototype.connect = function(connectionParams) {
if (connectionParams == null) {
connectionParams = {};
}
if (this.connection) {
return;
}
this.retryDelay = connectionParams.retryDelay ? connectionParams.retryDelay : 30000;
this.retryCount = connectionParams.retryCount ? connectionParams.retryCount : -1;
this.connection = new Socket();
this.connection.pipe(split(/\r?\n\u0000*/)).on('data', this.handleMessage);
this.connection.on('error', (function(_this) {
return function(err) {
debug.connection('%s:%s connection error: %s', _this.server.host, _this.server.port, err.message);
return _this.emit('error', err);
};
})(this));
this.connection.on('close', this.reconnect);
this.connection.on('end', this.reconnect);
this.connection.setKeepAlive(true);
debug.connection('connecting to %s:%s', this.server.host, this.server.port);
return this.connection.connect(this.server.port, this.server.host);
};
TeeworldsEcon.prototype.reconnect = function() {
if (this.retryTimer) {
return;
}
debug.connection('reconnecting to %s:%s', this.server.host, this.server.port);
if (this.retryCount === 0) {
this.disconnect();
this.emit('end');
return;
}
if (this.retryCount > 0) {
this.retryCount--;
}
this.emit('reconnect');
return this.retryTimer = setTimeout((function(_this) {
return function() {
_this.retryTimer = null;
_this.disconnect();
return _this.connect({
retryDelay: _this.retryDelay,
retryCount: _this.retryCount
});
};
})(this), this.retryDelay);
};
TeeworldsEcon.prototype.disconnect = function() {
if (!this.connection) {
return;
}
debug.connection('disconnecting from %s:%s', this.server.host, this.server.port);
this.connection.removeAllListeners('data');
this.connection.removeAllListeners('end');
this.connection.removeAllListeners('error');
this.connection.destroy();
this.connection.unref();
return this.connection = null;
};
TeeworldsEcon.prototype.isConnected = function(initialized) {
if (initialized == null) {
initialized = true;
}
return this.connection && this.connection.writable && (!initialized || this.connected);
};
return TeeworldsEcon;
})(EventEmitter);
module.exports = TeeworldsEcon;