const dns = require('dns');
const dgram = require('dgram');
module.exports = {
clean: string => string.replace(/\^[^^]/g, ''),
isIP: ip => ip.match(/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/),
status: (address, port, callback, timeout = 1000) => {
if (!module.exports.isIP(address)) {
let resolved = false;
dns.lookup(address, (e, result) => {
if (!result && !resolved) {
resolved = true;
callback('Failed to resolve ' + address + '.', null);
} else if (!resolved) {
resolved = true;
module.exports.status(result, port, callback, timeout / 2);
}
});
setTimeout(() => {
if (!resolved) {
resolved = true;
callback('DNS resolve of ' + address + ' timed out.', null);
}
}, timeout / 2);
return;
}
const socket = dgram.createSocket('udp4');
socket.unref();
socket.on('message', (response, info) => {
if (info.address !== address || info.port !== port) {
return;
}
const header = 'statusResponse\n';
if (response.length <= 4 + header.length) {
_callback('Received truncated status response.', null);
return;
}
for (let i = 0; i < 4; i++) {
if (response[i] !== 0xff) {
_callback('Received an invalid header prefix in status response.', null);
return;
}
}
if (response.slice(4, header.length + 4).toString().toLowerCase() !== header.toLowerCase()) {
_callback('Received an invalid header in status response.', null);
return;
}
const lines = response.slice(4 + header.length, response.length - 1).toString().split('\n');
const cvars = lines[0].substring(1).split('\\');
const status = {
info: {},
clients: [],
};
for (let i = 0; i + 1 < cvars.length; i += 2) {
status.info[cvars[i].toLowerCase()] = cvars[i + 1];
}
for (let i = 1; i < lines.length; i++) {
let matches = lines[i].match(/^(\d+) (\d+) "(.+)"$/);
if (matches) {
status.clients.push({
name: module.exports.clean(matches[3]),
score: parseInt(matches[1]),
ping: parseInt(matches[2]),
});
}
}
if ('sv_hostname' in status.info) {
status.info.sv_hostname = module.exports.clean(status.info.sv_hostname);
_callback(null, status);
} else {
_callback('An invalid general status response received.', null);
}
});
const buffer = Buffer.from([0xff, 0xff, 0xff, 0xff, 0x67, 0x65, 0x74, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x0a]);
socket.send(buffer, 0, buffer.length, port, address, (error, bytes) => {
if (error) {
_callback(error, null);
}
});
const _callback = (error, response) => {
clearTimeout(_timeout);
if (callback) {
socket.removeAllListeners();
socket.close();
callback(error, response);
callback = null;
}
};
const _timeout = setTimeout(() => _callback('Status response timed out.', null), timeout).unref();
},
};