Initial push
This commit is contained in:
104
lib/minecraft/sync.ts
Normal file
104
lib/minecraft/sync.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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<void> {
|
||||
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<typeof mcPlayers.$inferInsert>,
|
||||
): Promise<void> {
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(mcPlayers)
|
||||
.where(eq(mcPlayers.username, username))
|
||||
.get();
|
||||
|
||||
if (existing) {
|
||||
await db
|
||||
.update(mcPlayers)
|
||||
.set(data as Record<string, unknown>)
|
||||
.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,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user