Massive revamp for standardization of output

This commit is contained in:
Michael Morrison 2013-07-12 04:12:02 -05:00
parent 397d09d9d3
commit 007f1ffb8b
15 changed files with 153 additions and 119 deletions

View file

@ -22,25 +22,34 @@ Gamedig.query(
); );
``` ```
State Object Callback Function
--- ---
The callback function is "guaranteed" to be called exactly once, indicating either a query error or timeout The callback function is "guaranteed" to be called exactly once.
(in the state's error key), or with a state object containg the successful results.
The returned state object may contain some or all of the following keys: If an error occurs, the returned object will contain an "error" key, indicating the issue.
If the error key exists, it should be assumed that the game server is offline or unreachable.
Otherwise, the returned object will contain the following keys:
Guaranteed:
* error
* name * name
* numplayers
* maxplayers
* players
* name
* ping
* score
* map * map
* gametype * password (boolean)
* maxplayers
* players (may contain name, ping, score, team, address)
* bots (same as players)
* raw (contains special keys depending on the type of server queried - UNSTABLE)
* notes (passed through from the input)
* query (details about the query performed)
* host
* address
* port
* type
* pretty (a "pretty" string describing the game)
Many other keys will also be available will be available on a game by game basis. It can usually be assumed that the number of players online is equal to the length of the players array.
Some servers may return an additional player count number, which may be present in the unstable raw object.
Supported Games Supported Games
--- ---

View file

@ -1,41 +1,40 @@
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.pretty = 'Armagetron';
this.encoding = 'latin1'; this.encoding = 'latin1';
this.byteorder = 'be'; this.byteorder = 'be';
this.options.port = 4534; this.options.port = 4534;
}, },
run: function() { run: function(state) {
var self = this; var self = this;
var b = new Buffer([0,0x35,0,0,0,0,0,0x11]); var b = new Buffer([0,0x35,0,0,0,0,0,0x11]);
this.udpSend(b,function(buffer) { this.udpSend(b,function(buffer) {
var state = {};
var reader = self.reader(buffer); var reader = self.reader(buffer);
reader.skip(6); reader.skip(6);
state.port = self.readUInt(reader); state.raw.port = self.readUInt(reader);
state.hostname = self.readString(reader,buffer); state.raw.hostname = self.readString(reader,buffer);
state.name = self.readString(reader,buffer); state.name = self.readString(reader,buffer);
state.numplayers = self.readUInt(reader); state.raw.numplayers = self.readUInt(reader);
state.versionmin = self.readUInt(reader); state.raw.versionmin = self.readUInt(reader);
state.versionmax = self.readUInt(reader); state.raw.versionmax = self.readUInt(reader);
state.version = self.readString(reader,buffer); state.raw.version = self.readString(reader,buffer);
state.maxplayers = self.readUInt(reader); state.maxplayers = self.readUInt(reader);
var players = self.readString(reader,buffer); var players = self.readString(reader,buffer);
var list = players.split('\n'); var list = players.split('\n');
state.players = [];
for(var i = 0; i < list.length; i++) { for(var i = 0; i < list.length; i++) {
if(!list[i]) continue; if(!list[i]) continue;
state.players.push({name:list[i]}); state.players.push({name:list[i]});
} }
state.options = self.readString(reader,buffer); state.raw.options = self.readString(reader,buffer);
state.uri = self.readString(reader,buffer); state.raw.uri = self.readString(reader,buffer);
state.globalids = self.readString(reader,buffer); state.raw.globalids = self.readString(reader,buffer);
self.finish(state); self.finish(state);
return true; return true;
}); });

View file

@ -33,21 +33,40 @@ module.exports = Class.extend(EventEmitter,{
this.done({error: err.toString()}); this.done({error: err.toString()});
}, },
prepState: function(state) { initState: function() {
return {
name: '',
map: '',
password: false,
raw: {},
maxplayers: 0,
players: [],
bots: []
};
},
finalizeState: function(state) {
if(this.options.notes) if(this.options.notes)
state.notes = this.options.notes; state.notes = this.options.notes;
if('host' in this.options) state.queryhost = this.options.host;
if('port' in this.options) state.queryport = this.options.port; state.query = {};
if('host' in this.options) state.query.host = this.options.host;
if('port' in this.options) state.query.port = this.options.port;
state.query.type = this.type;
if('pretty' in this) state.query.pretty = this.pretty;
if('players' in state) state.numplayers = state.players.length;
if('bots' in state) state.numbots = state.bots.length;
}, },
finish: function(result) { finish: function(result) {
this.finalizeState(result);
this.done(result); this.done(result);
}, },
done: function(result) { done: function(result) {
if(this.finished) return; if(this.finished) return;
clearTimeout(this.globalTimeoutTimer); clearTimeout(this.globalTimeoutTimer);
this.prepState(result);
this.reset(); this.reset();
this.finished = true; this.finished = true;
this.emit('finished',result); this.emit('finished',result);
@ -80,7 +99,7 @@ module.exports = Class.extend(EventEmitter,{
self.parseDns(self.options.host,c); self.parseDns(self.options.host,c);
} }
}, function(c) { }, function(c) {
self.run(); self.run(self.initState());
} }
]); ]);
@ -98,12 +117,12 @@ module.exports = Class.extend(EventEmitter,{
reader: function(buffer) { reader: function(buffer) {
return new Reader(this,buffer); return new Reader(this,buffer);
}, },
translateState: function(state,trans) { translate: function(obj,trans) {
for(var from in trans) { for(var from in trans) {
var to = trans[from]; var to = trans[from];
if(from in state) { if(from in obj) {
if(to) state[to] = state[from]; if(to) obj[to] = obj[from];
delete state[from]; delete obj[from];
} }
} }
}, },

View file

@ -5,7 +5,7 @@ module.exports = require('./core').extend({
this.encoding = 'latin1'; this.encoding = 'latin1';
this.byteorder = 'be'; this.byteorder = 'be';
}, },
run: function() { run: function(state) {
var self = this; var self = this;
this.sendPacket(9,false,false,false,function(buffer) { this.sendPacket(9,false,false,false,function(buffer) {
@ -16,15 +16,12 @@ module.exports = require('./core').extend({
self.sendPacket(0,challenge,new Buffer([0xff,0xff,0xff,0x01]),true,function(buffer) { self.sendPacket(0,challenge,new Buffer([0xff,0xff,0xff,0x01]),true,function(buffer) {
var reader = self.reader(buffer); var reader = self.reader(buffer);
var state = {
players:[]
};
while(!reader.done()) { while(!reader.done()) {
var key = reader.string(); var key = reader.string();
if(!key) break; if(!key) break;
var value = reader.string(); var value = reader.string();
state[key] = value; state.raw[key] = value;
} }
var mode = ''; var mode = '';
@ -43,6 +40,9 @@ module.exports = require('./core').extend({
} }
} }
if('hostname' in state.raw) state.name = state.raw.hostname;
if('map' in state.raw) state.map = state.raw.map;
self.finish(state); self.finish(state);
}); });

View file

@ -1,6 +1,7 @@
module.exports = require('./unreal2').extend({ module.exports = require('./unreal2').extend({
init: function() { init: function() {
this._super(); this._super();
this.pretty = 'Killing Floor';
this.options.port = 7708; this.options.port = 7708;
} }
}); });

View file

@ -3,6 +3,7 @@ var dns = require('dns');
module.exports = require('./gamespy3').extend({ module.exports = require('./gamespy3').extend({
init: function() { init: function() {
this._super(); this._super();
this.pretty = 'Minecraft';
this.maxAttempts = 2; this.maxAttempts = 2;
this.options.port = 25565; this.options.port = 25565;
}, },

View file

@ -14,7 +14,7 @@ module.exports = require('./core').extend({
this.gbxclient = false; this.gbxclient = false;
} }
}, },
run: function() { run: function(state) {
var self = this; var self = this;
var cmds = [ var cmds = [
@ -46,8 +46,6 @@ module.exports = require('./core').extend({
}); });
} }
}, function() { }, function() {
var state = {};
var gamemode = ''; var gamemode = '';
var igm = results[5].GameMode; var igm = results[5].GameMode;
if(igm == 0) gamemode="Rounds"; if(igm == 0) gamemode="Rounds";
@ -61,14 +59,13 @@ module.exports = require('./core').extend({
state.password = (results[3].Password != 'No password'); state.password = (results[3].Password != 'No password');
state.maxplayers = results[3].CurrentMaxPlayers; state.maxplayers = results[3].CurrentMaxPlayers;
state.map = self.stripColors(results[4].Name); state.map = self.stripColors(results[4].Name);
state.gametype = gamemode; state.raw.gametype = gamemode;
state.players = [];
results[2].forEach(function(player) { results[2].forEach(function(player) {
state.players.push({name:self.stripColors(player.Name)}); state.players.push({name:self.stripColors(player.Name)});
}); });
console.log(state); self.finish(state);
}); });
}, },
stripColors: function(str) { stripColors: function(str) {

View file

@ -1,13 +1,14 @@
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.pretty = 'Quake 2';
this.options.port = 27910; this.options.port = 27910;
this.encoding = 'latin1'; this.encoding = 'latin1';
this.delimiter = '\n'; this.delimiter = '\n';
this.sendHeader = 'status'; this.sendHeader = 'status';
this.responseHeader = 'print'; this.responseHeader = 'print';
}, },
run: function() { run: function(state) {
var self = this; var self = this;
this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00',function(buffer) { this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00',function(buffer) {
@ -15,18 +16,15 @@ module.exports = require('./core').extend({
var header = reader.string(); var header = reader.string();
if(header != '\xff\xff\xff\xff'+this.responseHeader) return; if(header != '\xff\xff\xff\xff'+this.responseHeader) return;
var state = {};
var info = reader.string().split('\\'); var info = reader.string().split('\\');
if(info[0] == '') info.shift(); if(info[0] == '') info.shift();
while(true) { while(true) {
var key = info.shift(); var key = info.shift();
var value = info.shift(); var value = info.shift();
if(typeof value == 'undefined') break; if(typeof value == 'undefined') break;
state[key] = value; state.raw[key] = value;
} }
state.players = [];
while(!reader.done()) { while(!reader.done()) {
var player = reader.string(); var player = reader.string();
@ -50,11 +48,16 @@ module.exports = require('./core').extend({
var name = args[2] || ''; var name = args[2] || '';
var address = args[3] || ''; var address = args[3] || '';
state.players.push({ (ping == 0 ? state.bots : state.players).push({
frags:frags, ping:ping, name:name, address:address frags:frags, ping:ping, name:name, address:address
}); });
} }
if('g_needpass' in state.raw) state.password = state.raw.g_needpass;
if('mapname' in state.raw) state.map = state.raw.mapname;
if('sv_maxclients' in state.raw) state.maxplayers = state.raw.sv_maxclients;
if('sv_hostname' in state.raw) state.name = state.raw.sv_hostname;
self.finish(state); self.finish(state);
return true; return true;
}); });

View file

@ -1,6 +1,7 @@
module.exports = require('./quake2').extend({ module.exports = require('./quake2').extend({
init: function() { init: function() {
this._super(); this._super();
this.pretty = 'Quake 3';
this.options.port = 27960; this.options.port = 27960;
this.sendHeader = 'getstatus'; this.sendHeader = 'getstatus';
this.responseHeader = 'statusResponse'; this.responseHeader = 'statusResponse';

View file

@ -7,11 +7,10 @@ module.exports = require('./core').extend({
this.goldsrc = false; this.goldsrc = false;
this.options.port = 27015; this.options.port = 27015;
}, },
run: function() { run: function(state) {
var self = this; var self = this;
var challenge; var challenge;
var state = {};
async.series([ async.series([
function(c) { function(c) {
@ -21,55 +20,55 @@ module.exports = require('./core').extend({
function(b) { function(b) {
var reader = self.reader(b); var reader = self.reader(b);
if(self.goldsrc) state.address = reader.string(); if(self.goldsrc) state.raw.address = reader.string();
else state.protocol = reader.uint(1); else state.raw.protocol = reader.uint(1);
state.name = reader.string(); state.name = reader.string();
state.map = reader.string(); state.map = reader.string();
state.folder = reader.string(); state.raw.folder = reader.string();
state.game = reader.string(); state.raw.game = reader.string();
state.steamappid = reader.uint(2); state.raw.steamappid = reader.uint(2);
state.numplayers = reader.uint(1); state.raw.numplayers = reader.uint(1);
state.maxplayers = reader.uint(1); state.maxplayers = reader.uint(1);
if(self.goldsrc) state.protocol = reader.uint(1); if(self.goldsrc) state.raw.protocol = reader.uint(1);
else state.numbots = reader.uint(1); else state.raw.numbots = reader.uint(1);
state.listentype = String.fromCharCode(reader.uint(1)); state.raw.listentype = String.fromCharCode(reader.uint(1));
state.environment = String.fromCharCode(reader.uint(1)); state.raw.environment = String.fromCharCode(reader.uint(1));
state.passworded = reader.uint(1); state.password = reader.uint(1);
if(self.goldsrc) { if(self.goldsrc) {
state.ismod = reader.uint(1); state.raw.ismod = reader.uint(1);
if(state.ismod) { if(state.raw.ismod) {
state.modlink = reader.string(); state.raw.modlink = reader.string();
state.moddownload = reader.string(); state.raw.moddownload = reader.string();
reader.skip(1); reader.skip(1);
state.modversion = reader.uint(4); state.raw.modversion = reader.uint(4);
state.modsize = reader.uint(4); state.raw.modsize = reader.uint(4);
state.modtype = reader.uint(1); state.raw.modtype = reader.uint(1);
state.moddll = reader.uint(1); state.raw.moddll = reader.uint(1);
} }
} }
state.secure = reader.uint(1); state.raw.secure = reader.uint(1);
if(self.goldsrc) { if(self.goldsrc) {
state.numbots = reader.uint(1); state.raw.numbots = reader.uint(1);
} else { } else {
if(state.folder == 'ship') { if(state.raw.folder == 'ship') {
state.shipmode = reader.uint(1); state.raw.shipmode = reader.uint(1);
state.shipwitnesses = reader.uint(1); state.raw.shipwitnesses = reader.uint(1);
state.shipduration = reader.uint(1); state.raw.shipduration = reader.uint(1);
} }
state.version = reader.string(); state.raw.version = reader.string();
var extraFlag = reader.uint(1); var extraFlag = reader.uint(1);
if(extraFlag & 0x80) state.port = reader.uint(2); if(extraFlag & 0x80) state.raw.port = reader.uint(2);
if(extraFlag & 0x10) state.steamid = reader.uint(8); if(extraFlag & 0x10) state.raw.steamid = reader.uint(8);
if(extraFlag & 0x40) { if(extraFlag & 0x40) {
state.sourcetvport = reader.uint(2); state.raw.sourcetvport = reader.uint(2);
state.sourcetvname = reader.string(); state.raw.sourcetvname = reader.string();
} }
if(extraFlag & 0x20) state.tags = reader.string(); if(extraFlag & 0x20) state.raw.tags = reader.string();
if(extraFlag & 0x01) state.gameid = reader.uint(8); if(extraFlag & 0x01) state.raw.gameid = reader.uint(8);
} }
c(); c();
@ -87,13 +86,12 @@ module.exports = require('./core').extend({
self.sendPacket(0x55,challenge,false,0x44,function(b) { self.sendPacket(0x55,challenge,false,0x44,function(b) {
var reader = self.reader(b); var reader = self.reader(b);
var num = reader.uint(1); var num = reader.uint(1);
state.players = [];
for(var i = 0; i < num; i++) { for(var i = 0; i < num; i++) {
reader.skip(1); reader.skip(1);
var name = reader.string(); var name = reader.string();
var score = reader.uint(4); var score = reader.uint(4);
var time = reader.float(); var time = reader.float();
state.players.push({ (time == -1 ? state.bots : state.players).push({
name:name, score:score, time:time name:name, score:score, time:time
}); });
} }
@ -104,11 +102,11 @@ module.exports = require('./core').extend({
self.sendPacket(0x56,challenge,false,0x45,function(b) { self.sendPacket(0x56,challenge,false,0x45,function(b) {
var reader = self.reader(b); var reader = self.reader(b);
var num = reader.uint(2); var num = reader.uint(2);
state.rules = []; state.raw.rules = [];
for(var i = 0; i < num; i++) { for(var i = 0; i < num; i++) {
var key = reader.string(); var key = reader.string();
var value = reader.string(); var value = reader.string();
state.rules[key] = value; state.raw.rules[key] = value;
} }
c(); c();
}); });

View file

@ -3,9 +3,10 @@ var request = require('request');
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.pretty = 'Terraria';
this.options.port = 7878; this.options.port = 7878;
}, },
run: function() { run: function(state) {
var self = this; var self = this;
request({ request({
uri: 'http://'+this.options.address+':'+this.options.port+'/status', uri: 'http://'+this.options.address+':'+this.options.port+'/status',
@ -21,18 +22,16 @@ module.exports = require('./core').extend({
if(json.status != 200) return self.error('Invalid status'); if(json.status != 200) return self.error('Invalid status');
var players = [];
var split = json.players.split(','); var split = json.players.split(',');
split.forEach(function(one) { split.forEach(function(one) {
players.push({name:one}); state.players.push({name:one});
}); });
self.finish({ state.name = json.name;
'name': json.name, state.raw.port = json.port;
'port': json.port, state.raw.numplayers = json.playercount;
'numplayers': json.playercount,
'players': players self.finish(state);
});
}); });
} }
}); });

View file

@ -5,54 +5,55 @@ module.exports = require('./core').extend({
this._super(); this._super();
this.encoding = 'latin1'; this.encoding = 'latin1';
}, },
run: function() { run: function(state) {
var self = this; var self = this;
var state = {};
async.series([ async.series([
function(c) { function(c) {
self.sendPacket(0,true,function(b) { self.sendPacket(0,true,function(b) {
var reader = self.reader(b); var reader = self.reader(b);
state.serverid = reader.uint(4); state.raw.serverid = reader.uint(4);
state.ip = reader.pascal(); state.raw.ip = reader.pascal();
state.port = reader.uint(4); state.raw.port = reader.uint(4);
state.queryport = reader.uint(4); state.raw.queryport = reader.uint(4);
state.name = reader.pascal(); state.name = reader.pascal();
state.map = reader.pascal(); state.map = reader.pascal();
state.gametype = reader.pascal(); state.raw.gametype = reader.pascal();
state.numplayers = reader.uint(4); state.raw.numplayers = reader.uint(4);
state.maxplayers = reader.uint(4); state.maxplayers = reader.uint(4);
state.ping = reader.uint(4); state.raw.ping = reader.uint(4);
c(); c();
}); });
}, },
function(c) { function(c) {
self.sendPacket(1,true,function(b) { self.sendPacket(1,true,function(b) {
var reader = self.reader(b); var reader = self.reader(b);
state.mutators = []; state.raw.mutators = [];
state.rules = {}; state.raw.rules = {};
while(!reader.done()) { while(!reader.done()) {
var key = reader.pascal(); var key = reader.pascal();
var value = reader.pascal(); var value = reader.pascal();
if(key == 'Mutator') state.mutators.push(value); if(key == 'Mutator') state.raw.mutators.push(value);
else state.rules[key] = value; else state.raw.rules[key] = value;
} }
if('GamePassword' in state.raw.rules)
state.password = state.raw.rules.GamePassword != 'True';
c(); c();
}); });
}, },
function(c) { function(c) {
self.sendPacket(2,false,function(b) { self.sendPacket(2,false,function(b) {
var reader = self.reader(b); var reader = self.reader(b);
state.players = [];
while(!reader.done()) { while(!reader.done()) {
var id = reader.uint(4); var id = reader.uint(4);
console.log(b.slice(reader.offset()));
var name = reader.pascal(); var name = reader.pascal();
var ping = reader.uint(4); var ping = reader.uint(4);
var score = reader.uint(4); var score = reader.uint(4);
reader.skip(4); reader.skip(4);
state.players.push({ (ping == 0 ? state.bots : state.players).push({
id: id, name: name, ping: ping, score: score id: id, name: name, ping: ping, score: score
}); });
} }

View file

@ -1,6 +1,7 @@
module.exports = require('./unreal2').extend({ module.exports = require('./unreal2').extend({
init: function() { init: function() {
this._super(); this._super();
this.pretty = 'Unreal Tournament 2004';
this.options.port = 7778; this.options.port = 7778;
} }
}); });

View file

@ -1,13 +1,13 @@
module.exports = require('./gamespy3').extend({ module.exports = require('./gamespy3').extend({
init: function() { init: function() {
this._super(); this._super();
this.pretty = 'Unreal Tournament 3';
this.options.port = 6500; this.options.port = 6500;
}, },
prepState: function(state) { finalizeState: function(state) {
this._super(state); this._super(state);
this.translateState(state,{ this.translate(state.raw,{
//'OwningPlayerName': 'hostname',
'mapname': false, 'mapname': false,
'p1073741825': 'map', 'p1073741825': 'map',
'p1073741826': 'gametype', 'p1073741826': 'gametype',
@ -43,5 +43,9 @@ module.exports = require('./gamespy3').extend({
} }
if('custom_mutators' in state) state['custom_mutators'] = split(state['custom_mutators']); if('custom_mutators' in state) state['custom_mutators'] = split(state['custom_mutators']);
if('stock_mutators' in state) state['stock_mutators'] = split(state['stock_mutators']); if('stock_mutators' in state) state['stock_mutators'] = split(state['stock_mutators']);
if('map' in state.raw) state.map = state.raw.map;
if('password' in state.raw) state.password = state.raw.password;
if('servername' in state.raw) state.name = state.raw.servername;
} }
}); });

View file

@ -1,6 +1,7 @@
module.exports = require('./quake3').extend({ module.exports = require('./quake3').extend({
init: function() { init: function() {
this._super(); this._super();
this.pretty = 'Warsow';
this.options.port = 44400; this.options.port = 44400;
}, },
prepState: function(state) { prepState: function(state) {