UNPKG

@rlqd/minecraft-server-util

Version:

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

116 lines (92 loc) 4.25 kB
import assert from 'assert'; import { clean, format, parse, toHTML } from '@rlqd/minecraft-motd-util'; import UDPClient from './structure/UDPClient'; import { BedrockStatusOptions } from './types/BedrockStatusOptions'; import { BedrockStatusResponse } from './types/BedrockStatusResponse'; import { resolveSRV } from './util/srvRecord'; export function statusBedrock(host: string, port = 19132, options?: BedrockStatusOptions): Promise<BedrockStatusResponse> { 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 UDPClient(host, port); 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, 'udp'); if (srvRecord) { host = srvRecord.host; port = srvRecord.port; } } // Unconnected ping packet // https://minecraft.wiki/w/Java_Edition_protocol/Raknet_Protocol#Unconnected_Ping { socket.writeByte(0x01); socket.writeInt64BE(BigInt(Date.now())); socket.writeBytes(Uint8Array.from([0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78])); socket.writeInt64BE(BigInt(2)); await socket.flush(false); } // Unconnected pong packet // https://minecraft.wiki/w/Java_Edition_protocol/Raknet_Protocol#Unconnected_Pong { const packetType = await socket.readByte(); if (packetType !== 0x1C) throw new Error('Expected server to send packet type 0x1C, received ' + packetType); await socket.readInt64BE(); const serverGUID = await socket.readInt64BE(); await socket.readBytes(16); const responseLength = await socket.readInt16BE(); const response = await socket.readString(responseLength); const [edition, motdLine1, protocolVersion, version, onlinePlayers, maxPlayers, serverID, motdLine2, gameMode, gameModeID, portIPv4, portIPv6] = response.split(';'); const motd = parse(motdLine1 + (motdLine2 ? '\n' + motdLine2 : '')); socket.close(); clearTimeout(timeout); resolve({ edition, motd: { raw: format(motd), clean: clean(motd), html: toHTML(motd) }, version: { name: version, protocol: parseInt(protocolVersion) }, players: { online: parseInt(onlinePlayers), max: parseInt(maxPlayers) }, serverGUID, serverID, gameMode, gameModeID: parseInt(gameModeID), portIPv4: portIPv4 ? parseInt(portIPv4) : null, portIPv6: portIPv6 ? parseInt(portIPv6) : null, srvRecord }); } } catch (e) { clearTimeout(timeout); socket?.close(); reject(e); } }); }