mirror of
https://github.com/rmoren97/mc-manager.git
synced 2026-02-10 17:40:30 -08:00
159 lines
4.5 KiB
TypeScript
159 lines
4.5 KiB
TypeScript
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<void>((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<string, PlayerInfo>()
|
|
|
|
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<Array<{ name: string; uuid?: string }>> {
|
|
try {
|
|
const content = await readFile(filePath, 'utf-8')
|
|
return JSON.parse(content)
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|