@fabricio-191/valve-server-query
v4.1.9
Published
An implementation of valve protocols
Downloads
4,256
Maintainers
Readme
An implementation of valve protocols
const { Server, RCON, MasterServer } = require('@fabricio-191/valve-server-query');
This module allows you to:
- Easily make queries to servers running valve games
- Make queries to valve master servers
- Use a remote console to control your server remotely
Some of the games where it works with are:
- Counter-Strike
- Counter-Strike: Global Offensive
- Garry's Mod
- Half-Life
- Team Fortress 2
- Day of Defeat
- The ship
These are the default values
{
ip: 'localhost', //in MasterServer is 'hl2master.steampowered.com'
port: 27015, //in MasterServer is 27011
timeout: 2000,
debug: false,
enableWarns: true,
//RCON
password: 'The RCON password', // hasn't a default value
//Master server
quantity: 200,
region: 'OTHER',
}
Server
const server = await Server({
ip: '0.0.0.0',
port: 27015,
timeout: 3000,
});
const info = await server.getInfo();
console.log(info);
const players = await server.getPlayers();
console.log(players);
const rules = await server.getRules();
console.log(rules);
const ping = server.lastPing;
console.log(ping);
/*
You can also do:
const [info, players, rules] = await Promise.all([
server.getInfo(),
server.getPlayers().catch(e => {}),
server.getRules().catch(e => {})
]);
This make all queries in parallel
*/
Performs an A2S_INFO query to the server
server.getInfo()
.then(info => {
console.log(info);
})
.catch(err => {
//...
});
Info can be something like this:
{
address: '192.223.30.25:27015',
ping: 222,
protocol: 17,
goldSource: false,
name: '[Raidboss] Private KZ/Climb [GOKZ |Global | VIP/Whitelist Only]',
map: 'kz_frozen_go',
folder: 'csgo',
game: 'Counter-Strike: Global Offensive',
appID: 730n,
players: { online: 3, max: 10, bots: 0 },
type: 'dedicated',
OS: 'linux',
visibility: 'public',
VAC: true,
version: '1.37.6.9',
//data after this may not be present
port: 27015,
steamID: 85568392922144671n,
tv: {
port: 27020,
name: 'RaidbossTV'
},
keywords: [
'empty', '5v5',
'boss', 'casual',
'climb', 'comp',
'competitive', 'esea',
'faceit', 'gloves',
'knife', 'kreedz',
'kz', 'ladder',
'priz', 'pub',
'pug', 'raid',
'raidboss', 'rank',
'ranks', 'st'
],
gameID: 730n
}
Performs an A2S_PLAYER query to get the list of players in the server
Some servers may have disable this query (very rare).
server.getPlayers()
.then(players => {
console.log(`There are ${players.length} in the server`);
const list = players
.sort((a, b) => b.timeOnline - a.timeOnline)
.map((player, index) => `${index + 1}. ${player.name} ${player.timeOnline}`)
.join('\n');
console.log(list);
})
.catch(console.error);
The
time
class has an personalizedtoString()
and@@toPrimitive()
methods
Example:
[
{
index: 0,
name: 'kritikal',
score: 0,
timeOnline: Time {
hours: 0,
minutes: 10,
seconds: 25,
start: 2021-03-20T02:46:28.267Z, //Date
raw: 625.2186279296875
}
},
{
index: 0,
name: 'dmx;',
score: 0,
timeOnline: Time {
hours: 0,
minutes: 10,
seconds: 25,
start: 2021-03-20T02:46:28.267Z,
raw: 625.0791625976562
}
},
{
index: 0,
name: 'fenakz',
score: 0,
timeOnline: Time {
hours: 0,
minutes: 10,
seconds: 21,
start: 2021-03-20T02:46:28.271Z,
raw: 621.9488525390625
}
},
{
index: 0,
name: '[JC] Master-cba',
score: 0,
timeOnline: Time {
hours: 0,
minutes: 10,
seconds: 15,
start: 2021-03-20T02:46:28.278Z,
raw: 615.4395751953125
}
},
{
index: 0,
name: 'sAIONARAH34! [pw] ⚓✵♣',
score: 1,
timeOnline: Time {
hours: 0,
minutes: 10,
seconds: 1,
start: 2021-03-20T02:46:28.292Z,
raw: 601.7681274414062
}
},
{
index: 0,
name: 'INFRA-',
score: 0,
timeOnline: Time {
hours: 0,
minutes: 9,
seconds: 50,
start: 2021-03-20T02:46:28.303Z,
raw: 590.30859375
}
},
{
index: 0,
name: 'Agente86',
score: 1,
timeOnline: Time {
hours: 0,
minutes: 9,
seconds: 0,
start: 2021-03-20T02:46:28.353Z,
raw: 540.4190063476562
}
}
]
If the game is The Ship
every player will have 2 extra properties deaths
and money
Makes an A2S_RULES query to the server
Some servers may have disable this query, so you should expect an error.
server.getRules()
.then(console.log)
.catch(() => {});
The response can change a lot between servers
Example:
{
bot_quota: 0,
coop: 0,
cssdm_enabled: 0,
cssdm_ffa_enabled: 0,
cssdm_version: '2.1.6-dev',
deathmatch: 1,
decalfrequency: 10,
gungame_enabled: 1,
metamod_version: '1.10.7-devV',
mp_allowNPCs: 1,
mp_autocrosshair: 1,
mp_autoteambalance: 1,
mp_c4timer: 35,
mp_disable_respawn_times: 0,
mp_fadetoblack: 0,
mp_falldamage: 0,
mp_flashlight: 1,
mp_footsteps: 1,
mp_forceautoteam: 0,
mp_forcerespawn: 1,
mp_fraglimit: 0,
mp_freezetime: 1,
mp_friendlyfire: 0,
mp_holiday_nogifts: 0,
mp_hostagepenalty: 13,
mp_limitteams: 2,
mp_match_end_at_timelimit: 0,
mp_maxrounds: 0,
mp_respawnwavetime: 10,
mp_roundtime: 9,
mp_scrambleteams_auto: 1,
mp_scrambleteams_auto_windifference: 2,
mp_stalemate_enable: 0,
mp_stalemate_meleeonly: 0,
mp_startmoney: 800,
mp_teamlist: 'hgrunt;scientist',
mp_teamplay: 0,
mp_timelimit: 18,
mp_tournament: 0,
mp_weaponstay: 0,
mp_winlimit: 0,
nextlevel: '',
r_AirboatViewDampenDamp: 1,
r_AirboatViewDampenFreq: 7,
r_AirboatViewZHeight: 0,
r_JeepViewDampenDamp: 1,
r_JeepViewDampenFreq: 7,
r_JeepViewZHeight: 10,
r_VehicleViewDampen: 1,
scc_version: '2.0.0',
sm_advertisements_version: 0.6,
sm_allchat_version: '1.1.1',
sm_cannounce_version: 1.8,
sm_ggdm_version: '1.8.0',
sm_gungamesm_version: '1.2.16.0',
sm_nextmap: 'gg_toon_poolday',
sm_noblock: 1,
sm_playersvotes_version: '1.5.0',
sm_quakesounds_version: 1.8,
sm_resetscore_version: '2.6.0',
sm_show_damage_version: '1.0.7',
sm_vbping_version: 1.4,
sourcemod_version: '1.10.0.6482',
sv_accelerate: 5,
sv_airaccelerate: 10,
sv_allowminmodels: 1,
sv_alltalk: 1,
sv_bounce: 0,
sv_cheats: 0,
sv_competitive_minspec: 0,
sv_contact: '[email protected]',
sv_enableboost: 0,
sv_enablebunnyhopping: 0,
sv_footsteps: 1,
sv_friction: 4,
sv_gravity: 800,
sv_maxspeed: 320,
sv_maxusrcmdprocessticks: 24,
sv_noclipaccelerate: 5,
sv_noclipspeed: 5,
sv_nostats: 0,
sv_password: 0,
sv_pausable: 0,
sv_rollangle: 0,
sv_rollspeed: 200,
sv_specaccelerate: 5,
sv_specnoclip: 1,
sv_specspeed: 3,
sv_steamgroup: '',
sv_stepsize: 18,
sv_stopspeed: 75,
sv_tags: 'alltalk',
sv_voiceenable: 1,
sv_vote_quorum_ratio: 0.6,
sv_wateraccelerate: 10,
sv_waterfriction: 1,
tf_arena_max_streak: 3,
tf_arena_preround_time: 10,
tf_arena_round_time: 0,
tf_arena_use_queue: 1,
tv_enable: 0,
tv_password: 0,
tv_relaypassword: 0
}
Performs an A2A_PING query into the server
This is a deprecated feature of source servers, may not work. The
server.lastPing
property contains the server ping, so this is not necessary
A warn in console will be shown (you can disable it by using { enableWarns: false }
, see Options)
It will return -1
if the server did not respond to the query
server.ping()
.then(ping => {
console.log(ping); // 214
})
.catch(console.error)
Disconnect the server and destroy the socket
server.disconnect();
Use example
Server(...)
.then(async server => {
const players = await server.getPlayers();
server.disconnect();
//...
})
.catch(console.error);
Returns a promise that is resolved in an object with the server information, example:
const { Server } = require('@fabricio-191/valve-server-query');
Server.getInfo({
ip: '0.0.0.0',
port: 27015,
})
.then(console.log)
.catch(console.error);
MasterServer
Warns:
- Gold source master servers may not work. The reason is unknown, the servers simply do not respond to queries
- If the quantity is
Infinity
or'all'
, it will try to get all the servers, but most likely it will end up giving an warning (it will as many servers as possible). The master server stops responding at a certain point.
MasterServer({
quantity: 1000, // or Infinity or 'all'
region: 'US_EAST',
timeout: 3000,
})
.then(servers => {
//do something...
//servers is an array if 'ip:port' strings, see below
})
.catch(console.error);
Valid regions are
- US_EAST
- US_WEST
- SOUTH_AMERICA
- EUROPE
- ASIA
- AUSTRALIA
- MIDDLE_EAST
- AFRICA
- OTHER
[
'190.195.150.143:27015', '143.255.142.150:27015', '177.54.144.122:27523',
'189.1.173.26:27015', '177.144.128.13:27015', '177.66.222.92:27015',
'196.28.69.113:27065', '144.48.37.119:27015', '139.180.174.191:27051',
'108.61.168.31:27050', '108.61.168.31:27015', '108.61.168.31:27051',
'139.180.174.191:27052', '139.180.174.191:27053', '139.99.173.74:27015',
'45.121.210.91:27550', '139.99.131.105:27015', '139.99.144.39:27015',
'34.87.217.246:27015', '108.61.227.50:27025', '108.61.227.12:27035',
'220.240.1.134:27023', '221.121.159.236:35240', '221.121.159.236:35260',
'221.121.159.236:35250', '221.121.149.12:32860', '121.74.206.225:27015',
'41.190.141.250:27015', '111.221.44.137:27018', '111.221.44.137:27024',
'111.221.44.137:27023', '49.245.116.134:27017', '49.245.116.134:27025',
'49.245.116.134:27027', '49.245.116.134:27015', '49.245.116.134:27016',
'49.245.116.134:27026', '223.25.71.43:27015', '49.245.116.134:27115',
'49.245.116.134:27118', '49.245.116.134:27117', '49.245.116.134:27116',
'13.229.55.66:24000', '49.245.116.134:27215', '103.9.159.78:27065',
'75.85.184.227:27031', '75.85.184.227:27034', '168.235.81.229:27015',
'66.55.74.111:27015', '66.55.74.100:27015', '66.55.74.82:27015',
'66.55.74.38:27015', '66.55.74.103:27015', '66.55.68.38:27015',
'66.55.74.65:27015', '66.55.74.105:27015', '66.55.74.104:27015',
'66.55.68.36:27015', '66.55.70.53:27015', '64.111.99.165:27020',
'66.55.70.177:27115', '66.55.70.177:27315', '104.153.109.22:27015',
'104.153.109.26:27015', '47.153.235.28:27015', '173.199.84.186:27015',
'64.190.203.117:27015', '103.214.108.12:27105', '173.199.87.235:27025',
'8.3.6.148:27015', '66.75.2.253:27015', '172.107.198.173:27075',
'172.107.2.177:27035', '198.12.71.30:27015', '206.251.72.62:27017',
'104.207.148.159:27045', '92.38.148.25:27015', '159.89.142.219:27016',
'159.89.142.219:27015', '159.89.142.219:27017', '74.91.118.231:27015',
'108.61.124.77:27065', '192.53.126.95:27015', '108.61.124.78:27980',
'108.61.235.138:27015', '108.61.124.72:27045', '104.206.244.2:19001',
'198.24.171.83:27185', '131.153.29.243:27035', '173.27.92.73:27016',
'162.248.90.33:27015', '162.248.90.38:27015', '162.248.90.19:27015',
'66.58.130.164:27015', '71.193.199.206:27015', '71.193.199.206:27017',
'54.202.134.208:27015', '64.42.176.58:27015', '104.192.227.146:17741',
'74.201.72.18:27015',
...1051 more items
]
const filter = new MasterServer.Filter()
.addFlag('dedicated')
.add('map', 'cs_italy')
.addNOR(
new MasterServer.Filter()
.addFlag('secure')
);
MasterServer(
quantity: 1000,
region: 'EUROPE',
timeout: 3000,
filter,
})
.then(console.log)
.catch(console.error)
// Will return a list of ips that are dedicated servers, in the cs_italy map and that do not have an anti-cheat enabled
add(key, value)
adds a condition to the filter.
addFlag(flag)
adds a flag to the filter.
addFlags([flag, flag, ...])
adds multiple flags to the filter.
addNOR(filter)
a special condition, specifies that servers matching any of the filter
conditions should not be returned.
addNAND(filter)
a special condition, specifies that servers matching all of the filter
conditions should not be returned.
See https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol#Filter
| Parameter | Values | Description | | ------------------ | ------- | ----------------------------------------------------------------------------- | | name_match | string | Servers with their hostname matching that hostname (can use * as a wildcard) | | version_match | string | Servers running version that version (can use * as a wildcard) | | gameaddr | string | Return only servers on the specified IP address (port supported and optional) | | gamedir | string | Servers running the specified modification (ex. cstrike) | | map | string | Servers running the specified map (ex. cs_italy) | | appid | number | Servers that are running game with that appid | | napp | number | Servers that are NOT running game with that appid | | gametype | array | Servers with all of the given tag(s) in sv_tags | | gamedata | array | Servers with all of the given tag(s) in their 'hidden' tags (only in L4D2) | | gamedataor | array | Servers with any of the given tag(s) in their 'hidden' tags (only in L4D2) |
Notes:
- all array's are of strings.
flags
are not booleans. if you want to get the inverse of any of these flags, you should useaddNOR
oraddNAND
.
Flags
| Name | Description | | ---------- | --------------------------------------- | | dedicated | Servers that are dedicated servers | | secure | Servers using anti-cheat technology (VAC, but potentially others as well) | | linux | Servers running on a Linux platform | | password | Servers that are not password protected | | noplayers | Servers that are empty | | empty | Servers that are not empty | | full | Servers that are not full | | proxy | Servers that are spectator proxies | | white | Servers that are whitelisted | | collapse_addr_hash | Return only one server for each unique IP address matched |
MasterServer.getIPS()
.then(console.log)
.catch(console.error)
/*
Returns an object with the master servers ips, like this:
{
goldSource: [ '208.64.200.118', '208.64.200.117' ],
source: [ '208.64.200.65', '208.64.200.39', '208.64.200.52' ]
}
*/
See https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol#Master_servers
The port used in
hl2master.steampowered.com
(source) ip's is27011
but one of them is using a different port:27015
The port numbers used byhl1master.steampowered.com
(goldSource) can be anything between27010
and27013
.
RCON
Some commands will cause the server to disconnect, for example sv_gravity 0
in some cases or rcon_password newPassword
as it changes de password and needs to authenticate again.
const rcon = await RCON({
ip: '0.0.0.0',
port: 27015, //RCON port
password: 'your RCON password'
});
rcon.on('disconnect', async (reason) => {
console.log('disconnected', reason);
try{
await rcon.reconnect();
}catch(e){
console.log('reconnect failed', e.message);
}
});
rcon.on('passwordChanged', async () => {
const password = await getNewPasswordSomehow();
try{
await rcon.authenticate(password);
}catch(e){
console.error('Failed to authenticate with new password', e.message);
}
});
const response = await rcon.exec('sv_gravity 1000');
// Response is always a string that is some kind of log of the server or it can be empty
This will work well with server.getRules()
// gravity will change randomly every 5 seconds
setInterval(() => {
const value = Math.floor(Math.random() * 10000) - 3000;
//value will be a number between -3000 and 6999
rcon.exec(`sv_gravity ${value}`)
.catch(console.error);
}, 5000);
Destroys the RCON connection, this will not fire the disconnect
event
rcon.destroy();