UNPKG

minecraft-status

Version:

Node.js module for gathering information about Minecraft Java Edition servers and Minecraft Bedrock Edition servers.

270 lines (212 loc) 7.46 kB
const net = require("net"); const iconv = require("iconv-lite"); const varint = require("varint"); /* * Server List Ping * https://wiki.vg/Server_List_Ping * */ class MinecraftServerListPing { /* * Current * https://wiki.vg/Server_List_Ping#Current * */ static ping(protocol = 4, host, port = 25565, timeout = 3000) { return new Promise( (resolve, reject) => { const client = net.createConnection(port, host); client.setTimeout(timeout); client.on("timeout", (/* error */) => { client.destroy( ); reject( new Error("The client timed out while connecting to " + host + ":" + port) ); }); client.on("error", (error) => { reject(error); }); client.on("connect", ( ) => { // Packet ID const packetBuffer = Buffer.from([0x00]); // Protocol Version const protocolBuffer = Buffer.from( varint.encode(protocol) ); // Server Address const hostLengthBuffer = Buffer.from( varint.encode(host.length) ); const hostBuffer = Buffer.from(host); // Server Port const portBuffer = Buffer.alloc(2); portBuffer.writeUInt16BE(port); // Next state const stateBuffer = Buffer.from([0x01]); // Handshake const dataBuffer = Buffer.concat([packetBuffer, protocolBuffer, hostLengthBuffer, hostBuffer, portBuffer, stateBuffer]); const dataLengthBuffer = Buffer.from( varint.encode(dataBuffer.length) ); const handshakeBuffer = Buffer.concat([dataLengthBuffer, dataBuffer]); client.write(handshakeBuffer); // Request const requestBuffer = Buffer.from([0x01, 0x00]); client.write(requestBuffer); }); // Response let responseDataBuffer = Buffer.alloc(0); client.on("data", (responseBuffer) => { responseDataBuffer = Buffer.concat([responseDataBuffer, responseBuffer]); let responseDataBufferLength; // Error: Invalid try { responseDataBufferLength = varint.decode(responseDataBuffer); } catch(error) { return; } // Error: Too short if(responseDataBuffer.length < responseDataBufferLength - varint.decode.bytes) return; let offset = varint.decode.bytes; // Packet ID varint.decode(responseDataBuffer, offset); offset += varint.decode.bytes; // JSON Length varint.decode(responseDataBuffer, offset); offset += varint.decode.bytes; try { const response = JSON.parse( responseDataBuffer.toString("utf-8", offset) ); resolve(response); } catch(error) { reject(error); } client.end( ); }); }); } /* * 1.6 * https://wiki.vg/Server_List_Ping#1.6 * */ static ping16(protocol = 73, host, port = 25565, timeout = 3000) { return new Promise( (resolve, reject) => { const client = net.createConnection(port, host); client.setTimeout(timeout); client.on("timeout", (/* error */) => { client.destroy( ); reject( new Error("The client timed out while connecting to " + host + ":" + port + ".") ); }); client.on("error", (error) => { reject(error); }); client.on("connect", ( ) => { // FE — packet identifier for a server list ping // 01 — server list ping's payload (always 1) // FA — packet identifier for a plugin message // 00 0B — length of following string, in characters, as a short (always 11) // 00 4D 00 43 00 7C 00 50 00 69 00 6E 00 67 00 48 00 6F 00 73 00 74 — the string MC|PingHost encoded as a UTF-16BE string let requestBuffer = Buffer.from([0xFE, 0x01, 0xFA, 0x00, 0x0B, 0x00, 0x4D, 0x00, 0x43, 0x00, 0x7C, 0x00, 0x50, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x67, 0x00, 0x48, 0x00, 0x6F, 0x00, 0x73, 0x00, 0x74]); // length of the rest of the data, as a short. Compute as 7 + len(hostname), where len(hostname) is the number of bytes in the UTF-16BE encoded hostname const dataLengthBuffer = Buffer.alloc(2); dataLengthBuffer.writeUInt16BE(7 + 2 * host.length); // protocol version const protocolBuffer = Buffer.alloc(1); protocolBuffer.writeUInt8(protocol); // length of following string, in characters, as a short const stringLengthBuffer = Buffer.alloc(2); stringLengthBuffer.writeUInt16BE(host.length); // hostname the client is connecting to, encoded as a UTF-16BE string const hostBuffer = iconv.encode(host, "utf16-be"); // port the client is connecting to, as an int const portBuffer = Buffer.alloc(4); portBuffer.writeUInt32BE(port); // Request requestBuffer = Buffer.concat([requestBuffer, dataLengthBuffer, protocolBuffer, stringLengthBuffer, hostBuffer, portBuffer]); client.write(requestBuffer); }); // Response client.on("data", (responseBuffer) => { const dataBuffer = iconv.decode(responseBuffer.slice(9, responseBuffer.length), "utf-16be"); const data = dataBuffer.toString( ).split("\0"); resolve({ "version": { "name": data[1], "protocol": parseInt(data[0]) }, "players": { "max": parseInt(data[4]), "online": parseInt(data[3]) }, "description": data[2] }); }); }); } /* * 1.4 to 1.5 * https://wiki.vg/Server_List_Ping#1.4_to_1.5 * */ static ping15(host, port = 25565, timeout = 3000) { return new Promise( (resolve, reject) => { const client = net.createConnection(port, host); client.setTimeout(timeout); client.on("timeout", (/* error */) => { client.destroy( ); reject( new Error("The client timed out while connecting to " + host + ":" + port) ); }); client.on("error", (error) => { reject(error); }); client.on("connect", ( ) => { // Request const requestBuffer = Buffer.from([0xFE, 0x01]); client.write(requestBuffer); }); // Response client.on("data", (responseBuffer) => { const dataBuffer = iconv.decode(responseBuffer.slice(9, responseBuffer.length), "utf-16be"); const data = dataBuffer.toString( ).split("\0"); resolve({ "version": { "name": data[1], "protocol": parseInt(data[0]) }, "players": { "max": parseInt(data[4]), "online": parseInt(data[3]) }, "description": data[2] }); }); }); } /* * Beta 1.8 to 1.3 * https://wiki.vg/Server_List_Ping#Beta_1.8_to_1.3 * */ static ping13(host, port = 25565, timeout = 3000) { return new Promise( (resolve, reject) => { const client = net.createConnection(port, host); client.setTimeout(timeout); client.on("timeout", (/* error */) => { client.destroy( ); reject( new Error("The client timed out while connecting to " + host + ":" + port) ); }); client.on("error", (error) => { reject(error); }); client.on("connect", ( ) => { // Request const requestBuffer = Buffer.from([0xFE]); client.write(requestBuffer); }); // Response client.on("data", (responseBuffer) => { const dataBuffer = iconv.decode(responseBuffer.slice(3, responseBuffer.length), "utf-16be"); const data = dataBuffer.toString( ).split("\u00A7"); resolve({ "players": { "max": parseInt(data[2]), "online": parseInt(data[1]) }, "description": data[0] }); }); }); } } module.exports = MinecraftServerListPing;