Wo der Bot nicht hinkommt, fragt das Panel -- Typ panel
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s

Bisher entschied `x.gamedig` darueber, ob aus einem Panel-Server ein
Monitor-Eintrag wird: "kennt unsere Bibliothek dieses Spiel?" Das hat
einen Fall ausgesperrt, den es wirklich gibt -- TeamSpeak.

UND ZWAR NICHT, WEIL GAMEDIG ES NICHT KOENNTE. Am 16.08.2026
nachgeschlagen, nachdem die Annahme "gamedig kennt TeamSpeak nicht"
sich als falsch erwies: gamedig 5.3.3 hat `teamspeak3`, DiscordGSM
fuehrt es ebenfalls. Der Grund ist ein anderer und ein besserer:

    node_modules/gamedig/protocols/teamspeak3.js
      queryPort = options.teamspeakQueryPort || 10011
      'use port=<voice>' -> 'serverinfo' -> 'clientlist'

Es braucht den SERVERQUERY-Port. Beim Betreiber liegt der hier:

    9987/udp  -> 0.0.0.0:9987      Sprache, oeffentlich
    10011/tcp -> 127.0.0.1:9988    ServerQuery, NUR localhost
    30033/tcp -> 0.0.0.0:9989      Dateien, oeffentlich

Der Bot laeuft im Container und erreicht 127.0.0.1 des Hosts nie. Zwei
Gegenproben vom Host aus, beide gescheitert (die zweite auch deshalb,
weil gamedig ohne `teamspeakQueryPort` stur 10011 nimmt -- den es dort
gar nicht gibt). Eine Kennung einzutragen brauechte also einen OFFENEN
Verwaltungsport, und das ist genau das, was die Regel "kein Port geht
ohne Eintrag ins Netz" verhindert.

Das Panel dagegen laeuft auf dem Host, darf hin und hat die
Zugangsdaten -- und schickt seine eigene Messung seit heute im
Statusbericht mit. Also:

  - `taugt` fragt `zeigbar` statt `gamedig`: "kann UEBERHAUPT JEMAND
    diesen Server abfragen -- wir oder das Panel fuer uns?" Mit
    Rueckfall auf `gamedig` fuer ein aelteres Panel; ohne den
    verschwaenden nach diesem Update alle Eintraege, und zwar lautlos.
  - Neuer eigener Typ `panel` neben `fivem` und `http`. Er ersetzt
    nichts: wo gamedig kann, bleibt es bei gamedig -- dessen Antwort
    bringt Karte, Fassung und Ping mit, die das Panel nicht liefert.
  - `spieler === null` heisst KEINE ANTWORT, nicht null Spieler. Das
    Panel sichert dazu einen Grund zu; wir werfen damit und landen im
    selben Zweig wie ein gescheiterter gamedig-Query, statt eine 0 zu
    erfinden. Und kein Ping: die Zahl ist eine gespeicherte Messung des
    Panels, keine Antwortzeit von hier.

EINE FALLE BEIM ZWEITEN PARAMETER: `servers.map(queryServer)` haette
`panelItems` den INDEX untergeschoben -- der erste Server saehe null,
alle weiteren eine Zahl, an der `panelEintrag` still scheitert. Also
`map((s) => queryServer(s, panelItems))`.

Probe erweitert und im neu gebauten Abbild gelaufen, alles gruen --
einschliesslich "null Spieler sind eine Antwort, kein Ausfall".
This commit is contained in:
2026-08-16 12:01:24 +02:00
parent e39d4b33b6
commit c81e862992
3 changed files with 144 additions and 6 deletions
+49 -4
View File
@@ -18,7 +18,7 @@ const RED = 0xf23f43;
// standen mal ausgedachte wie "arkse" oder "terraria", die es dort gar nicht
// gibt. Beim Ändern gegen `games` aus gamedig prüfen, nicht gegen das Gefühl.
export const GAME_ICONS = {
fivem: '🚗', http: '🌐', minecraft: '⛏️', mbe: '⛏️',
fivem: '🚗', http: '🌐', minecraft: '⛏️', mbe: '⛏️', panel: '🛰️',
teamfortress2: '🎩', counterstrike2: '🔫', csgo: '🔫', garrysmod: '🔧',
rust: '🪓', valheim: '🛡️', palworld: '🐑', ase: '🦖', asa: '🦖', pixark: '🦖',
dayz: '🧟', projectzomboid: '🧟', sdtd: '🧟',
@@ -27,9 +27,13 @@ export const GAME_ICONS = {
};
// fivem und http sind unsere eigenen Typen — gamedig kennt sie nicht.
// `panel` ist der dritte, und er ist anders als die beiden: da fragt der Bot
// gar nicht, sondern das Panel hat schon gefragt und schickt die Zahl mit.
// Siehe `queryServer`.
const EIGENE = {
fivem: { name: 'FiveM', port: 30120 },
http: { name: 'HTTP-Check', port: null },
panel: { name: 'Über das Panel', port: null },
};
// Protokolle, die ohne Zugangs-Code gar nicht antworten. Der Code steht im
@@ -73,8 +77,15 @@ const alertState = new Map(); // id → { fails, down, since }
// Letztes Query-Ergebnis pro Server — für die öffentliche /server-Seite
export const lastResults = new Map(); // id → Query-Ergebnis + checkedAt
/** Einen Server abfragen — fivem/http direkt, alles andere über gamedig */
export async function queryServer(server) {
/**
* Einen Server abfragen — fivem/http direkt, `panel` gar nicht, alles andere
* über gamedig.
*
* `panelItems` ist der Statusbericht des Panels aus DIESEM Durchlauf. Er ist
* optional: fehlt er, verhält sich alles wie vorher, und ein `panel`-Eintrag
* gilt als offline — mit Grund.
*/
export async function queryServer(server, panelItems = null) {
const base = {
...server, online: false, players: null, max: null, map: null,
playerNames: [], ping: null, version: null, mods: null,
@@ -84,6 +95,34 @@ export async function queryServer(server) {
};
const started = Date.now();
try {
// ================== DAS PANEL HAT SCHON GEFRAGT ====================
//
// Kein Netzverkehr von hier. Für TeamSpeak wäre er sogar unmöglich:
// gamedig braucht dafür den ServerQuery-Port, und der liegt beim
// Betreiber absichtlich auf `127.0.0.1` — der Bot im Container kommt
// dort nie hin. Das Panel läuft auf dem Host, darf hin und hat die
// Zugangsdaten. Also fragt es, und wir lesen ab.
//
// `spieler === null` HEISST „KEINE ANTWORT", NICHT NULL SPIELER. Das
// Panel garantiert dazu einen `spieler_grund` — entweder eine Zahl
// oder ein Grund, nie beides leer. Genau diese Zusage macht das hier
// sauber: wir werfen mit dem Grund und landen im selben Zweig wie ein
// gescheiterter gamedig-Query, statt eine 0 zu erfinden.
if (server.type === 'panel') {
const p = panelEintrag(panelItems, server);
if (!p) throw new Error('Das Panel kennt diesen Server nicht (mehr).');
if (p.spieler == null) throw new Error(p.spieler_grund || 'Das Panel nennt keine Zahl.');
return {
...base, online: true,
players: p.spieler,
max: p.spieler_max ?? null,
spielname: p.spiel ?? null,
// KEIN PING. Die Zahl ist eine gespeicherte Messung des Panels
// (bis zu zehn Minuten alt) und keine Antwortzeit von hier.
// Eine Millisekundenzahl hinzuschreiben wäre erfunden.
ping: null,
};
}
if (server.type === 'fivem') {
const res = await fetch(`${server.query_url.replace(/\/$/, '')}/dynamic.json`, {
signal: AbortSignal.timeout(tuningMs.seconds('monitor_timeout')),
@@ -406,7 +445,13 @@ export async function monitorTick(client) {
const servers = listGameservers();
if (servers.length === 0) return;
const results = await Promise.all(servers.map(queryServer));
// NICHT `servers.map(queryServer)` -- so stand es hier, und mit dem
// zweiten Parameter wäre es eine Falle gewesen: `map` reicht (element,
// index, array) weiter, `panelItems` bekäme also die Zahl 0. Der erste
// Server hätte damit `null` gesehen und alle weiteren eine Zahl, an der
// `panelEintrag` still scheitert -- ein Fehler, der nur beim zweiten
// Eintrag auftritt und nach Zufall aussieht.
const results = await Promise.all(servers.map((s) => queryServer(s, panelItems)));
// Die Zusatzauskunft vom Panel: EINMAL je Durchlauf, nicht je Server.
// Sie ist optional -- ist nichts eingetragen oder antwortet das Panel
+26 -2
View File
@@ -33,9 +33,28 @@ const uebernehmenStmt = db.prepare('UPDATE gameservers SET panel_name = ? WHERE
* sie noch einmal, weil ein Bericht von einem älteren Panel kommen kann und
* ein halber Eintrag schlimmer ist als keiner: er stünde dauerhaft auf
* offline und schlüge Alarm.
*
* ================== WARUM NICHT MEHR `x.gamedig` ==========================
*
* Hier stand `x.gamedig`, also: „kennt unsere Bibliothek dieses Spiel?" Das
* hat einen Fall ausgesperrt, den es wirklich gibt — TeamSpeak. Nicht weil
* gamedig es nicht könnte (es hat `teamspeak3`), sondern weil der
* ServerQuery-Port des Betreibers absichtlich auf `127.0.0.1` liegt: der Bot
* läuft im Container und kommt dort nie hin. Am 16.08.2026 gegengeprüft,
* beide Versuche vom Host aus gescheitert.
*
* Das Panel dagegen sitzt auf dem Host, darf dorthin und hat die Zugangsdaten
* — es schickt seine eigene Messung seither im Statusbericht mit. Gefragt
* wird deshalb `zeigbar`: „kann ÜBERHAUPT JEMAND diesen Server abfragen —
* wir über gamedig oder das Panel für uns?"
*
* `?? Boolean(x.gamedig)` ist der Rückfall für ein ÄLTERES Panel, das
* `zeigbar` noch nicht kennt. Ohne ihn verschwänden nach einem Bot-Update
* schlagartig alle Einträge, und zwar lautlos.
*/
export function taugt(x) {
return Boolean(x?.discord && x.gamedig && x.adresse && x.port);
const zeigbar = x?.zeigbar ?? Boolean(x?.gamedig);
return Boolean(x?.discord && zeigbar && x.adresse && x.port);
}
/**
@@ -86,7 +105,12 @@ export function abgleichen(items) {
createGameserver({
name: x.name,
type: x.gamedig,
// OHNE gamedig-KENNUNG FRAGEN WIR DAS PANEL. Der Typ `panel` ist
// unser dritter eigener neben `fivem` und `http` — siehe
// `queryServer`. Wo gamedig kann, bleibt es bei gamedig: dessen
// Antwort bringt Karte, Fassung und Ping mit, die das Panel nicht
// liefert. Der neue Typ schliesst eine Luecke, er ersetzt nichts.
type: x.gamedig || 'panel',
host: x.adresse,
port: x.port,
address: `${x.adresse}:${x.port}`,