/** * Syncs Minecraft server data (players, plugins) into the local database * by parsing server logs and using RCON commands. */ import { db } from "@/lib/db"; import { mcPlayers, plugins } from "@/lib/db/schema"; import { rconClient } from "@/lib/minecraft/rcon"; import { eq } from "drizzle-orm"; import { nanoid } from "nanoid"; /** Parse the player list from the RCON "list" command response. */ export async function syncOnlinePlayers(): Promise { try { const response = await rconClient.sendCommand("list"); // Response format: "There are X of a max of Y players online: player1, player2" const match = response.match(/players online: (.*)$/); if (!match) return; const onlineNames = match[1] .split(",") .map((s) => s.trim()) .filter(Boolean); // Mark all players as offline first await db .update(mcPlayers) .set({ isOnline: false }) .where(eq(mcPlayers.isOnline, true)); // Mark online players for (const name of onlineNames) { const existing = await db .select() .from(mcPlayers) .where(eq(mcPlayers.username, name)) .get(); if (existing) { await db .update(mcPlayers) .set({ isOnline: true, lastSeen: Date.now() }) .where(eq(mcPlayers.username, name)); } } } catch { // RCON might not be connected — ignore } } /** Parse a log line and update player records accordingly. */ export function parseLogLine( line: string, onPlayerJoin?: (name: string) => void, onPlayerLeave?: (name: string) => void, ): void { // "[HH:MM:SS] [Server thread/INFO]: PlayerName joined the game" const joinMatch = line.match(/\[.*\]: (\w+) joined the game/); if (joinMatch) { const name = joinMatch[1]; upsertPlayer(name, { isOnline: true, lastSeen: Date.now() }); onPlayerJoin?.(name); return; } // "[HH:MM:SS] [Server thread/INFO]: PlayerName left the game" const leaveMatch = line.match(/\[.*\]: (\w+) left the game/); if (leaveMatch) { const name = leaveMatch[1]; upsertPlayer(name, { isOnline: false, lastSeen: Date.now() }); onPlayerLeave?.(name); return; } } async function upsertPlayer( username: string, data: Partial, ): Promise { const existing = await db .select() .from(mcPlayers) .where(eq(mcPlayers.username, username)) .get(); if (existing) { await db .update(mcPlayers) .set(data as Record) .where(eq(mcPlayers.username, username)); } else { await db.insert(mcPlayers).values({ id: nanoid(), uuid: (data as { uuid?: string }).uuid ?? nanoid(), // placeholder until real UUID is known username, firstSeen: Date.now(), lastSeen: Date.now(), isOnline: false, playTime: 0, isBanned: false, ...data, }); } }