import { NextRequest, NextResponse } from 'next/server' import { validateSession, hasPermission } from '@/lib/auth' import connectToDatabase from '@/lib/mongodb' import { Server } from '@/lib/models' import { isValidObjectId } from '@/lib/input-validation' import { getContainerByName, getServerPath } from '@/lib/docker' import { readFile } from 'fs/promises' import path from 'path' interface PlayerInfo { name: string uuid?: string isOp: boolean isWhitelisted: boolean isBanned: boolean isOnline: boolean } // GET /api/servers/[id]/players — List all known players export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const session = await validateSession(request) if (!session) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } if (!hasPermission(session, 'players:view')) { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) } const { id } = await params if (!isValidObjectId(id)) { return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 }) } await connectToDatabase() const server = await Server.findById(id) if (!server) { return NextResponse.json({ error: 'Server not found' }, { status: 404 }) } const serverDir = getServerPath(server._id.toString()) // Parse player lists from JSON files const whitelist = await readJsonFile(path.join(serverDir, 'whitelist.json')) const ops = await readJsonFile(path.join(serverDir, 'ops.json')) const bannedPlayers = await readJsonFile(path.join(serverDir, 'banned-players.json')) // Get online players via RCON let onlineNames: string[] = [] if (server.status === 'online') { try { const container = await getContainerByName(`mc-${server._id}`) if (container) { const exec = await container.exec({ Cmd: ['rcon-cli', 'list'], AttachStdout: true, }) const stream = await exec.start({ Detach: false }) let output = '' await new Promise((resolve) => { stream.on('data', (chunk: Buffer) => { output += chunk.slice(8).toString('utf-8') }) stream.on('end', resolve) setTimeout(resolve, 3000) }) // Parse "There are X of a max of Y players online: player1, player2" const match = output.match(/players online:(.*)/) if (match) { onlineNames = match[1].split(',').map(s => s.trim()).filter(Boolean) } } } catch { // RCON may not be available } } // Merge all sources const playerMap = new Map() for (const entry of whitelist) { const name = entry.name playerMap.set(name, { name, uuid: entry.uuid, isOp: false, isWhitelisted: true, isBanned: false, isOnline: onlineNames.includes(name), }) } for (const entry of ops) { const name = entry.name const existing = playerMap.get(name) if (existing) { existing.isOp = true } else { playerMap.set(name, { name, uuid: entry.uuid, isOp: true, isWhitelisted: false, isBanned: false, isOnline: onlineNames.includes(name), }) } } for (const entry of bannedPlayers) { const name = entry.name const existing = playerMap.get(name) if (existing) { existing.isBanned = true } else { playerMap.set(name, { name, uuid: entry.uuid, isOp: false, isWhitelisted: false, isBanned: true, isOnline: false, }) } } // Add online-only players not in any list for (const name of onlineNames) { if (!playerMap.has(name)) { playerMap.set(name, { name, isOp: false, isWhitelisted: false, isBanned: false, isOnline: true, }) } } const players = Array.from(playerMap.values()) return NextResponse.json({ success: true, data: players }) } catch (error) { console.error('Fetch players error:', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } async function readJsonFile(filePath: string): Promise> { try { const content = await readFile(filePath, 'utf-8') return JSON.parse(content) } catch { return [] } }