import { Rcon } from 'rcon-client'; const rconConnections = new Map(); const playerCache = new Map(); const CACHE_TTL = 30000; // 30 seconds async function getConnection(server) { const key = `${server.host}:${server.rconPort}`; if (rconConnections.has(key)) { const conn = rconConnections.get(key); if (conn.authenticated) { return conn; } rconConnections.delete(key); } const rcon = await Rcon.connect({ host: server.host, port: server.rconPort, password: server.rconPassword, timeout: 5000 }); rcon.on('error', (err) => { console.error(`RCON error for ${key}:`, err.message); rconConnections.delete(key); }); rcon.on('end', () => { rconConnections.delete(key); }); // Handle socket errors to prevent crash if (rcon.socket) { rcon.socket.on('error', (err) => { console.error(`RCON socket error for ${key}:`, err.message); rconConnections.delete(key); }); } rconConnections.set(key, rcon); return rcon; } export async function sendRconCommand(server, command) { if (!server.rconPassword) { throw new Error('RCON password not configured'); } const rcon = await getConnection(server); return await rcon.send(command); } export async function getPlayers(server) { const cacheKey = `${server.id}-count`; const cached = playerCache.get(cacheKey); if (cached && Date.now() - cached.time < CACHE_TTL) { return cached.data; } try { let result = { online: 0, max: null }; if (server.type === 'minecraft') { const response = await sendRconCommand(server, 'list'); const match = response.match(/There are (\d+) of a max of (\d+) players online/); if (match) { result = { online: parseInt(match[1]), max: parseInt(match[2]) }; } } else if (server.type === 'factorio') { const response = await sendRconCommand(server, '/players online count'); const match = response.match(/(\d+)/); if (match) { result = { online: parseInt(match[1]), max: null }; } } else if (server.type === 'zomboid') { const response = await sendRconCommand(server, 'players'); // Format: "Players connected (X):" or "Players connected (X): Player1, Player2" const match = response.match(/Players connected \((\d+)\)/); if (match) { result = { online: parseInt(match[1]), max: null }; } } else if (server.type === 'vrising') { const response = await sendRconCommand(server, 'listusers'); // Count lines that contain player info const lines = response.split('\n').filter(l => l.trim() && !l.includes('listusers')); result = { online: lines.length, max: null }; } playerCache.set(cacheKey, { data: result, time: Date.now() }); return result; } catch (err) { console.error(`Failed to get players for ${server.id}:`, err.message); // Clear cache on error - server might be offline playerCache.delete(cacheKey); return { online: 0, max: null }; } } export async function getPlayerList(server) { const cacheKey = `${server.id}-list`; const cached = playerCache.get(cacheKey); if (cached && Date.now() - cached.time < CACHE_TTL) { return cached.data; } try { let players = []; if (server.type === 'minecraft') { const response = await sendRconCommand(server, 'list'); // Format: "There are X of a max of Y players online: Player1, Player2, Player3" const colonIndex = response.indexOf(':'); if (colonIndex !== -1) { const playerPart = response.substring(colonIndex + 1).trim(); if (playerPart) { players = playerPart.split(',').map(p => p.trim()).filter(p => p); } } } else if (server.type === 'factorio') { const response = await sendRconCommand(server, '/players online'); // Format: "Online players (X):\n player1 (online)\n player2 (online)" const lines = response.split('\n'); for (const line of lines) { const match = line.match(/^\s*(\S+)\s*\(online\)/); if (match) { players.push(match[1]); } } } else if (server.type === 'zomboid') { const response = await sendRconCommand(server, 'players'); // Format: "Players connected (X): \n-Player1\n-Player2\n-Player3" const lines = response.split('\n'); for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith('-')) { players.push(trimmed.substring(1)); } } } else if (server.type === 'vrising') { const response = await sendRconCommand(server, 'listusers'); // Parse player names from listusers output const lines = response.split('\n').filter(l => l.trim() && !l.includes('listusers')); players = lines.map(l => l.trim()).filter(p => p); } const result = { players }; playerCache.set(cacheKey, { data: result, time: Date.now() }); return result; } catch (err) { console.error(`Failed to get player list for ${server.id}:`, err.message); // Clear cache on error - server might be offline playerCache.delete(cacheKey); return { players: [] }; } }