Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
381 changes: 205 additions & 176 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,182 +7,211 @@
* ----------------------------------------------------------------------------
*/

var net = require("net"),
LineInputStream = require("line-input-stream"),
events = require("events"),
util = require("util");

function TeamSpeakClient(host, port){
events.EventEmitter.call(this);

var self = this,
socket = net.connect(port || 10011, host || 'localhost'),
reader = null,
status = -2,
queue = [],
executing = null;

function tsescape(s){
var r = String(s);
r = r.replace(/\\/g, "\\\\"); // Backslash
r = r.replace(/\//g, "\\/"); // Slash
r = r.replace(/\|/g, "\\p"); // Pipe
r = r.replace(/\n/g, "\\n"); // Newline
r = r.replace(/\r/g, "\\r"); // Carriage Return
r = r.replace(/\t/g, "\\t"); // Tab
r = r.replace(/\v/g, "\\v"); // Vertical Tab
r = r.replace(/\f/g, "\\f"); // Formfeed
r = r.replace(/ /g, "\\s"); // Whitespace
return r;
}

function tsunescape(s){
var r = String(s);
r = r.replace(/\\s/g, " "); // Whitespace
r = r.replace(/\\p/g, "|"); // Pipe
r = r.replace(/\\n/g, "\n"); // Newline
r = r.replace(/\\f/g, "\f"); // Formfeed
r = r.replace(/\\r/g, "\r"); // Carriage Return
r = r.replace(/\\t/g, "\t"); // Tab
r = r.replace(/\\v/g, "\v"); // Vertical Tab
r = r.replace(/\\\//g, "\/"); // Slash
r = r.replace(/\\\\/g, "\\"); // Backslash
return r;
}

function checkQueue(){
if(!executing && queue.length >= 1){
executing = queue.shift();
socket.write(executing.text + "\n");
}
}

function parseResponse(s){
var response = [];
var records = s.split("|");

response = records.map(function(k){
var args = k.split(" ");
var thisrec = {};
args.forEach(function(v){
if(v.indexOf("=") > -1){
var key = tsunescape(v.substr(0, v.indexOf("=")));
var value = tsunescape(v.substr(v.indexOf("=")+1));
if(parseInt(value, 10) == value) value = parseInt(value, 10);
thisrec[key] = value;
}else thisrec[v] = "";
});
return thisrec;
});

if(response.length === 0){
response = null;
} else if(response.length === 1){
response = response.shift();
}

return response;
}

// Return pending commands that are going to be sent to the server.
// Note that they have been parsed - Access getPending()[0].text to get
// the full text representation of the command.
TeamSpeakClient.prototype.getPending = function(){
return queue.slice(0);
};

// Clear the queue of pending commands so that any command that is currently queued won't be executed.
// The old queue is returned.
TeamSpeakClient.prototype.clearPending = function(){
var q = queue;
queue = [];
return q;
};

// Send a command to the server
TeamSpeakClient.prototype.send = function(){
var args = Array.prototype.slice.call(arguments);
var options = [], params = {};
var callback = undefined;
var cmd = args.shift();
args.forEach(function(v){
if(util.isArray(v)){
options = v;
} else if(typeof v === "function"){
callback = v;
} else {
params = v;
}
});
var tosend = tsescape(cmd);
options.forEach(function(v){
tosend += " -" + tsescape(v);
});
for(var k in params){
var v = params[k];
if(util.isArray(v)){ // Multiple values for the same key - concatenate all
var doptions = v.map(function(val){
return tsescape(k) + "=" + tsescape(val);
});
tosend += " " + doptions.join("|");
} else {
tosend += " " + tsescape(k.toString()) + "=" + tsescape(v.toString());
}
}
queue.push({cmd: cmd, options: options, parameters: params, text: tosend, cb: callback});
if(status === 0) checkQueue();
};

// SetTimeout function
TeamSpeakClient.prototype.setTimeout = function(time) {
socket.setTimeout(time || 60000, function() {
socket.destroy();
self.emit("timeout");
});
};

socket.on("error", function(err){
self.emit("error", err);
});

socket.on("close", function(){
self.emit("close", queue);
});

socket.on("connect", function(){
reader = LineInputStream(socket);
reader.on("line", function(line){
var s = line.trim();
// Ignore two first lines sent by server ("TS3" and information message)
if(status < 0){
status++;
if(status === 0) checkQueue();
return;
}
// Server answers with:
// [- One line containing the answer ]
// - "error id=XX msg=YY". ID is zero if command was executed successfully.
var response = undefined;
if(s.indexOf("error") === 0){
response = parseResponse(s.substr("error ".length).trim());
executing.error = response;
if(executing.error.id === 0) delete executing.error;
if(executing.cb) executing.cb.call(executing, executing.error, executing.response,
executing.rawResponse);
executing = null;
checkQueue();
} else if(s.indexOf("notify") === 0){
s = s.substr("notify".length);
response = parseResponse(s);
self.emit(s.substr(0, s.indexOf(" ")), response);
} else if(executing) {
response = parseResponse(s);
executing.rawResponse = s;
executing.response = response;
}
});
self.emit("connect");
});
var net = require("net"),
LineInputStream = require("line-input-stream"),
events = require("events"),
util = require("util");

function TeamSpeakClient(host, port) {
events.EventEmitter.call(this);

var self = this,
socket = net.connect(port || 10011, host || 'localhost'),
reader = null,
status = -2,
queue = [],
executing = null,
keeper = null,
lastData = null;

function tsescape(s) {
var r = String(s);
r = r.replace(/\\/g, "\\\\"); // Backslash
r = r.replace(/\//g, "\\/"); // Slash
r = r.replace(/\|/g, "\\p"); // Pipe
r = r.replace(/\n/g, "\\n"); // Newline
r = r.replace(/\r/g, "\\r"); // Carriage Return
r = r.replace(/\t/g, "\\t"); // Tab
r = r.replace(/\v/g, "\\v"); // Vertical Tab
r = r.replace(/\f/g, "\\f"); // Formfeed
r = r.replace(/ /g, "\\s"); // Whitespace
return r;
}

function tsunescape(s) {
var r = String(s);
r = r.replace(/\\s/g, " "); // Whitespace
r = r.replace(/\\p/g, "|"); // Pipe
r = r.replace(/\\n/g, "\n"); // Newline
r = r.replace(/\\f/g, "\f"); // Formfeed
r = r.replace(/\\r/g, "\r"); // Carriage Return
r = r.replace(/\\t/g, "\t"); // Tab
r = r.replace(/\\v/g, "\v"); // Vertical Tab
r = r.replace(/\\\//g, "\/"); // Slash
r = r.replace(/\\\\/g, "\\"); // Backslash
return r;
}

function checkQueue() {
if (!executing && queue.length >= 1) {
executing = queue.shift();
socket.write(executing.text + "\n");
}
}

function parseResponse(s) {
var response = [];
var records = s.split("|");

response = records.map(function(k) {
var args = k.split(" ");
var thisrec = {};
args.forEach(function(v) {
if (v.indexOf("=") > -1) {
var key = tsunescape(v.substr(0, v.indexOf("=")));
var value = tsunescape(v.substr(v.indexOf("=") + 1));
if (parseInt(value, 10) == value) value = parseInt(value, 10);
thisrec[key] = value;
} else thisrec[v] = "";
});
return thisrec;
});

if (response.length === 0) {
response = null;
} else if (response.length === 1) {
response = response.shift();
}

return response;
}

// Return pending commands that are going to be sent to the server.
// Note that they have been parsed - Access getPending()[0].text to get
// the full text representation of the command.
TeamSpeakClient.prototype.getPending = function() {
return queue.slice(0);
};

// Clear the queue of pending commands so that any command that is currently queued won't be executed.
// The old queue is returned.
TeamSpeakClient.prototype.clearPending = function() {
var q = queue;
queue = [];
return q;
};

// Send a command to the server
TeamSpeakClient.prototype.send = function() {
var args = Array.prototype.slice.call(arguments);
var options = [],
params = {};
var callback = undefined;
var cmd = args.shift();
args.forEach(function(v) {
if (util.isArray(v)) {
options = v;
} else if (typeof v === "function") {
callback = v;
} else {
params = v;
}
});
var tosend = tsescape(cmd);
options.forEach(function(v) {
tosend += " -" + tsescape(v);
});
for (var k in params) {
var v = params[k];
if (util.isArray(v)) { // Multiple values for the same key - concatenate all
var doptions = v.map(function(val) {
return tsescape(k) + "=" + tsescape(val);
});
tosend += " " + doptions.join("|");
} else {
tosend += " " + tsescape(k.toString()) + "=" + tsescape(v.toString());
}
}
queue.push({
cmd: cmd,
options: options,
parameters: params,
text: tosend,
cb: callback
});
if (status === 0) checkQueue();
};

// SetTimeout function
TeamSpeakClient.prototype.setTimeout = function(keepAlive, time) {
time = time || 10000;
socket.setTimeout(time, function() {
socket.destroy();
self.emit("timeout");
});
if (keepAlive) {
// Clear old keeper intervals if available
if (keeper) clearInterval(keeper);
// Set the keeper interval to fireup 1 sec before the timeout
keeper = setInterval(function() {
if (new Date().getTime() - lastData > (time - 1000)) {
self.send("version", function(err, response, rawResponse) {
if (!err) self.emit("keepAlive");
});
}
}, 500);
}
};


socket.on("error", function(err) {
self.emit("error", err);
});

socket.on("close", function() {
self.emit("close", queue);
});

// Register the on data event to update the lastData timestamp for the keepAlive
socket.on("data", function(data) {
lastData = new Date().getTime();
self.emit("data", data);
});

socket.on("connect", function() {
reader = LineInputStream(socket);
reader.on("line", function(line) {
var s = line.trim();
// Ignore two first lines sent by server ("TS3" and information message)
if (status < 0) {
status++;
if (status === 0) checkQueue();
return;
}
// Server answers with:
// [- One line containing the answer ]
// - "error id=XX msg=YY". ID is zero if command was executed successfully.
var response = undefined;
if (s.indexOf("error") === 0) {
response = parseResponse(s.substr("error ".length).trim());
executing.error = response;
if (executing.error.id === 0) delete executing.error;
if (executing.cb) executing.cb.call(executing, executing.error, executing.response,
executing.rawResponse);
executing = null;
checkQueue();
} else if (s.indexOf("notify") === 0) {
s = s.substr("notify".length);
response = parseResponse(s);
self.emit(s.substr(0, s.indexOf(" ")), response);
} else if (executing) {
response = parseResponse(s);
executing.rawResponse = s;
executing.response = response;
}
});
self.emit("connect");
});
}

util.inherits(TeamSpeakClient, events.EventEmitter);
Expand Down