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
(in the state's error key), or with a state object containg the successful results.
The callback function is "guaranteed" to be called exactly once.
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
* numplayers
* maxplayers
* players
* name
* ping
* score
* 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
---

View File

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

View File

@ -33,21 +33,40 @@ module.exports = Class.extend(EventEmitter,{
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)
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) {
this.finalizeState(result);
this.done(result);
},
done: function(result) {
if(this.finished) return;
clearTimeout(this.globalTimeoutTimer);
this.prepState(result);
this.reset();
this.finished = true;
this.emit('finished',result);
@ -80,7 +99,7 @@ module.exports = Class.extend(EventEmitter,{
self.parseDns(self.options.host,c);
}
}, function(c) {
self.run();
self.run(self.initState());
}
]);
@ -98,12 +117,12 @@ module.exports = Class.extend(EventEmitter,{
reader: function(buffer) {
return new Reader(this,buffer);
},
translateState: function(state,trans) {
translate: function(obj,trans) {
for(var from in trans) {
var to = trans[from];
if(from in state) {
if(to) state[to] = state[from];
delete state[from];
if(from in obj) {
if(to) obj[to] = obj[from];
delete obj[from];
}
}
},

View File

@ -5,7 +5,7 @@ module.exports = require('./core').extend({
this.encoding = 'latin1';
this.byteorder = 'be';
},
run: function() {
run: function(state) {
var self = this;
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) {
var reader = self.reader(buffer);
var state = {
players:[]
};
while(!reader.done()) {
var key = reader.string();
if(!key) break;
var value = reader.string();
state[key] = value;
state.raw[key] = value;
}
var mode = '';
@ -42,6 +39,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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,9 +3,10 @@ var request = require('request');
module.exports = require('./core').extend({
init: function() {
this._super();
this.pretty = 'Terraria';
this.options.port = 7878;
},
run: function() {
run: function(state) {
var self = this;
request({
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');
var players = [];
var split = json.players.split(',');
split.forEach(function(one) {
players.push({name:one});
state.players.push({name:one});
});
state.name = json.name;
state.raw.port = json.port;
state.raw.numplayers = json.playercount;
self.finish({
'name': json.name,
'port': json.port,
'numplayers': json.playercount,
'players': players
});
self.finish(state);
});
}
});

View File

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

View File

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

View File

@ -1,13 +1,13 @@
module.exports = require('./gamespy3').extend({
init: function() {
this._super();
this.pretty = 'Unreal Tournament 3';
this.options.port = 6500;
},
prepState: function(state) {
finalizeState: function(state) {
this._super(state);
this.translateState(state,{
//'OwningPlayerName': 'hostname',
this.translate(state.raw,{
'mapname': false,
'p1073741825': 'map',
'p1073741826': 'gametype',
@ -35,7 +35,7 @@ module.exports = require('./gamespy3').extend({
'p268435968': false,
'p268435969': false
});
function split(a) {
var s = a.split('\x1c');
s = s.filter(function(e) { return e });
@ -43,5 +43,9 @@ module.exports = require('./gamespy3').extend({
}
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('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({
init: function() {
this._super();
this.pretty = 'Warsow';
this.options.port = 44400;
},
prepState: function(state) {