UNPKG

@rlqd/minecraft-server-util

Version:

A Node.js library for Minecraft servers that can retrieve status, perform queries, and RCON into servers.

131 lines (105 loc) 4.44 kB
import assert from 'assert'; import { clean, format, parse, toHTML } from '@rlqd/minecraft-motd-util'; import { TextDecoder } from 'util'; import TCPClient from './structure/TCPClient'; import { JavaStatusOptions } from './types/JavaStatusOptions'; import { JavaStatusLegacyResponse } from './types/JavaStatusLegacyResponse'; import { resolveSRV } from './util/srvRecord'; // Credit for this method is owed to fabianwennink <3 // https://github.com/fabianwennink/minecraft-server-util/blob/master/src/statusFE01All.ts const decoder = new TextDecoder('utf-16be'); export function statusLegacy(host: string, port = 25565, options?: JavaStatusOptions): Promise<JavaStatusLegacyResponse> { host = host.trim(); assert(typeof host === 'string', `Expected 'host' to be a 'string', got '${typeof host}'`); assert(host.length > 1, `Expected 'host' to have a length greater than 0, got ${host.length}`); assert(typeof port === 'number', `Expected 'port' to be a 'number', got '${typeof port}'`); assert(Number.isInteger(port), `Expected 'port' to be an integer, got '${port}'`); assert(port >= 0, `Expected 'port' to be greater than or equal to 0, got '${port}'`); assert(port <= 65535, `Expected 'port' to be less than or equal to 65535, got '${port}'`); assert(typeof options === 'object' || typeof options === 'undefined', `Expected 'options' to be an 'object' or 'undefined', got '${typeof options}'`); if (typeof options === 'object') { assert(typeof options.enableSRV === 'boolean' || typeof options.enableSRV === 'undefined', `Expected 'options.enableSRV' to be a 'boolean' or 'undefined', got '${typeof options.enableSRV}'`); assert(typeof options.timeout === 'number' || typeof options.timeout === 'undefined', `Expected 'options.timeout' to be a 'number' or 'undefined', got '${typeof options.timeout}'`); if (typeof options.timeout === 'number') { assert(Number.isInteger(options.timeout), `Expected 'options.timeout' to be an integer, got '${options.timeout}'`); assert(options.timeout >= 0, `Expected 'options.timeout' to be greater than or equal to 0, got '${options.timeout}'`); } } return new Promise(async (resolve, reject) => { const socket = new TCPClient(); const timeout = setTimeout(() => { socket?.close(); reject(new Error('Server is offline or unreachable')); }, options?.timeout ?? 1000 * 5); try { let srvRecord = null; if (typeof options === 'undefined' || typeof options.enableSRV === 'undefined' || options.enableSRV) { srvRecord = await resolveSRV(host); if (srvRecord) { host = srvRecord.host; port = srvRecord.port; } } await socket.connect({ host, port, timeout: options?.timeout ?? 1000 * 5 }); // Client to server packet // https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping#Client_to_server { socket.writeBytes(Uint8Array.from([0xFE, 0x01])); await socket.flush(false); } let protocolVersion; let versionName; let rawMOTD; let onlinePlayers; let maxPlayers; // Server to client packet // https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping#Server_to_client { const packetType = await socket.readByte(); if (packetType !== 0xFF) throw new Error('Packet returned from server was unexpected type'); const length = await socket.readUInt16BE(); const data = decoder.decode(await socket.readBytes(length * 2)); if (data[0] === '\u00A7' || data[1] === '1') { // 1.4+ server const split = data.split('\0'); protocolVersion = parseInt(split[1]); versionName = split[2]; rawMOTD = split[3]; onlinePlayers = parseInt(split[4]); maxPlayers = parseInt(split[5]); } else { // < 1.4 server const split = data.split('\u00A7'); protocolVersion = null; versionName = null; rawMOTD = split[0]; onlinePlayers = parseInt(split[1]); maxPlayers = parseInt(split[2]); } } socket.close(); clearTimeout(timeout); const motd = parse(rawMOTD); resolve({ version: versionName === null && protocolVersion === null ? null : { name: versionName, protocol: protocolVersion }, players: { online: onlinePlayers, max: maxPlayers }, motd: { raw: format(motd), clean: clean(motd), html: toHTML(motd) }, srvRecord }); } catch (e) { clearTimeout(timeout); socket?.close(); reject(e); } }); }