UNPKG

@shorkiedev/mc-status

Version:

A simple ESM package that can check multiple minecraft server status.

190 lines (164 loc) 5.8 kB
/** * JAVA EDITION SERVER */ import { Socket } from "node:net"; import { encodeVarInt, decodeVarInt } from "../utils/varInt.js"; import type { JavaServerStatus, JavaPingResponse } from "../types/mcTypes.js"; import { parseMotd, detectSoftware } from "../utils/mcParser.js"; import { resolveSRVRecord, queryJavaServer, tcpPing } from "../utils/netUtils.js"; const CACHE_TTL = 5000; const PING_COUNT = 3; const cache = new Map<string, { timestamp: number; data: JavaServerStatus }>(); async function singlePingJava(host: string, port = 25565, timeout = 5000, protocolVersion = 758): Promise<JavaServerStatus> { const { host: realHost, port: realPort } = await resolveSRVRecord(host, port); // Measure latency using tcpPing const pingResult = await tcpPing(realHost, realPort, timeout); if (!pingResult.online) { return { online: false, latency: null, motd: null, playersOnline: null, playersMax: null, version: null, software: "Unknown", plugins: [], levelName: null, }; } return new Promise((resolve) => { const socket = new Socket(); let called = false; const fail = () => { if (called) return; called = true; resolve({ online: false, latency: pingResult.latency, motd: null, playersOnline: null, playersMax: null, version: null, software: "Unknown", plugins: [], levelName: null, }); socket.destroy(); }; socket.setTimeout(timeout, fail); socket.once("error", fail); socket.connect(realPort, realHost, () => { try { const hostBuf = Buffer.from(realHost, "utf8"); const handshakeBody = Buffer.concat([ encodeVarInt(protocolVersion), encodeVarInt(hostBuf.length), hostBuf, Buffer.from([(realPort >> 8) & 0xff, realPort & 0xff]), encodeVarInt(1), ]); const handshakePacket = Buffer.concat([ encodeVarInt(handshakeBody.length + 1), Buffer.from([0x00]), handshakeBody, ]); socket.write(handshakePacket); socket.write(Buffer.from([0x01, 0x00])); } catch { fail(); } }); let buf = Buffer.alloc(0); socket.on("data", (data) => { buf = Buffer.concat([buf, data]); try { const len = decodeVarInt(buf, 0); const pid = decodeVarInt(buf, len.bytesRead); if (pid.value !== 0x00) return; const jsonLen = decodeVarInt(buf, len.bytesRead + pid.bytesRead); const startPos = len.bytesRead + pid.bytesRead + jsonLen.bytesRead; const endPos = startPos + jsonLen.value; if (buf.length < endPos) return; const json = JSON.parse(buf.subarray(startPos, endPos).toString("utf8")) as { description?: any; players?: { online?: number; max?: number }; version?: { name?: string; protocol?: number }; favicon?: string; modInfo?: any; }; if (!called) { called = true; const software = detectSoftware(json.version?.name, json.modInfo); const response: JavaPingResponse = { online: true, latency: pingResult.latency, // use tcpPing latency motd: parseMotd(json.description), players: { online: json.players?.online ?? null, max: json.players?.max ?? null, }, version: { name: json.version?.name ?? null, protocol: json.version?.protocol, }, favicon: json.favicon, software, plugins: [], levelName: null, }; resolve({ online: response.online, latency: response.latency, motd: response.motd, playersOnline: response.players.online, playersMax: response.players.max, version: response.version.name, software: response.software, plugins: response.plugins, levelName: response.levelName, }); socket.destroy(); } } catch { fail(); } }); }); } export async function getJavaServer(host: string, port = 25565, timeout = 5000): Promise<JavaServerStatus> { const key = `${host}:${port}`; const now = Date.now(); const cached = cache.get(key); if (cached && now - cached.timestamp < CACHE_TTL) return cached.data; const attempts: JavaServerStatus[] = []; for (let i = 0; i < PING_COUNT; i++) { const pingResult = await tcpPing(host, port, timeout); if (!pingResult.online) { attempts.push({ online: false, latency: null, motd: null, playersOnline: null, playersMax: null, version: null, software: "Unknown", plugins: [], levelName: null, }); continue; } const serverData = await singlePingJava(host, port, timeout); serverData.latency = pingResult.latency; attempts.push(serverData); } const online = attempts.filter(a => a.online); const avgLatency = online.length ? Math.round(online.reduce((a, b) => a + (b.latency ?? 0), 0) / online.length) : null; const final = online[0] ? { ...online[0], latency: avgLatency } : attempts[0]; const query = await queryJavaServer(host, port).catch(() => ({ levelName: null, plugins: [] })); final.levelName = query.levelName; final.plugins = query.plugins; cache.set(key, { timestamp: now, data: final }); return final; }