import { NextResponse } from "next/server"; import { status } from "minecraft-server-util"; import { execSync } from "child_process"; import { MC_SERVER_IP, MC_SERVER_PORT } from "@/lib/constants"; import { sendCommand } from "@/lib/rcon"; import { memoAsync } from "@/lib/cache"; export const dynamic = "force-dynamic"; type StatusResult = { online: boolean; starting?: boolean; players: { online: number; max: number }; version?: string; motd?: string; }; async function probeStatus(): Promise { // Race protocol ping + RCON in parallel — whichever wins first signals "online" const ping = status(MC_SERVER_IP, MC_SERVER_PORT, { timeout: 3000 }).then( (r): StatusResult => ({ online: true, players: { online: r.players.online, max: r.players.max }, version: r.version.name, motd: r.motd.clean, }) ); const rcon = sendCommand("list").then((response): StatusResult => { const match = response.match(/There are (\d+) of a max of (\d+) players/); return { online: true, players: { online: match ? parseInt(match[1], 10) : 0, max: match ? parseInt(match[2], 10) : 20, }, }; }); try { return await Promise.any([ping, rcon]); } catch { // Both failed — check if process is up let starting = false; try { const out = execSync("systemctl is-active minecraft.service", { encoding: "utf8", }).trim(); starting = out === "active" || out === "activating"; } catch {} return { online: false, starting, players: { online: 0, max: 0 } }; } } export async function GET() { const result = await memoAsync("status", 3000, probeStatus); return NextResponse.json(result, { headers: { "Cache-Control": "public, max-age=3" }, }); }