// Game-Server-Monitor: pollt FiveM-kompatible HTTP-Endpoints (/dynamic.json) // und pflegt ein persistentes Status-Embed im Status-Kanal + Bot-Presence. // Setting "gameservers": kommagetrennt "Name=http://ip:30120, Name2=…" import { ActivityType, EmbedBuilder, Events } from 'discord.js'; import { getSetting, setSetting } from '../db.js'; const CHECK_INTERVAL_MS = 2 * 60 * 1000; const TIMEOUT_MS = 8000; const BRAND_YELLOW = 0xf5c518; function serverList() { return (getSetting('gameservers') ?? '') .split(',') .map((entry) => { const [name, ...rest] = entry.split('='); const url = rest.join('=').trim().replace(/\/$/, ''); return name?.trim() && /^https?:\/\//.test(url) ? { name: name.trim(), url } : null; }) .filter(Boolean); } /** Einen Server abfragen (FiveM-Format: clients, sv_maxclients, hostname) */ async function queryServer(server) { try { const res = await fetch(`${server.url}/dynamic.json`, { signal: AbortSignal.timeout(TIMEOUT_MS), headers: { 'User-Agent': 'd4rkbot-monitor/1.0' }, }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); return { ...server, online: true, players: Number(data.clients) || 0, max: Number(data.sv_maxclients) || 0, map: data.mapname ?? null, }; } catch { return { ...server, online: false, players: 0, max: 0, map: null }; } } /** Status-Embed bauen */ function buildEmbed(results) { const lines = results.map((r) => r.online ? `🟢 **${r.name}** — ${r.players}/${r.max} Spieler${r.map ? ` · ${r.map}` : ''}` : `🔴 **${r.name}** — offline` ); return new EmbedBuilder() .setColor(BRAND_YELLOW) .setTitle('🎮 Server-Status') .setDescription(lines.join('\n')) .setFooter({ text: 'D4RKST3R // STATUS • aktualisiert alle 2 min' }) .setTimestamp(); } /** Ein Poll-Durchlauf — exportiert für Tests und den Timer */ export async function monitorTick(client) { const servers = serverList(); const channelId = getSetting('status_channel_id'); if (servers.length === 0) return; const results = await Promise.all(servers.map(queryServer)); // Bot-Presence: Gesamtspielerzahl (nur wenn mindestens ein Server online) const online = results.filter((r) => r.online); const totalPlayers = online.reduce((sum, r) => sum + r.players, 0); try { client.user?.setActivity?.( online.length > 0 ? `${totalPlayers} Spieler online` : 'Server offline', { type: ActivityType.Watching } ); } catch { /* Presence ist nice-to-have */ } // Persistentes Embed im Status-Kanal pflegen (einmal posten, danach editieren) if (!channelId) return; const channel = await client.channels.fetch(channelId).catch(() => null); if (!channel?.isTextBased()) return; const embed = buildEmbed(results); const messageId = getSetting('status_message_id'); if (messageId) { const existing = await channel.messages.fetch(messageId).catch(() => null); if (existing) { await existing.edit({ embeds: [embed] }); return; } } const message = await channel.send({ embeds: [embed] }); setSetting('status_message_id', message.id); } export function startServerMonitor(client) { setInterval(() => monitorTick(client).catch((e) => console.error('[monitor] Fehler:', e)), CHECK_INTERVAL_MS); client.once(Events.ClientReady, () => monitorTick(client).catch(() => {})); }