Commit Graph
139 Commits
Author SHA1 Message Date
D4rkst3randClaude Opus 5 89f6dfcd29 Radio: Webradio im Sprachkanal, 44 Sender zum Durchschalten
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
/radio holt den Bot in den Sprachkanal und postet eine Tafel: Auswahlmenue
mit allen Sendern, Stopp, und ein Embed mit dem laufenden Titel, das sich
alle 30 Sekunden selbst nachfuehrt.

Der Titel kommt aus dem Icecast-Strom selbst (Icy-MetaData). Bewusst so und
nicht ueber eine Sender-API: das funktioniert bei jedem Strom, den jemand
eintraegt, und nicht nur bei den drei, die ich kenne. Leere Titel-Bloecke
heissen "unveraendert", deshalb werden bis zu drei gelesen.

Die Tonkette braucht keine Opus-Bibliothek: ffmpeg holt das MP3 und gibt
direkt Ogg/Opus aus, prism-media packt nur noch aus. Gemessen: 600 Pakete in
fuenf Sekunden. Sonst muesste @discordjs/opus mit ins Image, und das will
gebaut werden. Neu im Image ist nur ffmpeg.

44 RauteMusik-Sender sind ab Werk eingetragen — dieselben, die im LS25 und
ETS als Bordradio laufen. Die Liste ist nicht abgeschrieben, sondern
gemessen: jede Adresse einzeln angefragt, der Name kommt aus icy-name.
Doppelgaenger sind raus (deutschrap-charts und wackenradio liefern denselben
Strom wie deutschrap und metal). Eingetragen wird einmal, solange die Liste
leer ist — wer loescht, behaelt es geloescht.

Ein Auswahlmenue fasst nur 25 Eintraege. Bei 44 Sendern haette ein Deckel
die letzten 19 stillschweigend verschluckt; stattdessen mehrere Menues,
benannt nach ihrem ersten und letzten Sender.

Der Bot geht raus, wenn der Kanal leer ist, und kommt nach einem Neustart in
den Kanal zurueck — sonst beendet jeder Deploy die Musik endgueltig.

Kein YouTube. Genau daran sind Groovy und Rythm gestorben, und was davon
uebrig ist, ist ein Dauerlauf gegen kaputte Extraktoren.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 11:40:15 +02:00
D4rkst3randClaude Opus 5 b7f54185f7 fix: ein schon von Hand eingetragener Server wird uebernommen, nicht uebergangen
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Der Betreiber hat im Panel den Schalter fuer atm11-test umgelegt und im
Discord passierte nichts. Er hat den Fehler bei sich gesucht -- ob er den
Namen falsch geschrieben habe. Hat er nicht: der Server stand schon von
Hand im Monitor, weil ich ihn vorhin selbst dort eingetragen hatte, und
der Abgleich uebersprang ihn mit `continue`, damit nicht zwei Embeds fuer
denselben Server im Kanal stehen.

Das Ueberspringen war richtig, das Schweigen nicht. Wer den Schalter
umlegt, sagt "dieser Server gehoert ans Panel" -- also bekommt der
bestehende Eintrag jetzt den Herkunftsvermerk, statt ignoriert zu werden.
Ein zweites Embed entsteht dabei nicht.

Der Preis gehoert benannt und wird mitgeprueft: ab der Uebernahme nimmt
ein Ausschalten den Eintrag mit, samt Bild und Links, die von Hand daran
haengen. Das ist der Sinn des Schalters, aber es ist eine Uebertragung
von Besitz.

Ueberschrieben wird weiterhin kein Feld -- die Uebernahme setzt nur
panel_name.

Und die Meldung im Protokoll unterscheidet jetzt "neu" von "uebernommen";
vorher hiess beides "uebernommen", was genau die Auskunft verwischt haette,
um die es hier geht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 19:13:58 +02:00
D4rkst3randClaude Opus 5 0982e8cd1b feat: Server aus dem Panel uebernehmen -- und nur die auch wieder loeschen
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Im d4rk_gameserver-Panel bekommt jeder Server einen Schalter "Im Discord
anzeigen". Das Panel schreibt dafuer NICHTS hierher: es setzt nur ein
Haekchen, und das reist in dem Statusbericht mit, den der Monitor ohnehin
alle 30 Sekunden abholt. Der Bot legt den Eintrag dann selbst an.

Der umgekehrte Weg -- Panel ruft unsere API -- braeuchte dort ein
Zeichen mit Schreibrecht. So bleibt es bei einem, das nur lesen kann.

ZWEI FALLEN, beide beim Bauen aufgefallen:

Der Abgleich lief zu spaet. In monitorTick stand `if (servers.length ===
0) return` VOR dem Panel-Abruf; beim allerersten Server haette der
Schalter also nichts getan, und zwar stillschweigend. Der Abruf steht
jetzt davor.

Loeschen braucht einen Besitzvermerk. Ohne den nimmt ein Haekchen im
Panel den handgepflegten ATM10 mit -- die einzige verfuegbare Grundlage
waere der Name, und beim ersten Server, der in beiden Werkzeugen gleich
heisst, waere der Eintrag samt Bild, Links und Zugangs-Code weg. Neue
Spalte `panel_name`, leer heisst "von Hand". Die UPDATE-Anweisung fasst
sie nicht an, damit ein Bearbeiten im Webinterface die Herkunft behaelt.

Und der Abgleich ueberschreibt nichts: angelegt und geloescht wird, mehr
nicht.

tools/panel-abgleich-pruefen.mjs prueft das auf einer EIGENEN Datenbank
in /tmp und bricht ab, wenn sie es nicht ist -- eine Probe, die loeschen
kann, darf nicht dort loeschen, wo es zaehlt. Alles gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 19:08:43 +02:00
D4rkst3randClaude Opus 5 21eab627b9 fix: die Panel-Anbindung steht jetzt dort, wo man sie sucht
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Der Betreiber hat sie nicht gefunden, und das zu Recht: die zwei Felder
gab es nur auf der Modul-Detailseite unter Module -> Game-Server-Monitor.
Der Reiter "Game-Server" hat aber einen eigenen, handgeschriebenen Block
mit Status- und Alarm-Kanal -- und genau dort schaut nach, wer den
Monitor einrichtet.

Also stehen sie jetzt auch dort, direkt unter den Kanaelen, mit dem
Absatz, der erklaert wofuer das gut ist. Die Modulseite behaelt sie; es
sind dieselben Formularwerte, keine zweite Wahrheit -- genauso wie
status_channel_id schon an beiden Stellen steht.

Und ins Suchregister (setting-index.js), sonst findet die Suche in der
Seitenleiste das Feld nicht und man sucht es mit den Augen. Das Register
traegt selbst den Kommentar, dass ein vergessener Eintrag genau so
endet.

Geprueft, indem die Frontend-Stufe des Images gebaut und im Ergebnis
nachgesehen wurde -- "Panel-Adresse", "Panel-Zeichen" und
"Panel-Anbindung speichern" stehen im ausgelieferten Bundle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 18:38:52 +02:00
D4rkst3r d6124fc2cd feat: der Monitor fragt das Panel, wenn eine Abfrage nicht ausreicht
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
gamedig FRAGT einen Server. Ein startendes Modpack antwortet nicht, und
nach zwei Fehlversuchen stand im Alarm-Kanal eine Ausfallmeldung, obwohl
niemand etwas kaputtgemacht hat -- am ATM10 des Betreibers gemessen jedes
Mal 306 Sekunden Fehlalarm.

Das d4rk_gameserver-Panel SIEHT den Container, statt ihn zu fragen. Neu
ist deshalb `src/panel.js`: Zustand, Fertig-Merkmal, RAM und CPU kommen
als Zusatzauskunft dazu, einmal je Durchlauf abgerufen und 20 Sekunden
zwischengespeichert.

Was das im Alarm-Kanal aendert:

  startet gerade   kein Alarm, aber nur innerhalb einer Gnadenfrist von
                   15 Minuten. Ohne diese Grenze fraesse die Anbindung
                   genau den Alarm, fuer den es sie gibt -- `bereit`
                   wird nie von allein wahr, ein haengender Start bliebe
                   sonst fuer immer stumm.
  im Panel gestoppt  kein Alarm (Code 0/143/137)
  abgestuerzt      ALARM, mit Code im Embed
  OOM-Kill         ALARM, und beim Namen genannt statt als Absturz
                   getarnt -- bei Modpacks die haeufigste Ursache
  Container weg    ALARM

Der Zaehler wird beim Unterdruecken NICHT zurueckgesetzt: ein bereits
gemeldeter Ausfall bleibt gemeldet, sonst verschluckt ein Stopp im Panel
die spaetere "wieder online"-Entwarnung und im Kanal bliebe ein Alarm
ohne Aufloesung stehen.

ES IST DURCHGEHEND OPTIONAL. Adresse und Zeichen stehen im Webinterface
unter "Server" und NICHT in der .env; ohne Eintrag -- und ebenso, wenn
das Panel nicht antwortet -- verhaelt sich der Monitor exakt wie vorher.
Das Zeichen darf nur lesen, damit auch ein verlorenes niemandem einen
Server stoppen kann.

`tools/panel-pruefen.mjs` prueft die Logik ohne Discord und ohne
Datenbank; deshalb laedt panel.js die Einstellungen erst beim Aufruf.
2026-08-13 18:05:24 +02:00
D4rkst3randClaude Opus 5 bce75f8c65 docs: Befund 1 geschlossen -- die Sicherung verlaesst die Maschine
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
backup_channel_id ist gesetzt. Vorher nachgesehen, ob der Bot den Kanal auch
bedienen KANN -- ein eingetragener Kanal, in dem er nicht posten darf, sieht
von aussen genauso aus wie ein richtig eingetragener:

    Kanal  #backup-upload-kanal   Textkanal, richtige Gilde
    Bot    D4rk DevBot            VIEW_CHANNEL, SEND_MESSAGES, ATTACH_FILES

Dann von Hand ausgeloest, und der ganze Weg lief durch -- inklusive der heute
gebauten Gegenpruefung:

    [backup] d4rkbot-2026-08-12.db.gz erstellt (1021 KB),
             geprueft: 55 Tabellen, 89 Einstellungen
    last_backup 2026-08-12T13:54:44Z, last_backup_ok 1, kein Fehler

Der Zaehler der Einstellungen ist zwischen zwei Laeufen von 87 auf 89
gewachsen: das sind die Laeufe selbst, die last_backup schreiben. Ein huebscher
Beleg, dass wirklich das neue Archiv geoeffnet wurde und nicht ein altes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 15:55:41 +02:00
D4rkst3randClaude Opus 5 6057bdb0d7 docs: Befund 1 -- was gebaut wurde und was ausserhalb des Repos liegt
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Die Abholung laeuft taeglich um 03:30 -- nach der Sicherung des Bots (ab 03:00)
und vor der von d4rk_media (04:30), damit nicht zwei Vorgaenge gleichzeitig auf
derselben Platte arbeiten.

Ueber die geplante Aufgabe gemessen, nicht von Hand: Ergebnis 0, Archiv auf
Platte Nr. 1 statt Nr. 0, Pruefsumme identisch, 55 Tabellen und 86
Einstellungen im ausgepackten Archiv, last_zweitziel steht in der Datenbank.

Zwei Dinge liegen ausserhalb des Repos und stehen deshalb im Bericht, damit sie
nach einem Neuaufsetzen nicht fehlen: die Arbeitskopie unter
Desktop\d4rkbot (vorher gab es gar keine, der Bot lief nur aus Portainers
Checkout) und die Aufgabe "d4rkbot Sicherung abholen". Sie startet pwsh ueber
den stabilen Ausfuehrungsalias und nicht ueber den Pfad mit Versionsnummer --
genau daran hing bei d4rk_media eine Sicherung, die beim naechsten
PowerShell-Update aufgehoert haette zu laufen.

Offen bleibt: beide Kopien liegen im SELBEN RECHNER. Gegen einen Plattenausfall
hilft das jetzt, gegen Feuer oder eine verschluesselte Maschine nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 15:19:22 +02:00
D4rkst3randClaude Opus 5 720785c175 backup: die Sicherung verlaesst das Volume -- tools/abholen.ps1 plus Wachhund
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Befund 1 aus docs/befunde-2026-08-12.md, der wichtigste: Datenbank und alle
vierzehn Sicherungen lagen im selben Volume. Geht das Volume verloren, geht
beides zusammen verloren -- das ist keine Sicherung, das ist eine Kopie.

tools/abholen.ps1 SICHERT NICHT. Das Archiv baut der Bot selbst; hier wird nur
abgeholt, und zwar so, dass am Ende feststeht, dass die Kopie heil ist und
nicht nur, dass ein Kopierbefehl keinen Fehler geworfen hat:

  1. neuestes Archiv im Container finden (und meckern, wenn es aelter als
     24 Stunden ist -- ein Skript, das treu das Archiv von vorletzter Woche
     abholt und "fertig" sagt, ist schlimmer als eines, das gar nicht laeuft)
  2. docker cp heraus
  3. Pruefsumme IM CONTAINER gegen die Kopie hier -- nicht die Dateigroesse:
     eine abgebrochene Kopie auf eine volle Platte hat oft genau die richtige
     Laenge und trotzdem Nullen am Ende
  4. auspacken und die Datenbank darin oeffnen, mit dem Bot-Abbild, weil dort
     better-sqlite3 schon liegt; integrity_check plus Tabellen zaehlen
  5. Bericht als last_zweitziel zurueck in die Einstellungen
  6. ausduennen

Und es warnt, wenn es DIESELBE PLATTE ist. Verglichen wird die physische Platte
und nicht der Laufwerksbuchstabe: zwei Partitionen derselben NVMe sterben
zusammen.

Echter Lauf, kein Trockentest:

    d4rkbot-2026-08-12.db.gz   993.332 Bytes
    andere Platte: Nr. 1 statt Nr. 0
    identisch (ff08516d293d...)
    in der Sicherung: 55 Tabellen, 86 Einstellungen

DAZU EIN ZWEITER WACHHUND, pruefeZweitziel(). Ein Skript auf dem Wirt, das
still aufhoert zu laufen -- Aufgabe deaktiviert, Laufwerk weg, Pfad geaendert --
waere derselbe Schaden noch einmal, nur eine Ebene weiter aussen. Er meldet nur,
wenn die Abholung schon einmal lief: fehlt der Eintrag ganz, ist sie nicht
eingerichtet, und daraus taeglich eine Meldung zu machen waere Naergelei.

Gegen eine Kopie der Datenbank durchgespielt:

    40 h alt   -> "alt:17863965"
    nochmal    -> unveraendert          (keine Wiederholung)
    Fehlschlag -> "fehler:1786540570974"
    wieder gut -> ""                    (Entwarnung)

Nebenbei gelernt, zum zweiten Mal in diesem Projekt: die Kopie der Datenbank
ohne -wal war leer an der Stelle, die zaehlte -- last_zweitziel stand noch im
Write-Ahead-Log. Dieselbe Falle wie beim Zurueckspielen von d4rk_media.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 15:17:01 +02:00
D4rkst3randClaude Opus 5 40ede9c56a docs: Richtigstellung -- der Auto-Deploy funktioniert, ich war zu ungeduldig
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Zwei falsche Diagnosen hintereinander, beide aus einem Zwischenstand
geschlossen, waehrend der Vorgang noch lief. Was wirklich passiert ist:

    12:48:18   Push 93156e5
    12:48      Portainer hat die Dateien im Stack-Verzeichnis
    12:57      hier gemessen: Abbild 15 h alt, Container von gestern
               -> daraus "er baut nicht" geschlossen. FALSCH.
    13:01:02   Abbild neu gebaut
    13:01:14   Container neu gestartet
    13:02      pruefeSicherung im Container: 2, melden.js da, Panel HTTP 200

Zwischen Push und laufendem neuen Code liegen rund DREIZEHN MINUTEN -- Portainer
fragt in Intervallen nach, nicht sofort. Die Messung um 12:57 fiel mitten in
dieses Fenster: die Dateien waren schon da, das Abbild noch nicht neu. Aus dem
Schnappschuss habe ich einen Systemfehler gemacht.

Das ist genau der Fehler, gegen den der Bericht geschrieben ist -- eine
plausible Erklaerung, die zum Schnappschuss passt, behauptet, bevor der Vorgang
zu Ende war.

pull_policy: build bleibt drin, aber NICHT mehr aus dem Grund im Commit davor.
Es repariert nichts; es macht das Neubauen nur ausdruecklich, damit ein
handgetipptes `docker compose up -d` dasselbe tut wie die Automatik. Folgenlos
entfernbar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 15:03:18 +02:00
D4rkst3randClaude Opus 5 b98f4e15c9 fix: pull_policy build -- der Auto-Deploy zog, baute aber nie
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Der erste Verdacht ("kein Runner") war die halbe Wahrheit. Portainers
GitOps-Weg funktioniert, nachgemessen in seinem eigenen Datenverzeichnis:

    /d/compose/1/src/melden.js     12:48   <- die Uhrzeit des Pushes
    /d/compose/1/stack.env         12:57   <- Portainer hat neu geschrieben
    docker images d4rkbot:latest   00:21   <- das Abbild 15 Stunden alt

Portainer holt den neuen Stand also brav ins Stack-Verzeichnis und ruft dann
docker compose up -d. UND DAS BAUT NUR, WENN DAS ABBILD FEHLT. d4rkbot:latest
gab es -- also kein Bau, kein neuer Container, und der Bot lief weiter mit dem
Code von vorgestern.

Kein Runner, kein Webhook, keine Fehlermeldung: es sah nach "nichts zu tun"
aus. Das ist die unangenehme Sorte Fehler -- man pusht, hakt es ab, und der
Fehler, den man gerade behoben hat, laeuft weiter.

pull_policy: build laesst compose bei jedem Ausrollen neu bauen. Geprueft mit
docker compose config (v5.3.1 nimmt es an und gibt es aufgeloest zurueck).

Einmal noch von Hand, denn die Zeile wirkt erst, wenn sie selbst ausgerollt
ist: Portainer -> Stack ecobot -> Pull and redeploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 15:00:51 +02:00
D4rkst3randClaude Opus 5 b63d4cae3b docs: der Auto-Deploy hat nicht ausgeloest -- gemessen, nicht vermutet
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Nach dem Push von 93156e5 nachgesehen, ob der Bot den neuen Stand bekommt:

    Push registriert          updated_at 12:48:18Z
    Workflow-Laeufe           "total_count": 0     <- keiner, nie
    Act-Runner auf dem Wirt   kein Container
    d4rkbot neu gestartet?    nein, laeuft seit 11.08. 22:21
    Code im Container         grep pruefeSicherung -> 0

has_actions ist am Repo AN, es gibt also nur niemanden, der die Laeufe
abarbeitet -- und der Webhook-Weg, der ohne Runner auskaeme, ist offenbar auch
nicht eingerichtet.

Pushen allein rollt also nichts aus. Das gehoert aufgeschrieben, weil ein
Deploy, von dem man GLAUBT dass er laeuft, schlimmer ist als gar keiner: man
pusht, hakt es ab, und der Fehler, den man gerade behoben hat, laeuft weiter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:57:41 +02:00
D4rkst3randClaude Opus 5 93156e5205 backup: gegenpruefen, nachholen, und endlich Bescheid sagen
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Befund 2, 3 und 4 aus docs/befunde-2026-08-12.md. Befund 1 bleibt offen, Befund
5 war falsch und ist zurueckgezogen.

DER BOT SAGT JETZT BESCHEID. src/melden.js ist neu und enthaelt, was vorher in
watchdog.js eingeschlossen war: dmAdmin. Genau deshalb hat alles andere im Bot
geschwiegen oder in die Konsole geschrieben -- und eine Konsolenzeile in einem
Container liest niemand.

Dazu dmAdminEinmalig: meldet nur bei ZUSTANDSWECHSEL. Sonst wuerde eine
Pruefung, die alle zehn Minuten laeuft, denselben Ausfall alle zehn Minuten
melden, und nach der dritten DM liest man sie nicht mehr. Der Merker steht in
den Einstellungen und nicht im Arbeitsspeicher -- ein Bot, der nach jedem
Update neu startet, haette sonst nach jedem Update wieder eine frische Meinung.
Gemessen gegen eine KOPIE der Datenbank, sechs Schritte, alle wie beabsichtigt.

DER WACHHUND AUF last_backup. pruefeSicherung() meldet, wenn die letzte
Sicherung aelter als 26 Stunden ist, wenn sie fehlgeschlagen ist, oder wenn es
nie eine gab. Im Fehlerfall steht NICHT last_backup als Zeitpunkt im Embed: der
wird nur bei Erfolg gesetzt, dort staende also der letzte GUTE Lauf, und das
liesse die Sicherung frischer aussehen als sie ist.

DER TAG FAELLT NICHT MEHR AUS. Vorher "if (now.getHours() !== 3) return" -- wer
waehrend dieser einen Stunde unten war, hatte den Tag verloren, und ein Bot,
der nach jedem Update neu startet, ist genau dieser Fall. Jetzt zaehlt nur: es
ist nach 03:00 und heute war noch keine.

DAS ARCHIV WIRD AUSGEPACKT UND GEZAEHLT. integrity_check plus Tabellenzahl
gegen die laufende Datenbank. Faellt das durch, wird das Archiv GELOESCHT (im
Sicherungsordner saehe es sonst aus wie eine Sicherung), die Rotation laeuft
nicht, und last_backup bleibt stehen.

An echten Archiven gemessen, mit zwei Gegenproben, damit die Pruefung nicht nur
"ja" sagen kann:

    2026-08-10  817 KB  54 Tabellen  86 ms
    2026-08-11  886 KB  54 Tabellen  64 ms
    2026-08-12  970 KB  55 Tabellen  78 ms   (laufend: 55)
    halbes gzip -> "unexpected end of file"
    Muell       -> "incorrect header check"

Die 54 gegen 55 sind kein Fehler, sondern eine gewachsene Tabelle zwischen dem
11. und dem 12. Verglichen wird zeitgleich, also stoert das nicht -- wissen
sollte man es, bevor jemand alte Archive gegen die heutige Zahl haelt.

BEFUND 5 WAR FALSCH. pruneMessageCache wird sehr wohl aufgerufen, taeglich, in
mod-tools.js:239 -- und stand schon zum Zeitpunkt der Durchsicht dort. Gezaehlt
wurden "Aufrufe ausserhalb von db.js", und die Sammel-Importzeile ist als
Import durchgegangen, waehrend der Aufruf zwanzig Zeilen tiefer nicht mitkam.
Das ist genau der Fehler, den zu vermeiden dieser Bericht dasteht; die anderen
vier sind deshalb einzeln nachgemessen worden und stimmen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:47:09 +02:00
D4rkst3randClaude Opus 5 1808b7c40b docs: Durchsicht -- die Sicherung liegt neben dem Original, und niemand merkt ihren Ausfall
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Gesucht wurde nach denselben Fehlermustern, die beim Bau von d4rk_media
aufgefallen sind. 24105 Zeilen liest niemand am Stueck; gesucht wurde gezielt
nach nie aufgerufenen Aufraeumfunktionen, Wachen an '*', SQL mit eingesetzten
Zeichenketten, fetch auf fremde Adressen, ungelesenen Antwortkoerpern,
Schleifen mit Zeitlimit und Sicherungen ohne Gegenprobe.

AN DIESEM BOT WURDE NICHTS GEAENDERT. Die Entscheidungen gehoeren dem
Betreiber.

DER WICHTIGSTE BEFUND: Datenbank (5,0 MB) und alle vierzehn Sicherungen
(5,7 MB) liegen im SELBEN Volume ecobot_ecobot_data. Geht es verloren, geht
beides zusammen verloren -- das ist keine Sicherung, das ist eine Kopie. Der
eingebaute Ausweg (Upload in einen privaten Discord-Kanal) laeuft nie, weil
backup_channel_id nicht gesetzt ist.

DAZU: niemand prueft last_backup. Es wird geschrieben und im Panel ANGEZEIGT,
aber nichts vergleicht es mit heute. Und weil der Zeitplan auf getHours() === 3
steht, faellt der Tag still aus, wenn der Bot waehrend dieser Stunde unten ist
-- bei einem Bot, der nach jedem Update neu startet, kein Sonderfall. Die
Maschinerie dafuer ist vollstaendig da (dmAdmin, brandEmbed, Incidents) und
wird nicht benutzt.

WEITER: "zu gross fuer Discord" endet in einem console.warn, das niemand liest;
die Archive wachsen um rund 70 KB je Tag auf eine Grenze von 9 MB zu. Das
Archiv wird nie ausgepackt und gegengeprueft. Und pruneMessageCache wird
nirgends aufgerufen -- exakt dasselbe Muster wie pruneEvents in d4rk_media,
heute mit 18 Zeilen harmlos.

WAS GEPRUEFT WURDE UND IN ORDNUNG IST, damit es niemand ein zweites Mal prueft:
db.backup() statt Dateikopie ist richtig und WAL-sicher; das gefaehrlich
aussehende ORDER BY ${ord} ist eine feste Weissliste; die oeffentliche
Serverliste zaehlt ihre Felder einzeln auf; die zwei bekannten Geheimnisse
gehen nur als Ja/Nein hinaus; der Spielserver-Monitor nutzt bereits
Promise.all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 01:26:36 +02:00
D4rkst3randClaude Opus 5 5ac079699c watchdog: sagen WARUM, nebeneinander pruefen, Gespenster aufraeumen
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
DER GRUND STEHT JETZT DABEI. Bisher landete im Ausfall-Embed nur "HTTP 503".
Das sagt, DASS etwas ist, nicht WAS -- und wer nachts eine DM bekommt, will
genau das wissen, bevor er sich einloggt.

Viele Dienste sagen es naemlich selbst. d4rk_media antwortet auf /status mit
einer 503 UND einer Begruendung, und die geht jetzt mit in die Meldung UND in
die Stoerung (startIncident nimmt einen Grund entgegen -- damit steht er in der
Historie der oeffentlichen Statusseite und nicht nur einmalig in einer DM):

    vorher   HTTP 503
    jetzt    HTTP 503 — sicherung: letzte vor 40 Stunden

Gemessen am laufenden Dienst, nicht ausgedacht.

Gelesen wird nur bei content-type JSON und hoechstens 64 KB; der Satz wird bei
240 Zeichen gekappt. Erkannt werden erst unsere eigene Form (eine Liste von
Pruefungen, die durchgefallenen werden genannt), dann die ueblichen Felder
error/message/reason/grund/detail, zuletzt ein blosser Zustand wie "degraded".
Wer nichts davon liefert, bekommt weiterhin "HTTP nnn" -- kein Fehler, nur
keine Zusatzauskunft. Zehn Faelle durchgeprueft, darunter kaputtes JSON, kein
Objekt und null.

DAS URTEIL BLEIBT DER STATUSCODE. Ein Dienst, der 200 mit "ok": false
antwortet, gilt weiterhin als erreichbar -- alles andere waere eine stille
Verhaltensaenderung fuer jede andere ueberwachte Adresse.

Nebenbei bekommt der Rumpf damit endlich eine Behandlung: er wird gelesen oder
verworfen. Ein fetch, dessen Antwort niemand anfasst, haelt die Verbindung, bis
der Aufraeumer sie holt.

NEBENEINANDER STATT NACHEINANDER. Die Schleife lief der Reihe nach, und jede
Adresse durfte bis zum Zeitlimit brauchen: bei fuenf Diensten und zehn Sekunden
Grenze konnte ein Durchlauf fast eine Minute dauern. Bei zwei Minuten Intervall
ist das knapp, bei zehn Diensten ueberholt sich der Waechter selbst. Geprueft
wird jetzt gleichzeitig, AUSGEWERTET weiter der Reihe nach -- die DMs sollen in
nachvollziehbarer Ordnung ankommen und nicht in der, in der zufaellig
geantwortet wurde.

GESPENSTER AUFRAEUMEN. `state` wurde nie aufgeraeumt: ein aus der Dienst-Tabelle
geloeschter Eintrag blieb bis zum Neustart im Speicher und damit in der
Uebersicht im Panel stehen. Was nicht mehr ueberwacht wird, fliegt jetzt am Ende
jedes Durchlaufs raus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 00:19:58 +02:00
D4rkst3randClaude Opus 5 1ef06f747c Modliste: melden was sich geaendert hat, einzeln laden
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Bisher gab es fuer Mods nur einen Link auf das Sammelpaket des Hosters — vier
Gigabyte, auch wenn sich zwei Dateien geaendert haben. Jetzt liest der Bot die
Modliste aus der Statusabfrage, die er ohnehin holt, und meldet im Kanal, was
neu, aktualisiert oder entfernt wurde. Beim ersten Durchlauf bleibt es still,
sonst kaeme eine Meldung ueber 110 "neue" Mods.

Geaendert heisst: andere Version oder anderer Hash. Beides einzeln reicht
nicht — manche Modder bessern nach, ohne die Version zu erhoehen.

Dazu die Seite /mods/<server>: jeder Mod mit Version und Groesse, Suche ueber
Titel, Dateiname und Autor, und ein Download je Zeile. Der laeuft durch den
Bot, damit der Zugangs-Code des Spielservers nicht in einem Link landet — mit
dem Code liesse sich auch der ganze Spielstand lesen. Angefragt wird nur, was
wirklich in der Modliste steht; ueber den Dateinamen kommt man an nichts
anderes heran.

Groessen lernt der Bot beim Weiterleiten. Der Spielserver beantwortet kein
HEAD (501) und ignoriert Range, es gibt also keinen billigen Weg, sie vorher
zu erfahren — und 4 GB nur fuers Anzeigen zu holen waere keiner.

Nebenbei gefunden und behoben: 'server.mods' und 'commands.tag' gab es je
zweimal in den Sprachdateien. Der spaetere Eintrag gewinnt still, deshalb
stand auf dem neuen Knopf woertlich "%s Mods (110)" und auf der Befehlsseite
die Beschreibung von /tag statt der Kopfzeile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:22:55 +02:00
D4rkst3randClaude Opus 5 9b681754fa Ein Embed je Hof, das sich selbst aktualisiert
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Fuhrpark stand bisher nur als Gesamtzahl im Karten-Embed. Jetzt bekommt
jeder Hof eine eigene Nachricht im selben Kanal, die bei jedem Durchlauf
bearbeitet statt neu gepostet wird: Land, Fahrzeuge mit Wert und
Betriebsstunden, was in die Werkstatt muss, was gewaschen gehoert und was
geladen ist.

Die Fahrzeuge kommen aus vehicles.xml, blockweise gelesen — Verschleiss und
Schmutz stehen in Kindelementen und gehoeren sonst dem falschen Fahrzeug.
Gemietete Missionsfahrzeuge bleiben draussen, sie verfaelschen Anzahl und
Wert. Faellt die Datei einmal aus, bleiben die Embeds stehen, statt dass ein
Hof verschwindet und gleich darauf doppelt gepostet wird.

Damit tauchen auch Hoefe auf, die noch kein Land gekauft haben: die Liste der
Hoefe kommt aus Parzellen und Fuhrpark zusammen.

Dazu raus, was nur den Kanal vollgeschrieben hat: die Meldung zum
Monatswechsel und die zum Landkauf. Der Monat wird still weitergestellt — er
steht ohnehin im Embed. Damit faellt der Ereignis-Kanal weg und mit ihm die
gespeicherte Besitzliste.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:53:02 +02:00
D4rkst3randClaude Opus 5 0875dff127 Server-Embed aufgeraeumt: volle Reihen statt halber
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Discord setzt drei Inline-Felder in eine Reihe. Es waren fuenf — Status,
Spieler, Map, Version, Mods —, also drei plus zwei, und die zweite Reihe stand
halb leer. Bei einem Server, der weniger meldet, stand ein einzelnes Feld
allein herum.

Neu sortiert nach dem, was man wissen will:

Der Zustand ist die Ueberschrift, kein Datenpunkt. Er steht jetzt in der
Beschreibung, eine Zeile mit Trennpunkten: online, Schloss, Ping. Das spart
eine ganze Feldreihe und liest sich als Satz. Der Ping ist damit aus der
Fusszeile raus, wo er zwischen Marke und Intervall stand.

Die Auslastung ist die Zahl, wegen der man hinschaut — sie bekommt die volle
Breite statt eines Drittels, und der Balken wurde von zehn auf sechzehn
Zeichen breiter.

Map, Version und Mods bilden genau eine Reihe. Fehlt eins davon, fuellen
unsichtbare Felder auf, damit die Reihe nicht schief steht.

Adresse und alles, was Discord nicht klickbar macht — fivem://, steam://,
ts3server:// —, standen als je eigener Codeblock untereinander. Das war eine
Wand aus Kaesten. Jetzt ein Block "Zum Kopieren" mit allem drin.

Nebenbei: fuer den Namen im Spiel hatte ich Discords Kleintext-Auszeichnung
genommen. Die rendert in Embeds nicht verlaesslich, und dann staende das
Steuerzeichen woertlich da. Kursiv tut es auch.

Geprueft: drei Zusammenstellungen im Klartext durchgespielt (LS mit allem,
FiveM mit wenig, offline), und die Auffuellung mit einem, zwei und drei
Feldern sowie ohne.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:25:58 +02:00
D4rkst3randClaude Opus 5 ea523b76b3 Servername aus dem Spiel und ein Schloss fuer Passwort oder Whitelist
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Name im Panel ist der, den DU vergibst — wie der Server sich im Spiel
nennt, stand nirgends. Steht jetzt darunter, aber nur wenn es etwas anderes
ist als der eingetragene Name; sonst haette dieselbe Zeile zweimal dagestanden.

Beim Schloss war die Frage, wie viel die Abfrage ueberhaupt hergibt. Nachgesehen
statt geraten: 31 der 358 gamedig-Protokolle melden ein Passwort. valve
gehoert dazu, deckt also CS2, Rust, ARK, Squad, 7DTD, Zomboid, Gmod und DayZ
ab. Minecraft nicht, Farming Simulator auch nicht — im LS-XML kommt das Wort
Passwort kein einziges Mal vor. Und eine Whitelist meldet grundsaetzlich kein
Spiel, die steht in einer Datei auf dem Server.

Also beides: automatisch, wo es geht, und eine Einstellung, die es schlaegt.
Wer "Whitelist" eintraegt, bekommt sie angezeigt, auch wenn der Server
schweigt.

Wichtig dabei ist der dritte Zustand. "Sagt nichts" ist nicht "offen" —
deshalb `null` und kein `false`. Ein Schloss, das bei jedem schweigenden
Server fehlt, waere eine Aussage, die wir nicht belegen koennen; umgekehrt
waere ein Schloss ohne Grundlage eine Luege. Bei unbekannt steht schlicht
nichts da.

Geprueft: Einstellung schlaegt Messung in allen vier Faellen, automatisch mit
true/false/null, fehlender Server, unbekannter Wert faellt auf automatisch
zurueck statt zu sperren, Schloss im Embed-Titel nur wenn belegt, Spielname
nur bei echtem Unterschied (auch mit Leerzeichen drumherum). Panel und
/server-Seite im laufenden Frontend angesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:12:33 +02:00
D4rkst3randClaude Opus 5 4ce3c10d76 Kein Monatswechsel mehr aus einer kaputten Antwort
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Bot rief einen neuen Monat aus, obwohl im Spiel keiner war. Zwei Fehler,
die zusammen genau das ergeben:

statsLesen las die Uhrzeit als `Number(server.dayTime) || 0`. Eine Antwort
ohne dayTime — Server startet neu, Proxy schiebt eine Fehlerseite dazwischen,
XML halb geschrieben — wurde damit zu 0. Und tagUmgeschlagen prueft auf
`jetzt < vorher`. Null ist kleiner als jeder Vortagswert, also sah jede
kaputte Antwort wie Mitternacht aus.

Beides behoben: fehlt die Angabe, ist sie jetzt null und nicht 0 — "weiss
nicht" ist kein Tageswechsel. Und ein Wechsel muss die Form eines echten
Mitternachtssprungs haben: vom spaeten Abend in den fruehen Morgen, beide
Werte innerhalb eines Tages. Ein Zappeln um ein paar Minuten faellt jetzt
durch. Eine Antwort ohne Uhrzeit ueberschreibt ausserdem den letzten guten
Stand nicht mehr, sonst ginge der echte Sprung danach verloren.

Beim Nachmessen zeigte sich, dass meine Doku an der Stelle falsch war: dayTime
ist nicht live, sondern ein Schnappschuss. Fuenf Abrufe ueber zwei Minuten mit
einem Spieler online ergaben denselben Wert (37028620) — der Server schreibt
das XML im "Web API Interval" neu, hier alle 360 Sekunden. Steht jetzt richtig
in docs/ls-feed.md, samt dem verbleibenden Restrisiko: ein Serverneustart
setzt die Uhr auf den gespeicherten Stand zurueck und kann in seltenen Faellen
einen Monat zu viel zaehlen.

Geprueft: echter Sprung, normaler Tagesverlauf, Erstlauf, fehlende Angabe als
0/null/NaN, kleiner Ruecksprung, Ruecksprung am Vormittag, unsinnige Groessen,
und dass eine fehlende dayTime im XML als null ankommt statt als 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 13:05:21 +02:00
D4rkst3randClaude Opus 5 b69cb6c5e4 Freie Links je Server statt fester Felder
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
connect_url und mods_url waren zwei Spalten fuer zwei Zwecke — und beim
naechsten Zweck waere eine dritte dazugekommen. Jetzt gibt es eine Liste:
Beschriftung plus Adresse, so viele wie noetig. Regeln, TeamSpeak, Shop,
Bewerbung.

Als JSON-Spalte, nicht als eigene Tabelle: es haengt an genau einem Server,
wird immer komplett gelesen und immer komplett geschrieben. Gelesen wird es als
Liste, geschrieben darf es als Liste hereinkommen — better-sqlite3 kann kein
Array binden und wuerde sonst werfen. Kaputtes JSON ergibt eine leere Liste
statt eines Absturzes im Monitor.

Discord nimmt fuenf Knoepfe je Reihe; ab jetzt werden sie umgebrochen, bei
zwei Reihen ist Schluss. Die Grenze steht im Code und nicht im Fehler beim
Senden, den niemand sieht. Zu lange Beschriftungen werden auf 80 Zeichen
gekuerzt, halbe Zeilen fliegen raus statt als leerer Knopf zu erscheinen. Was
nicht http(s) ist — ts3server://, steam:// —, wird wie bisher ein Codeblock im
Embed zum Kopieren.

Dabei umbenannt: das Embed-Feld fuer einen nicht klickbaren Connect-Link hiess
"Connect", der Knopf daneben "Verbinden". Jetzt heisst beides gleich.

Geprueft: Umbruch bei zwoelf Links, Reihenfolge mit Connect und Mods vorn,
halbe Zeilen, ueberlange Beschriftung, nicht klickbare Schemata, fehlendes und
unbrauchbares links-Feld, dazu Speichern, Aendern und kaputtes JSON in der
Datenbank. Panel und /server-Seite im laufenden Frontend angesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 13:39:10 +02:00
D4rkst3randClaude Opus 5 5de172f862 LS bekommt einen festen Platz unter Gaming
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Das Modul stand nur unter "Meine Funktionen" — also solange, wie du es
angeheftet laesst. Loest man die Nadel, ist es nur noch uebers Modul-Raster
erreichbar. Fuer die groesste Seite im Panel zu wenig.

Jetzt kann ein Modul `nav: true` setzen und bekommt damit einen Eintrag in der
Seitenleiste, unter seiner eigenen Gruppe. Kein Sonderfall fuer LS: jedes
Modul, das eine ganze Seite fuellt, kann sich so einsortieren.

Dabei aufgefallen und mitgenommen: eine halbe Antwort von /api/modules/:id hat
die ganze Config weiss gemacht. Der Ladepfad ist zwar sauber abgesichert —
`fehler` und `!detail` fangen ab —, aber ein Objekt OHNE die Listen kommt
durch beide Wachen und laesst dann `detail.werte.some(...)` werfen, was React
mit dem gesamten Baum quittiert. Vier Listen mit `?? []` kosten nichts und
machen aus dem Weissbild schlimmstenfalls eine leere Sektion.

Zwei Werkzeug-Fallen nebenbei: `npx vite build` zieht mal das lokale Vite 6,
mal ein gecachtes Vite 8 aus dem npx-Ordner — ab jetzt nur noch
`npm --prefix frontend run build`. Und der laufende Preview-Server haelt
dist/assets fest, was den Build mit EPERM abbricht; vorher stoppen.

Geprueft: Seitenleiste zeigt Gaming mit Game-Server und LS, Klick oeffnet die
Modulseite und markiert den Eintrag, kein Fehler in der Konsole.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 08:38:52 +02:00
D4rkst3randClaude Opus 5 9f929fe0e4 Eigene Kategorie Gaming fuer Game-Server und LS
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Game-Server-Monitor stand unter "Server & Technik" zwischen DB-Sicherung,
Repo-Sicherung und Member-Gate. Technisch nicht falsch — dort laeuft alles,
was der Bot an Infrastruktur anfasst. Nur sucht niemand seinen LS-Server neben
dem Datenbank-Backup. Mit dem LS-Modul waren es zwei Eintraege in der falschen
Schublade, und das reicht fuer eine eigene.

Getrennt wird nach dem, worum es geht: "Server & Technik" ist der Bot selbst,
"Gaming" sind die Spiele-Server drumherum. Beide Ebenen ziehen mit — die
Modul-Kacheln ueber MODULE_GROUPS, die Seitenleiste ueber TAB_SECTIONS —, und
der Tab heisst jetzt "Game-Server" statt "Server", weil "Server" im selben
Panel schon zweierlei bedeutete.

Nebenbei: das Modul hiess "Server-Monitor" und ueberwacht Game-Server; jetzt
steht das auch dran. Die Suche im Panel findet den Tab zusaetzlich ueber "ls",
"landwirtschaft", "farming", "hof", "karte" und "preise".

Geprueft: kein Modul ohne Gruppe, keine leere Gruppe, Seitenleiste und
Modul-Kacheln im laufenden Panel angesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 08:24:34 +02:00
D4rkst3randClaude Opus 5 1075b91121 Kanaele werden auch angezeigt, Monate statt Schluessel, Sorten auf Deutsch
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Die Kanalauswahl blieb leer, obwohl "einsatzbereit" gruen daneben stand — ein
Widerspruch, der die Ursache schon verraet: der Wert lag in der Datenbank, das
Formular bekam ihn nur nie zu sehen. Ich hatte beim letzten Mal das Speichern
repariert und uebersehen, dass currentSettings() dieselbe von Hand gepflegte
Liste fuehrt. Jetzt holt auch sie die Schluessel aus dem Modul-Register.
Geheimes bleibt aussen vor: gitea_api_token steht unter requires, faellt aber
durch die Namenspruefung und wird weiterhin nur als "ist gesetzt" gemeldet.

Dazu die Uebersetzung. Im Embed standen wheat, clover_windrow und seeds, weil
der Feed nun mal Englisch spricht. Jetzt Weizen, Rotklee (Schwad) und Saatgut,
mit lesbarem Rueckfall fuer alles, was nicht in der Liste steht — 371 Sorten
uebersetzt niemand von Hand.

Und der Monat. Die zwoelf Perioden sind schlicht Monate, EARLY_SPRING ist
Maerz. Also nimmt das Feld jetzt "September", "sept" oder "9" statt
EARLY_AUTUMN; Mehrdeutiges wie "Ju" wird abgelehnt statt geraten.

Weiterzaehlen kann der Bot selbst, seit klar ist, dass ein Monat ueber einen
Tag laeuft. Nicht ueber playTime — die steht im Savegame und wird nur beim
Autosave geschrieben: ueber zwei Stunden gemessen stieg sie um 50, waehrend
die Spieluhr 18,7 Stunden weiterlief. dayTime in der Statusabfrage ist dagegen
live, und jeder Ruecksprung um Mitternacht ist ein Monatswechsel. Steht der
Server leer, steht die Spielzeit — und der Monat bleibt richtigerweise stehen.

Geprueft: alle zwoelf Monate hin und zurueck, Abkuerzungen, Monatszahlen,
Mehrdeutiges, acht Sorten Unsinn, der Jahreswechsel von Februar auf Maerz, der
Ruecksprung um Mitternacht samt Erstlauf, und dass jede Sorte der
Standardauswahl einen deutschen Namen hat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 02:31:20 +02:00
D4rkst3randClaude Opus 5 c5e13182c0 Modul-Felder speichern wieder, und die Feed-Adresse ist einstellbar
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Die LS-Kanaele liessen sich im Panel waehlen und waren nach dem Speichern
wieder leer. Der Grund: PUT /api/settings fuehrt fuer jede Feldart eine von
Hand gepflegte Liste erlaubter Schluessel, und ein Schluessel, der nirgends
steht, faellt still durch. Keine Fehlermeldung, kein Log — das Feld sieht
funktionsfaehig aus und vergisst.

Das ist keine Einzelheit, sondern eine Falle fuer jedes kuenftige Modul: wer
eins ergaenzt und diese Listen nicht mitpflegt, baut denselben Fehler nach.
Die Modulliste weiss laengst, welche Felder es gibt und welcher Art sie sind —
also holt der Handler sie jetzt von dort, statt sie ein zweites Mal
aufzuzaehlen.

Beim Bauen des Helfers ware ich fast in die naechste Falle gelaufen: ich hatte
alle requires-Eintraege als Kanaele eingestuft. Dort stehen aber auch
gitea_api_token und watchdog_urls, und die haetten dann die Kanal-Pruefung
durchlaufen — Speichern waere mit "Kanal nicht gefunden" gescheitert, sobald
jemand seinen Gitea-Token aendert. Uebernommen wird jetzt nur, was der
Namenskonvention folgt.

Dazu die fest verdrahtete Feed-Adresse. http://host:port/feed war eine Wette
auf drei Annahmen gleichzeitig: dass jeder Hoster http nimmt, den Feed unter
/feed ablegt und keinen Pfad davorsetzt. Aendern konnte das niemand. Jetzt gibt
es ein Feld dafuer, und wer die ganze Abfrage-URL ins Host-Feld einfuegt,
bekommt es mitsamt Schema und Pfad automatisch ausgefuellt. Was dahinter kommt
(dedicated-server-stats.xml und Geschwister) gibt Giants vor und bleibt im
Code.

Geprueft: jedes Feld jedes Moduls landet in einem Eimer — der Test faellt
kuenftig aus, sobald jemand ein Feld ergaenzt, das nicht speicherbar waere.
Dazu, dass nichts faelschlich in die Kanal- oder Rollenpruefung geraet, keine
Dubletten, und die Feed-Basis mit eigenem Pfad, https, ueberzaehligen
Schraegstrichen sowie sechs Sorten Unbrauchbarem. Das Einfuegen einer
https-URL mit Pfad im laufenden Panel angesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 02:13:32 +02:00
D4rkst3randClaude Opus 5 768fcda666 Karte: die Welt ist doppelt so breit wie mapSize
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Die Parzellen lagen im Wald. Ich hatte -mapSize/2 bis +mapSize/2 angenommen —
naheliegend beim Namen, aber falsch: die Koordinaten laufen von -mapSize bis
+mapSize. Damit wurde jede Parzelle um Faktor zwei vom Mittelpunkt weggezogen.

Schlimmer als der Fehler war meine Pruefung. Der Zonentest hat die Koordinaten
auf ihre eigene Bounding-Box normiert und danach geschaut, ob die Felder in der
richtigen Ecke liegen. Das besteht bei JEDEM Massstab, weil die Anordnung
zueinander ja stimmt. 14 von 15 Treffern klangen ueberzeugend und sagten
ueber den Massstab genau nichts aus.

Gemessen wurde es dann so: die eingebrannte Legende steht sowohl im Feed-Bild
als auch im Ingame-Screenshot, damit laesst sich der eine aufs andere
umrechnen. Vier Parzellen an den vier Ecken ergaben eine Weltbreite von 7822
bei mapSize 4096 — Faktor 1,91 bei rund 5 Prozent Ablesegenauigkeit.

Dabei ist noch etwas herausgekommen: die Marken H1..H8 auf dem Luftbild sind
als Passpunkte hervorragend, nicht wertlos, wie ich in der Doku behauptet
hatte. Mit richtigem Massstab liegen alle acht Hofparzellen unter 13 px von
ihrer Marke; mit falschem rund 200. Das ist jetzt der Test — acht Passpunkte,
die jede kuenftige Verschiebung sofort auffliegen lassen, statt eines
Zonenvergleichs, der alles durchwinkt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 01:59:21 +02:00
D4rkst3randClaude Opus 5 e215cd4c7c LS-Modul: Besitzkarte auf dem Luftbild und Preiskurven
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Feed eines LS-Servers gibt viel mehr her als die Spielerzahl, die der
Monitor bisher davon benutzt hat: in denselben 49 KB stehen 163 Parzellen mit
Besitzer und Hektar, 130 Felder, jedes Fahrzeug samt Ladung und die Position
jedes Spielers. Und das Luftbild liefert der Server auch, in jeder Groesse bis
4096.

Also eine Karte: gekaufte Parzellen nach Hof eingefaerbt, Kreisflaeche gleich
Hektar, Fahrzeuge und Spieler live, dazu eine Hof-Uebersicht mit Flaeche und
Wert. Wechselt Land den Besitzer, kommt eine Meldung — beim allerersten
Durchlauf bewusst nicht, sonst haette der Botstart eine Meldung pro bereits
gekaufter Parzelle abgesetzt.

Dazu die Preiskurven aus der economy.xml: je Fruchtart, was sie in welcher der
zwoelf Perioden bringt, als Balkenzeile mit "jetzt verkaufen" oder "+43 Prozent
im Hochwinter".

Was NICHT geht, und zwar grundsaetzlich: welche Frucht auf welchem Feld steht
und wie weit sie ist. Das liegt in einer binaeren Density-Map, die kein
Endpunkt herausgibt. Ich habe 18 Dateinamen durchprobiert, es gibt genau drei.
Die Mods, die das anzeigen, sind reine HUD-Overlays ohne Ausgang. Und keiner
der fuenf anderen LS-Bots kann es, obwohl einer davon Geld kostet. Das steht
mit allen Belegen in docs/ls-feed.md, damit die Frage nicht dreimal kommt.

Beim Bauen aufgefallen:

- istLsServer() gab den Zugangs-Code zurueck statt true — die &&-Kette liefert
  den letzten wahren Wert. Beim ersten Log-Aufruf haette der Code im Klartext
  im Protokoll gestanden.
- Die Karte als PNG war 2,5 MB, als JPEG 268 KB. Es ist ein Luftbild.
- Nach Preisschwankung sortiert gewinnt Spargel: 26 bis 7540 Euro, und Weizen
  taucht gar nicht mehr auf. Die Liste arbeitet jetzt mit einer Auswahl.
- Die Jahreszeit steht in keinem Endpunkt. Schaetzbar waere sie, aber nur
  solange niemand die Zeitskala aendert — also eine Einstellung.

Geprueft gegen echte Serverdaten: Zerlegen der Statusabfrage inklusive
Vollstaendigkeit aller 163 Parzellen, Ladung ohne Diesel und leere Slots,
Hof-Uebersicht ohne verlorene Parzellen, Preiskurve mit vollem und leerem
Balken an der richtigen Stelle, flache Kurve ohne Division durch Null,
Besitzwechsel inklusive Erstlauf und neu hinzugekommener Parzelle,
Hof-Namen aus der Einstellung, SVG ohne NaN und mit maskierten Namen, und der
Merker in der Datenbank inklusive kaputtem JSON.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 01:52:25 +02:00
D4rkst3randClaude Opus 5 eec170ff41 Connect-Links im Discord anklickbar, und Mods bekommen einen eigenen Knopf
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Im Kanal stand woertlich das da:

    [https://cfx.re/join/4odv68](https://cfx.re/join/4odv68)

Der Link war sein eigener Linktext. Discord macht aus der nackten URL zwischen
den eckigen Klammern zuerst selbst einen Link und kommt mit der
Masked-Link-Schreibweise dann nicht mehr durch — heraus kommen die Klammern im
Klartext. Der Kommentar an der Stelle behauptete ausserdem, so seien steam://
und Konsorten klickbar. Waren sie nie.

Jetzt sind es Link-Knoepfe unter dem Embed. Die Frage nach der Schreibweise
stellt sich damit gar nicht mehr, und ein Knopf sieht auch nach einem aus.
Discord nimmt dort nur http(s) an; fivem:// und steam:// stehen deshalb weiter
im Embed, aber als Codeblock zum Kopieren statt als toter Link.

Dazu ein eigenes Feld fuer den Mod-Download. Vorher landete der beim
Connect-Link, weil es kein anderes gab — und dann fuehrte "Verbinden" zu einer
ZIP-Datei. Farming Simulator liefert die Modliste ohnehin mit, also steht die
Anzahl jetzt auch im Embed und als Chip auf der Seite.

Beim Testen aufgefallen: eine neue Spalte hat jeden Aufrufer umgebracht, der
sie noch nicht kannte — better-sqlite3 wirft bei einem fehlenden benannten
Parameter. createGameserver und updateGameserver fuellen die Felder jetzt
einmal auf, statt das an jeder Aufrufstelle nachzuziehen.

Geprueft: was in einen Knopf darf und was nicht (https, http, fivem://,
steam://, leer, null, Leerzeichen, Satz mit Link drin), beide Knoepfe in einer
Reihe, nur einer, gar keiner — eine leere Reihe weist Discord ab —, https
landet im Knopf und nicht doppelt auch im Embed, fivem:// umgekehrt, Mods bei
47/0/null, und der Aufruf ohne die neue Spalte. Seite im laufenden Frontend
angesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:52:08 +02:00
D4rkst3r 8981eb91b8 Ganze Abfrage-URL einfuegen statt drei Felder tippen
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2026-08-05 17:14:30 +02:00
D4rkst3randClaude Opus 5 4e7fc99999 Server-Seite ueberarbeitet, und die Spielauswahl kommt jetzt aus gamedig
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
LS25 fehlte, aber nicht nur LS25: die Auswahl im Panel war eine von Hand
gepflegte Liste mit neunzehn Eintraegen, und fuenf davon gab es bei gamedig
gar nicht — "arkse", "ark", "sevendaystodie", "minecraftbe" und "terraria".
Wer eines davon auswaehlte, bekam einen Server, der dauerhaft offline stand,
ohne dass irgendwo ein Grund stand. Die echten IDs heissen ase, sdtd, mbe und
terrariatshock.

Also nicht LS25 nachtragen, sondern die Liste abschaffen: sie kommt jetzt aus
gamedig selbst, 360 Eintraege, sortiert, mit Suche. Jedes Update bringt neue
Spiele automatisch mit. Dazu der Standard-Query-Port als Vorschlag, sobald man
ein Spiel waehlt. Bestehende Eintraege mit den toten IDs werden beim Start
einmalig umgebogen.

Farming Simulator hat noch eine zweite Huerde: der Server antwortet nur mit
Zugangs-Code, und den hat queryServer nie durchgereicht. LS25 waere also auch
mit richtiger ID offline geblieben. Jetzt gibt es eine Spalte dafuer, das Feld
erscheint nur bei Spielen, die einen brauchen (Farming Simulator, Terraria —
bei Satisfactory optional), und Speichern ohne Code wird abgelehnt statt
stillschweigend hingenommen. Der Code steht nicht in der oeffentlichen API.

Nebenbei aufgefallen: input[type=password] war im CSS nirgends erfasst. Das
erste solche Feld kam voellig ungestylt daher.

Die /server-Seite dazu:

- Auslastungs-Balken mit eigener Farbe statt des XP-Balkens vom Level-System.
  Gruen frei, gelb ab 80 Prozent, rot voll — die Zahl steht rechtsbuendig,
  mittig laege sie bei 50 Prozent genau auf der Fuellkante.
- Map, Version und Ping als Chips. Vorher standen sie mit Punkten getrennt in
  der Statuszeile und wurden auf dem Handy zu Brei.
- Wer gerade drauf ist. Die Namen holt der Monitor ohnehin schon fuers Embed,
  auf der Seite standen sie nur nirgends.
- Welches Spiel es ueberhaupt ist. Das stand nirgends ausser im Emoji.
- Offline-Karten waren bisher leer bis auf das Wort. Jetzt steht dort, seit
  wann — soweit der Verlauf reicht, sonst gar nichts statt einer Schaetzung.
- Zeitachse und Peak-Linie am Verlauf. Eine Flaeche ohne Bezugspunkt sagt
  nicht, wie hoch hoch ist.
- Plaetze insgesamt in der Uebersicht oben.

Geprueft: Token speichern und aendern, Spielnamen inklusive unbekannter ID,
Token-Bedarf pro Protokoll, Liste ohne Doppler, alle Icon-Schluessel gegen
gamedig, "zuletzt online" ignoriert Offline-Proben, und die Migration mit einer
Datenbank im alten Stand. Beide Seiten am laufenden Frontend angesehen, auch
auf Handybreite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 16:52:09 +02:00
D4rkst3randClaude Opus 5 22054c109e Mod-Log: eigenes Gedaechtnis, damit beim Loeschen nicht "unbekannt" dasteht
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Dein Eintrag war der Normalfall, nicht die Ausnahme:

    User: unbekannt
    Inhalt: [kein Text / nicht im Cache]

Discord schickt beim Loeschen nur eine Huelle, sobald die Nachricht nicht mehr
im Cache des Bots liegt. Der reicht ein paar hundert Nachrichten weit und faengt
erst beim Start des Bots an — bei einem Post von gestern ist sie also nie drin.
Ein Protokoll, das genau dann nichts weiss, wenn es interessant wird, ist keins.

Also merkt sich der Bot Nachrichten jetzt kurz selbst: Verfasser, Wortlaut,
Namen der Anhaenge. Beim Loeschen steht das im Eintrag, danach wird der Merker
weggeworfen. Wie lange aufbewahrt wird, steht unter Werte — sieben Tage
voreingestellt, 0 heisst gar nicht mitschreiben. Bewusst knapp und einstellbar:
das ist ein Werkzeug zum Nachvollziehen von Loeschungen, kein Archiv.

Dazu drei Sachen, die vorher fehlten:

- Wer geloescht hat. Steht in Discords Audit-Log, aber nur wenn es jemand
  anders war als der Verfasser. Gemeldet wird nur, was frisch ist und zu Kanal
  und Verfasser passt — Discord zaehlt bei wiederholtem Loeschen denselben
  Eintrag hoch statt neue anzulegen, da ist eine falsche Angabe schnell
  gemacht. Lieber keine als eine falsche.
- Sammel-Loeschungen. /purge und Discords eigenes Aufraeumen standen gar nicht
  im Log: eine geloeschte Nachricht fiel auf, hundert nicht. Jetzt mit Anzahl,
  Kanal und den ersten fuenfzehn Zeilen.
- Anhaenge. Dass ein Bild dranhing, war vorher nirgends zu sehen — und das
  Bild selbst ist nach dem Loeschen ohnehin weg.

Nebenbei: "[kein Text / nicht im Cache]" waren zwei Aussagen in einer und beide
unklar. Jetzt steht dort entweder der Wortlaut, oder dass die Nachricht keinen
hatte, oder dass sie aelter ist als die Aufbewahrung reicht.

Beim Testen gefunden: pruneMessageCache(0) hat nichts geloescht. "-0 days" ist
genau jetzt, und `created_at < jetzt` laesst die eben geschriebene Zeile stehen.
Bei 0 wird jetzt alles geleert, wie es gemeint war.

Geprueft: Merken, Wiederfinden, Anhaenge durch JSON, Bearbeiten zieht mit,
Vergessen nach dem Loggen, unbekannte ID, doppelte ID, Aufbewahrung mit einem
zu alten und einem frischen Eintrag, und der 0-Fall. Ereignisnamen und
AuditLogEvent gegen die installierte discord.js geprueft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 09:25:37 +02:00
D4rkst3randClaude Opus 5 b6b767acb7 Bis fuenf Eintraege Knoepfe statt Auswahlmenue
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Du hattest recht. Bei vier Anliegen ist ein Auswahlmenue der schlechtere Weg:
zwei Klicks statt einem, und die Moeglichkeiten sieht man erst nach dem
Aufklappen. Ein Knopf ist beides — sichtbar und sofort.

Umgekehrt gilt es nicht immer. Ab sechs wird die Knopfreihe zur Wand und
Discord bricht sie um; dann ist das Menue das ruhigere Bild. Also nicht
"Knoepfe statt Menue", sondern der Uebergang: bis fuenf Knoepfe, darueber
Menue. Fuenf, weil genau so viele in eine Reihe passen.

Betroffen waren nur zwei Stellen — Ticket-Anliegen und Wunsch-Bereiche. Alles
andere im Bot waren ohnehin schon Knoepfe.

Eine Sache verliert man dabei: die graue Beschreibungszeile, die ein Menue
unter jedem Eintrag anzeigt. Die steht jetzt im Text des Aufruf-Posts, und da
ist sie ehrlich gesagt besser aufgehoben — man liest sie, bevor man klickt,
statt danach.

Geprueft gegen discord.js selbst, weil Ueberlaengen und ungueltige Emojis sonst
erst beim Posten auffallen: null Eintraege (der eine alte Knopf), eins bis
fuenf (Knoepfe mit Emoji und der ID in der Kennung), sechs (zurueck zum Menue),
Murks-Emoji (faellt weg, Knopf bleibt), 200 Zeichen langer Name (auf 80
gekuerzt), und die Beschreibungszeile in allen drei Faellen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 09:14:59 +02:00
D4rkst3randClaude Opus 5 b3758f1f76 Screenshots zu Ideen, und der Bot legt die Forum-Tags selbst an
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Zwei Fragen, zwei Antworten — eine davon ein Nein mit Umweg.

## Bilder im Eingabefenster: geht nicht

Discord-Modals koennen nur Textfelder. Kein Datei-Anhang, keine Umgehung — das
ist eine Grenze der Plattform, nicht unseres Codes.

Der Umweg ist besser als er klingt: Wer nach dem Einreichen einen Screenshot in
den Thread haengt, sieht ihn auf der Ideen-Seite. Die Bestaetigung nach dem
Absenden sagt das jetzt auch. Damit kann man auch nachtraeglich noch Bilder
dazulegen, was im Eingabefenster gar nicht ginge.

## Warum nicht einfach der Discord-Link

Discord haengt seit einiger Zeit eine Signatur an die Anhang-Links
(?ex=&is=&hm=), die nach etwa einem Tag ablaeuft. Ein gespeicherter Link zeigt
morgen ein kaputtes Bild. Deshalb laden Devlogs und Galerie seit dem ersten Tag
herunter — im Kopf von devlog-archive.js steht der Grund. Wuensche machen es
jetzt genauso, ausgeliefert unter /wish-assets/.

Beim Loeschen eines Beitrags oder eines ganzen Wunsches gehen die Dateien mit;
sonst waechst der Ordner mit Bildern, auf die nichts mehr zeigt.

Das Herunterladen stand vorher zweimal fast wortgleich im Code. Mit den
Wuenschen waeren es drei Kopien geworden — also einmal nach bot/bilder.js, und
Devlogs und Galerie ziehen mit um. Ein Fehler steckt jetzt an einer Stelle
statt an dreien.

## Forum-Tags per Knopf

Bisher musstest du sie von Hand anlegen. Jetzt macht es das Panel: die Bereiche
von dort plus die fuenf Staende, mit ihren Emojis. Vorhandene bleiben, auch
fremde — die Liste wird ergaenzt und nicht ersetzt. Discords Grenze von 20 Tags
wird eingehalten, was nicht mehr reinpasst, wird gemeldet statt still
verschluckt.

Geprueft: forumTagsAnlegen gegen eine Kanal-Attrappe — leeres Forum, teilweise
vorhandene, Gross/Kleinschreibung, fremde Tags, nichts zu tun (dann wird auch
nicht geschrieben), volles Forum, kein Forum, kein Kanal. Im Browser die
Ideen-Seite mit Bildern in einem Beitrag, einem Beitrag nur aus Bild ohne Text,
und der Lightbox darueber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 00:17:01 +02:00
D4rkst3randClaude Opus 5 5073e7bcf4 Jede Idee bekommt ihre eigene Seite mit der Unterhaltung aus dem Thread
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Vorbild windrose.support: Klick auf eine Idee, und man sieht sie ganz — Stand,
Begruendung, wer sie eingereicht hat, und darunter, was dazu geschrieben wurde.

Der Unterschied bleibt, wo geschrieben wird. Die Beitraege sind aus dem
Discord-Thread gespiegelt, nicht hier getippt. Deshalb fuehrt der einzige
Knopf am Ende in den Thread und nicht in ein Formular. Ein zweiter Ort fuer
dieselbe Unterhaltung waere genau das, was ich beim Konzept als das Falsche
bezeichnet habe.

Bisher wurde nur mitgezaehlt. Jetzt landen die Beitraege in einer eigenen
Tabelle — mit Bearbeiten und Loeschen, sonst stuende auf der Webseite fuer
immer, was in Discord laengst zurueckgenommen wurde. Reine Bild-Posts ohne Text
bleiben draussen, die haetten hier nichts zu sagen.

Der Kommentar-Zaehler wird jetzt aus der Tabelle gezaehlt statt in einer Spalte
mitgefuehrt. Eine Wahrheit statt zwei, die auseinanderlaufen koennen — dieselbe
Ueberlegung wie beim Zuspruch.

Nebenbei geprueft, weil es haette schiefgehen koennen: /api/wishes/suche und
/api/wishes/:id liegen auf derselben Ebene. Der Router bevorzugt die feste
Route vor der mit Platzhalter — nachgestellt mit find-my-way, nicht aus dem
Gedaechtnis behauptet.

Geprueft: im Browser die Seite mit drei Beitraegen und die ohne (kein Thread,
keine Begruendung, Hinweis statt Liste), der Weg von der Roadmap dorthin ohne
Neuladen, Migration erneut durchgespielt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 00:05:27 +02:00
D4rkst3randClaude Opus 5 1856a1ed83 Foren waehlbar, Emoji-Tafel repariert, Balken sagt seit wann er misst
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Drei Dinge, die beim Ausprobieren aufgefallen sind.

## Foren standen nicht zur Auswahl

Ein Forum-Kanal ist fuer discord.js nicht "textBased" — er hat keine
Nachrichten, nur Beitraege. Die Kanal-Liste im Panel hat genau danach
gefiltert, also tauchte das Forum nirgends auf, und beim Speichern haette
dieselbe Pruefung es abgelehnt. Beides kennt jetzt Foren und Medien-Kanaele;
in der Auswahl stehen sie mit 📋 statt # und dem Hinweis "Forum".

Das erklaert auch das "Unknown Channel" beim Testen: eingestellt war noch der
alte Kanal, und der neue liess sich gar nicht erst waehlen.

## Emoji-Tafel landete in der Nachbarspalte

Die Config laeuft ab genug Breite zweispaltig — CSS `columns: 2`. In einem
Mehrspalten-Layout zerreisst es absolut positionierte Kaesten: die Tafel wurde
fragmentiert und ihr Inhalt in der anderen Spalte gezeichnet, waehrend beim
Feld nur der leere Rahmen stehen blieb. Genau so sah es auch aus.

Die Tafel haengt jetzt an <body> und wird per JS an den Knopf gerechnet, wie
der Dialog auch. Sie klappt nach oben, wenn unten kein Platz ist, wandert beim
Scrollen mit und bleibt beim Fenstergroesse-Aendern an ihrem Feld.

## Balken sagt jetzt, seit wann er misst

Die Waechter-Balken sahen leer aus. Sind sie nicht: 545 Messpunkte, alle
gruen — aber eben nur von heute, weil die Dienste gestern angelegt wurden.
Sechs von sieben Tagen sind zu Recht grau, das sieht nur aus wie kaputt.

Steht links jetzt "misst seit 01.08." statt stur "vor 7 Tagen", sobald die
erste Messung deutlich spaeter liegt als der Anfang des Fensters. Bei vollem
Verlauf bleibt es bei "vor 7 Tagen".

Geprueft im Browser: Tafel an <body> und `position: fixed`, richtig
positioniert und nach oben geklappt, alle sieben Gruppen und 127 Symbole in
einem Stueck; Forum in der Auswahlliste mit Kennzeichnung; beide
Beschriftungen des Balkens (angebrochener und voller Verlauf).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 23:59:19 +02:00
D4rkst3randClaude Opus 5 cc7a51f02e Emojis zum Anklicken, und Wuensche als Forums-Beitraege mit Tags
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Ueberall, wo ein Emoji nach Discord geht — Rollen-Menues, Ticket-Anliegen,
Wunsch-Bereiche, Portal-Kacheln — stand ein leeres Textfeld, in das man mit
Win+. hineintippen musste. Jetzt sitzt daneben ein Knopf mit einer Tafel:
sieben Gruppen, deutsche Suchbegriffe ("auto", "geld", "warnung"), ein Klick
schreibt ins Feld. Das Feld bleibt daneben stehen, denn Server-Emojis
(<:name:123>) kennt nur Discord.

Bewusst eine eigene Liste statt einer Bibliothek: die vollstaendige
Unicode-Tabelle waere ein halbes Megabyte fuer eine Handvoll Symbole, und die
Suchbegriffe duerfen deutsch sein.

## Forum-Tags

Ist der Voting-Kanal ein Forum, wird jeder Wunsch jetzt ein Forums-Beitrag
statt Nachricht-plus-Thread — und bekommt Tags fuer Bereich und Stand. Damit
laesst sich auch in Discord filtern, wofuer Tags ja da sind. Beim Wechsel des
Stands zieht das Tag mit: der alte fliegt raus, der neue kommt rein, der
Bereich bleibt stehen.

Zugeordnet wird ueber den Namen. Wer im Forum ein Tag "Fahrzeuge" anlegt,
bekommt es automatisch gesetzt; wer keines anlegt, verliert nichts. Bewusst
keine ID-Zuordnung im Panel — das waere eine zweite Liste, die man pflegen
muss und die beim ersten Umbenennen auseinanderlaeuft.

Dabei aufgefallen und mitgefixt: im Forum liegt die Startnachricht im Beitrag
selbst und nicht im Kanal. Der Status-Aktualisierer hat sie vorher im Kanal
gesucht und nicht gefunden — die Begruendung waere im Forum also nie im Post
gelandet. Und der Titel wird jetzt nur um den Stand ergaenzt statt
ueberschrieben, damit "🚗 Fahrzeuge" stehen bleibt.

Geprueft: tagsFuer gegen 20 Faelle — kein Forum, Medien-Kanal, fehlende Tags,
Gross/Kleinschreibung, Deckel bei fuenf (mehr nimmt Discord nicht), und alle
fuenf Staende einzeln. Im Browser die Emoji-Tafel an allen vier Stellen:
Suche, Escape, Klick daneben, Leeren, Uebernehmen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 23:45:18 +02:00
D4rkst3randClaude Opus 5 edf8fa14c5 Ideen bekommen Bereiche und einen Thread zum Reden
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Nach dem Vorbild von windrose.support: Bereiche zum Einsortieren, Sortierung
nach Top / Bewegung / Neu, und eine Zahl daneben, wie viel darueber geredet
wird.

Der Unterschied zu so einem Dienst ist, wo geredet wird. Zu jedem Wunsch macht
der Bot jetzt einen Thread unter dem Post auf. Der Kommentar-Zaehler auf der
Roadmap zaehlt die Beitraege darin und verlinkt hinein — es gibt also einen
Zaehler wie bei einem Ideen-Board, aber ohne ein zweites Kommentarsystem neben
Discord zu stellen. Genau das hatte ich beim letzten Mal als das Falsche
bezeichnet, und dabei bleibt es.

Eingereicht wird jetzt auch ueber einen Aufruf-Post: /wunsch-setup postet ihn,
und mit Bereichen wird daraus ein Auswahlmenue — erst wohin, dann das
Formular. Ohne Bereiche bleibt es beim einen Knopf. Derselbe Aufbau wie bei den
Ticket-Anliegen, bis hin zur Emoji-Pruefung, die ich mir dort schon geschrieben
hatte.

Das Anlegen selbst ist nach bot/wishes.js gewandert. /wunsch, der Knopf und die
Webseite gehen jetzt denselben Weg — vorher hatte die Webseite ihr eigenes
Embed zusammengebaut, das dem aus dem Befehl nur aehnlich sah und keinen Thread
bekam.

"Bewegung" sind die Stimmen der letzten sieben Tage. Die Rangliste allein
zementiert alte Wuensche: was einmal oben steht, bleibt oben, egal ob noch
jemand hinschaut. Sortiert wird auf dem Server, weil die Zeitstempel der
Stimmen im Browser gar nicht ankommen.

Bewusst nicht uebernommen: Gegenstimmen. Windrose hat sie, aber bei einer
Community dieser Groesse laden sie zum Nachtreten ein und schrecken vom
Einreichen ab — und eine Zahl, die aus zwei Richtungen kommt, sagt am Ende
weniger als eine, die nur zaehlt, wer etwas will.

Geprueft: Migration erneut durchgespielt (das Schema hat drei Spalten
dazubekommen), Routen, SQL gegen das Schema, und im Browser alle drei
Filterreihen einzeln und kombiniert, der Sortierwechsel, die Thread-Links und
die Bereichs-Pflege im Panel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 23:31:55 +02:00
D4rkst3randClaude Opus 5 fb61e7279d Wuensche bekommen einen Ausgang statt nur einer Punktzahl
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Voting hatten wir. Was fehlte, war alles danach: ein Wunsch sammelte zwoelf
Stimmen und dann passierte sichtbar nie wieder etwas damit. Genau das nennt der
Artikel, der die Idee angestossen hat, "letting the board become a graveyard" —
und das war der Zustand.

Jeder Wunsch hat jetzt einen Stand: wird geprueft, geplant, in Arbeit,
umgesetzt, nicht geplant — mit Begruendung. Die steht oeffentlich unter dem
Wunsch auf der Roadmap und wird in den Discord-Post zurueckgeschrieben, wo
abgestimmt wurde. Ein abgelehnter Wunsch mit einem Satz Begruendung ist mehr
wert als einer, der ewig oben schwebt.

Springt ein Wunsch auf "umgesetzt", bekommt jeder eine Direktnachricht, der
dafuer gestimmt hat. Das ist der Punkt, an dem unser Aufbau einem fertigen
Voting-Dienst ueberlegen ist: der Draht zu jedem Einzelnen ist ohnehin offen.

## Zwei Sachen, die kaputt waren

Doppelt abstimmen ging. Der Zuspruch wurde von zwei Wegen hochgezaehlt — 👍 in
Discord und der Web-Knopf — und nur der Web-Weg merkte sich, wer geklickt hat.
Dieselbe Person zaehlte zweimal. Jetzt wird nicht mehr hochgezaehlt, sondern
gezaehlt: eine Zeile je Person, egal woher der Klick kam. Der zusammengesetzte
Schluessel schliesst den Fall aus, statt ihn nachtraeglich zu korrigieren.

Die Discord-Nachrichten-ID war der Primaerschluessel. Damit konnte ein Wunsch
nur existieren, solange seine Nachricht existiert, das Team konnte keinen von
Hand eintragen, und zwei Doppler liessen sich nicht zusammenfuehren. Wuensche
haben jetzt eine eigene ID; die Nachrichten-ID ist nur noch ein Verweis.

Die Migration erhaelt den Zuspruch: was vom alten Punktestand nicht auf
gespeicherte Web-Stimmen zurueckgeht, waren Reaktionen ohne Namen — die Zahl
bleibt als Sockel stehen, weil rueckwirkend niemand mehr feststellen kann, wer
das war. Durchgespielt gegen eine Datenbank im alten Aufbau: Punktestand
erhalten, verwaiste Stimmen fallen raus, ein zweiter Start migriert nicht
nochmal, und der Fall, in dem der alte Zaehler hinter den echten Stimmen
zurueckhing, zaehlt jetzt richtig.

## Dazu

Doppler zusammenfuehren: die Stimmen wandern zum Original, wer fuer beide
gestimmt hat, zaehlt dort weiterhin einmal. Beim Tippen im Wunsch-Feld zeigt die
Seite, was es schon gibt — ein Klick darauf stimmt mit, statt einen zweiten
gleichen Wunsch anzulegen. Und die Liste laesst sich nach Stand filtern.

Was ich bewusst nicht gebaut habe: Kommentare auf der Webseite. Jeder Wunsch
ist schon eine Discord-Nachricht — die Diskussion gehoert in den Thread
darunter und nicht in ein zweites System.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 23:18:29 +02:00
D4rkst3randClaude Opus 5 1c5c7d2470 Playtester werden jetzt beworben, nicht angeklickt
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Knopf unter dem Playtester-Aufruf hat jeden aufgenommen, der ihn gedrueckt
hat — ohne Frage, ohne Pruefung. Fuer ein Programm, dessen Teilnehmer Zugang
und Alpha-Keys bekommen, ist das die falsche Tuer.

Statt einen zweiten Pruef-Ablauf danebenzustellen, laeuft die Aufnahme jetzt
ueber die Bewerbungs-Formulare, die es laengst gibt: Modal ausfuellen,
Review-Embed im Staff-Kanal,  oder , Rolle und Antwort-DM bei Zusage. Das
ist dieselbe Maschinerie, nur ein anderer Aufhaenger — und damit auch nur eine
Stelle, an der spaeter etwas kaputtgehen kann.

Ein Formular laesst sich als Playtester-Formular markieren ("Annahme traegt als
Playtester ein"). Wer dort angenommen wird, landet zusaetzlich in der
Playtester-Liste, damit die Schluessel-Verteilung ihn kennt. Nur eines kann es
sein — sonst wuesste /playtester-setup nicht, welches es posten soll.

/playtester-setup postet jetzt den Knopf dieses Formulars. Fehlt das Formular,
fehlen Fragen oder fehlt der Review-Kanal, sagt der Befehl das, statt einen
Aufruf zu posten, der ins Leere fuehrt.

Der alte Knopf in bereits geposteten Nachrichten nimmt niemanden mehr auf: er
verweist auf den Aufruf. Austreten bleibt Selbstbedienung — dafuer braucht es
keine Freigabe.

Im Panel steht im Playtester-Bereich, ueber welches Formular die Aufnahme
laeuft, ob es schon gepostet ist, und ein Weg dorthin. Ohne markiertes Formular
steht dort, dass gerade niemand hereinkommt — das ist sonst der Grund, warum
sich tagelang niemand bewirbt.

Geprueft: Routen, SQL gegen das Schema, und im Browser beide Zustaende des
Hinweises sowie der neue Schalter — beim Wechsel zwischen zwei Formularen
folgt er dem richtigen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:04:16 +02:00
D4rkst3randClaude Opus 5 ddd7610d6c Playtester bekommen einen eigenen Bereich, Module ziehen um
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Playtester und Alpha-Keys standen als zwei Kaesten mitten im Community-Bereich
zwischen Geburtstagen und Starboard. Die Liste war reine Anzeige: Namen und
Datum, sonst nichts. Wer einem Einzelnen einen Schluessel geben wollte, konnte
nur "an alle verteilen" druecken, und wer jemanden wieder rausnehmen wollte,
musste an die Datenbank.

Jetzt ist es ein eigener Bereich mit drei Zahlen oben und einer Liste, in der
neben jedem Namen sein Schluessel steht. Daneben, je nach Zustand, "Schluessel
geben" oder "Zurueckziehen", und ein ✕ zum Entfernen — die Rolle geht dabei
mit, denn nur aus der Liste zu streichen und die Rolle stehen zu lassen waere
eine Halbwahrheit.

Der Vorrat ist jetzt sichtbar, nicht nur seine Groesse: einen vertippten
Schluessel findet man sonst nie wieder. Nachlegen und "wer bekommt einen?"
laufen ueber Dialoge, damit der Bereich nicht zumuellt — dafuer gibt es jetzt
eine kleine Dialog-Komponente, gebaut wie die Lightbox (Portal, Escape,
Hintergrund festhalten).

Zurueckziehen legt den Schluessel zurueck in den Vorrat. Die Person hat ihn per
DM natuerlich weiterhin — das ist eine Buchhaltung, keine Sperre. Loeschen geht
absichtlich nur bei freien Schluesseln.

## Module dorthin, wo man sie sucht

Ein paar Module standen im falschen Bereich, teils seit Langem:

- Willkommens-Karte: Support → Brand. Sie ist Aussehen, kein Support-Fall, und
  im Brand-Bereich steht der Rest vom Auftritt. Der Block ist woertlich
  umgezogen, nur in eine eigene Konstante — so kann sich beim Umzug nichts am
  Inhalt geaendert haben.
- Twitch & YouTube: System → Feeds. Es ist ein Feed.
- Events: System → Community.
- Auto-Rolle und Rollen merken: Ihre Felder standen laengst im Rollen-Bereich,
  nur das Register sagte "Community" — dadurch sprang "Einrichten" woanders hin
  als die Einstellung liegt.
- Playtester und Alpha-Keys: Community → eigener Bereich.

Damit ist System kein Sammelbecken mehr, sondern nur noch Technik: Waechter,
Sprachkanaele, Erinnerungen, Sicherungen, Member-Gate.

Geprueft: Routen (alle vier neuen bewacht), SQL gegen das Schema, und im
Browser beide Dialoge (Escape schliesst, Hintergrund wird wieder freigegeben,
Hinzufuegen bleibt bei leerem Feld aus), die Liste in allen drei Zustaenden,
und die Suche — "willkommen" fuehrt jetzt nach Brand, "twitch" zu Feeds,
"sticky" zu Rollen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 09:49:27 +02:00
D4rkst3randClaude Opus 5 bb2aee7818 Tickets: Anliegen zur Auswahl, und das Team kann uebernehmen
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Bisher sah jedes Ticket gleich aus: ein Knopf, ein Thread, und im Thread stand
als Erstes die Frage, worum es ueberhaupt geht. Und wer sich darum kuemmert,
stand nirgends — man sah hinterher nur, wer zufaellig auf "Schliessen" geklickt
hat.

Anliegen sind Kategorien, die man in der Config anlegt: Name, Emoji, eine
Zeile Beschreibung, ein Einstiegstext und optional eine Rolle, die beim
Oeffnen erfaehrt, dass etwas anliegt. Der Ticket-Post wird dadurch vom Knopf
zum Auswahlmenue; das gewaehlte Anliegen steht im Thread-Namen und der
Einstiegstext gleich im ersten Beitrag — gute Stelle fuer "was wir wissen
muessen".

Wer keine anlegt, merkt nichts: ohne Eintrag bleibt es beim einen Knopf. Wer
nur eine Sorte Tickets hat, soll nichts einrichten muessen.

Uebernehmen ist ein Knopf im Thread, sichtbar fuers Team (wer aufraeumen darf,
darf auch uebernehmen). Danach steht im Thread, wer sich kuemmert, und der
Knopf wird zu "Freigeben". Die Bedingung "nur wenn noch niemand dran ist"
steckt im UPDATE und nicht davor — zwei gleichzeitige Klicks wuerden sonst
beide gewinnen. Beim Schliessen landen Anliegen und Bearbeiter im Transcript
und im Mod-Log-Eintrag.

Die Erstellung ist dabei aus client.js nach tickets.js gewandert. Knopf und
Menue gehen jetzt denselben Weg, und in client.js sind drei Importzeilen
weggefallen, die nur noch dort standen.

Geprueft: alsEmoji gegen 20 Eingaben (Unicode, Hautfarbe, ZWJ-Ketten,
Server-Emoji, Murks) und das fertige Panel gegen discord.js selbst — ein
ungueltiges Emoji oder ein zu langes Label laesst Discord sonst die ganze
Nachricht fallen, und das faellt erst beim Senden auf. Im Browser: Liste,
Bearbeiten-Formular und der leere Zustand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 09:14:49 +02:00
D4rkst3randClaude Opus 5 2292dedeaa README auf den heutigen Stand bringen und neu ordnen
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Die Funktionsliste war eine einzige Tabelle mit 46 Zeilen — in der Reihenfolge,
in der die Sachen entstanden sind, also in keiner. Jetzt ist sie nach denselben
Bereichen sortiert wie das Panel: Inhalte, Community, Moderation, Server, dazu
Plattform fuer alles, was keine Discord-Funktion ist. Wer im Panel etwas sucht,
findet den Abschnitt in der README am selben Platz.

Nachgetragen, was seither dazugekommen ist: AutoMod, Raid-Schutz, verknuepfte
Rollen, native Umfragen, Statusseite, Herzschlag, Seiten-Editor, Englisch fuer
die oeffentlichen Seiten. Und die Trennung in zwei Domains stand bisher gar
nicht drin, obwohl sie das Erste ist, was man verstehen muss — sie steht jetzt
ganz oben, mit einer Tabelle welche Seite was zeigt.

Korrigiert: 35 Module waren es mal, es sind 39. 15 Config-Bereiche waren es
mal, es sind 16. Die Seitentabelle fuehrte Hub-Seiten unter der Bot-Domain.
Die Projektstruktur kannte die Haelfte der Dateien nicht. In der
Ersteinrichtung fehlten die Rechte fuer AutoMod und Kanal-Anlegen sowie die
zusaetzlichen OAuth-Redirects.

Dazu ein Inhaltsverzeichnis, eine docs/README.md als Wegweiser, und in
konzept-zwei-seiten.md steht nicht mehr "muss noch aktiviert werden" — es
laeuft seit Ende Juli.

Geprueft mit einem kleinen Skript: 11 Markdown-Dateien, keine toten
Datei-Links, keine toten Anker, keine Tabelle mit falscher Spaltenzahl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 07:16:32 +02:00
D4rkst3randClaude Opus 5 e32152b5ac Eigener Herzschlag: der Bot kann jetzt auch ueber sich selbst Auskunft geben
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Waechter prueft alles ausser sich selbst — ist der Bot weg, prueft
niemand mehr, und hinterher sieht der Verlauf aus, als waere nie etwas
gewesen. Im Dashboard stand deshalb nur "seit 12d 4h", also wie lange es
diesmal gutgegangen ist. Was davor war, wusste niemand.

Jetzt schreibt der Bot alle zwei Minuten eine Zeile: laeuft der Prozess, und
steht die Verbindung zu Discord? Ein Bot, der laeuft aber nicht verbunden
ist, ist fuer alle draussen genauso weg — deshalb zaehlt beides.

Der Takt ist absichtlich nicht einstellbar. Ein spaeter geaenderter Abstand
wuerde den alten Verlauf falsch aussehen lassen: wie viele Proben eine
Stunde haette haben muessen, liesse sich rueckwirkend nicht mehr sagen.

Ausgewertet wird vor allem die Abwesenheit. Fuer einen fremden Dienst heisst
eine fehlende Stunde "niemand hat gemessen" und wird grau; beim Bot selbst
ist genau das die Aussage — fehlende Stunden werden rot. Grau bleibt nur,
was vor der allerersten Aufzeichnung liegt. Die angebrochene erste und die
laufende letzte Stunde rechnen anteilig, sonst waeren sie dauerhaft gelb.

Nebenbei mitgenommen:

- Statusseite und die frei angelegten Seiten stehen jetzt in der Fusszeile
  beider Seiten — dort sucht man Rechtliches. Die Navigationsleiste zeigt
  weiterhin nur, was "im Menü" gesetzt hat; wer die Nutzungsbedingungen dort
  raus nimmt, hat sie trotzdem noch in der Fusszeile.
- Die Config-Spalte hatte keine Obergrenze. min-width:0 allein reicht nicht,
  wenn ein Kind eine grosse Mindestbreite mitbringt — mit dem Balken wuchs
  sie auf 724 statt 335 px, und weil der Body waagerecht abschneidet, war
  der Ueberstand nicht scrollbar, sondern weg.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 07:10:53 +02:00
D4rkst3randClaude Opus 5 3080f52e71 AutoMod sichtbar machen: Treffer ins Protokoll, Regeln ins Panel
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Discords eigener Filter arbeitet serverseitig und ohne Latenz — daran ist
nichts zu verbessern. Was fehlte, war die Sicht darauf: Treffer landeten nur
in Discords eigenem Kanal, und die Regeln pflegte man in einer ganz anderen
Oberflaeche als alles andere.

Jetzt spiegelt der Bot jeden Treffer als Embed ins Mod-Log — mit Nutzer,
Regel, Ausloeser, Massnahme, getroffenem Wort und gekuerztem Wortlaut, ohne
Erwaehnungen, die den Kanal anpingen. Im Panel steht unter Support jede Regel
mit Ausloeser, Massnahmen und der Zahl ausgenommener Rollen und Kanaele —
letzteres oft der Grund, warum eine Regel scheinbar nicht greift. Schalten
geht dort direkt.

Das Bearbeiten der Wortlisten bleibt bewusst in Discord; dort ist es gut
geloest, und ein zweiter Ort dafuer waere nur eine weitere Stelle, an der
etwas auseinanderlaeuft.

Dazu noetig: der Gateway-Intent AutoModerationExecution. Nicht privilegiert,
aber Discord schickt die Ereignisse nur an Bots mit "Server verwalten" —
fehlt das Recht, sagt das Panel es statt still leer zu bleiben.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 06:53:33 +02:00
D4rkst3randClaude Fable 5 c8b1f604f0 Umfragen: /umfrage als native Discord-Abstimmung
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Bewusst Discords eigenes Poll-Objekt statt einer Nachbildung mit Knoepfen —
Oberflaeche, Auszaehlen und Ergebnisanzeige macht Discord dann selbst, auch
auf dem Handy. Wir bauen nur die Frage zusammen.

Bis zu vier Antworten (Discord erlaubt zehn, vier halten den Befehl
uebersichtlich), Laufzeit von einer Stunde bis einer Woche, Mehrfachauswahl
optional. Rechte: ManageMessages.

Zwei gleiche Antworten werden abgefangen. Discord stoert das nicht, aber
abstimmen kann darauf niemand sinnvoll.

Nicht zu verwechseln mit /wunsch: das sammelt Feature-Wuensche dauerhaft und
zeigt sie auf der Roadmap. /umfrage ist die schnelle Frage zwischendurch und
laeuft von selbst ab. Steht so auch als Kommentar im Befehl.

Geprueft gegen die installierte discord.js-Fassung (14.27) statt gegen die
Dokumentation: Feldnamen aus den Typdefinitionen gelesen, und die fertige
Nutzlast durch discord.js' eigene Serialisierung gejagt. Kommt als
poll_media / allow_multiselect / layout_type 1 raus, genau wie Discords API
es erwartet.

Dazu: 18 Befehle laden ohne Namenskollision, Befehl steht auf der
oeffentlichen Liste in beiden Sprachen, Zahl auf der Produktseite von 17
auf 18.

Nutzungsbedingungen als Entwurf unter docs/ — Text fuer den Seiten-Editor,
keine Rechtsberatung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 06:34:04 +02:00
D4rkst3randClaude Fable 5 52a735f83a SPA-Fallback beantwortet auch HEAD
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Discord prueft die im Portal hinterlegte Datenschutz- und AGB-Adresse mit
einer HEAD-Anfrage. Der Fallback behandelte nur GET, alles andere lief in
reply.code(404) — Discord bekam eine 404 und lehnte die Adresse mit
"URL ist nicht erlaubt" ab.

Im Cloudflare-Log nachgewiesen: 35.190.130.193 (Google LLC, dort laeuft
Discord), python-requests, Methode HEAD, Pfad /datenschutz, und ausdruecklich
"nicht eingedaemmt" — Cloudflare hat also durchgelassen, die 404 kam von uns.

Damit sind auch zwei Verdaechtigungen vom Tisch: weder Bot Fight Mode (war
ohnehin aus) noch AI Crawl Control. Letzteres hatte nur meine eigenen Abrufe
geblockt, was mich faelschlich glauben liess, der RSS-Feed sei fuer alle tot.
Er ist es nicht.

Geprueft gegen den echten Fastify-Aufbau, ueber eine echte TCP-Verbindung
statt app.inject(): HEAD /datenschutz und /impressum liefern 200, text/html,
korrekte Content-Length und keinen Rumpf. API-Pfade bleiben unangetastet
(HEAD /api/settings weiter 401).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 06:26:36 +02:00
D4rkst3randClaude Fable 5 d9f13b4d92 Leere Seiten behoben: IconLink war benutzt, aber nicht importiert
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Mit dem Linked-Roles-Modul kam ein Symbol in die Icon-Zuordnung, dessen
Import ich vergessen habe. Das ist kein fehlender Import, sondern eine
undefinierte Variable — der Build laeuft sauber durch, und erst im Browser
wirft das Modul beim Laden einen ReferenceError. React mountet dann gar
nicht: weisse Seite auf Hub, Bot-Seite und Panel gleichzeitig.

Geprueft: Startseite rendert wieder (12050 Zeichen im Root), und der
Modul-Ueberblick, der die Icons wirklich zeichnet, liefert 36 Karten mit
100 Symbolen ohne Konsolenfehler.

Der Build schuetzt hier nicht — vor dem naechsten Ausrollen einer
Frontend-Aenderung gehoert ein Blick in die laufende Seite dazu, nicht nur
ein gruener Build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 06:04:56 +02:00
D4rkst3randClaude Fable 5 137452da11 Linked Roles: Rollen, die an geprueften Werten haengen
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Neues Modul, standardmaessig aus. Ein Mitglied klickt in Discord auf "Rolle
holen", landet auf /linked-roles, bestaetigt den OAuth-Dialog — und der Bot
schiebt ihm drei Kennzahlen zu, die Discord dann gegen die Rollen-Einstellung
prueft:

  level        Zahl >= eingestelltem Wert
  dabei_seit   Datum <= "vor X Tagen", also Mitglied seit mindestens X Tagen
  playtester   Wahrheitswert

Der Bot vergibt dabei keine Rollen. Er liefert nur die Zahlen; was daraus
wird, entscheidet die Rollen-Einstellung im Server. Die Daten kommen aus dem,
was ohnehin da ist: Level-Tabelle, Playtester-Liste, Discords Beitrittsdatum.

Eigener OAuth-Weg statt /auth/login, weil die Berechtigungen andere sind —
role_connections.write hat der normale Login nicht.

Die Tokens werden gespeichert, und das ist keine Bequemlichkeit: ohne
Refresh-Token bliebe jede Rolle auf dem Stand des Verknuepfungs-Tages stehen,
ein Level-Up kaeme nie an. Discords eigene Anleitung sagt dasselbe. Der
Umfang ist eng (nur identify + role_connections.write), beim Widerruf oder
Entkoppeln fliegt der Datensatz sofort raus. Steht so auch als Kommentar an
der Tabelle.

Abgeglichen wird alle 6 Stunden, einstellbar. Dafuer kann everyTuned jetzt
auch Stunden — und wirft bei einer unbekannten Einheit, statt den Abstand
still um Faktor 60 oder 3600 danebenzulegen.

Beim Bauen gefunden: moduleEnabled fehlte im Import von client.js. Der Bot
waere beim ClientReady mit ReferenceError gestorben.

Geprueft: alle drei Kennzahlen gegen Discords Formatgrenzen (Schluessel-Regex,
Laengen, gueltige Typ-Codes), Modul- und Stellwert-Register, die
Stunden-Umrechnung (6 h = 21600000 ms), Speichern und Loeschen einer
Verknuepfung, dazu 186 SQL-Abfragen und alle Routen auf Rechtepruefung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 05:57:57 +02:00
D4rkst3randClaude Fable 5 55e9ef57e2 Oeffentliche Statusseite nach Statuspage-Vorbild
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Neue Seite /status auf dem Hub: Gesamtlage als Banner, darunter die
freigegebenen Dienste mit 90-Tage-Balken, die Game-Server, und die
Stoerungs-Historie der letzten 90 Tage.

Die Adressliste ist von einer kommagetrennten Einstellung in eine Tabelle
gewandert. Jeder Dienst hat jetzt Namen, Gruppe und einen Schalter
"oeffentlich" — sonst stuende auf einer oeffentlichen Seite die nackte URL,
auch von internen Diensten. Die alte Liste wird beim ersten Start uebernommen,
bewusst als nicht oeffentlich: was dort auftaucht, soll eine Entscheidung sein
und nicht durch eine Migration passieren.

/api/status liefert nur freigegebene Dienste und dabei keine Adressen, nur
Namen. Auch die Stoerungs-Historie ist gefiltert — sonst verriete sie, was es
sonst noch gibt. Im Browser gegengeprueft: auf der ganzen Seite steht kein
einziges https://.

Stoerungen werden jetzt aufgezeichnet, nicht nur gemeldet: pro Adresse ein
offener Eintrag, bis es wieder laeuft. Beim Wiederkommen wird er geschlossen,
bevor die DM rausgeht — sonst bliebe er bei einem Sendefehler ewig offen.

Aufbewahrung von 7 auf 90 Tage. Der Balken kann jetzt zwei Aufloesungen:
168 Stunden fuers Panel ("was war letzte Nacht?") und 90 Tage fuer die
Statusseite ("wie zuverlaessig?"). Stuendlich ueber 90 Tage waeren ueber 2000
Striche und unlesbar.

Im Panel sind aus der reinen Anzeige Verwaltung geworden: anlegen, bearbeiten,
entfernen, freigeben — dazu die Stoerungsliste.

Beim Bauen gefunden: die Uebernahme der alten Liste stand mitten in db.js und
rief getSetting(), dessen prepared statement erst 300 Zeilen weiter unten
entsteht. Der Bot starb beim Start mit "Cannot access getSettingStmt before
initialization" — nachgewiesen, dann ans Dateiende verschoben.

Geprueft: 182 SQL-Abfragen gegen das Schema, alle schreibenden Routen bewacht,
Stoerungs-Logik durchgespielt (doppeltes Eroeffnen wird abgelehnt, Schliessen
ohne offene gibt null), und beide Seiten im Browser mit drei Diensten in allen
drei Zustaenden.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 05:45:50 +02:00
D4rkst3randClaude Fable 5 fd2d31f07f Watchdog: Embeds statt Textzeilen, dazu eine Uebersicht im Panel
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Die DM-Alarme waren als einzige Meldung noch reiner Text — direkt neben dem
Raid-Alarm, der ein Embed ist. Jetzt beide im selben Look, mit Ausfalldauer
bzw. Fehlergrund und Antwortzeit als Felder.

Uebersicht im Panel (System, unter den Watchdog-URLs): je Adresse ein
Erreichbarkeits-Balken ueber 7 Tage, gruen/rot/grau wie bei den Game-Servern,
dazu Status, Antwortzeit und seit wann etwas weg ist.

Dafuer schreibt der Waechter jetzt mit — vorher lebte sein Zustand nur im
Speicher und war nach jedem Neustart weg. Neue Tabelle watchdog_history,
gleiche Bauart und gleiche 7-Tage-Aufbewahrung wie player_history, stundenweise
zusammengefasst statt roh ausgeliefert.

Wichtig dabei: aufgezeichnet wird jeder Check, nicht nur die Wechsel. Sonst
saehen ruhige Stunden im Balken aus wie Messluecken.

Der Balken ist aus Server.jsx in components/UptimeTrack.jsx gewandert, damit
oeffentliche Seite und Panel denselben benutzen. Er zeigt die Antwortzeit im
Tooltip nur, wo sie erhoben wird — bei Game-Servern gibt es keine.

Der Endpunkt /api/watchdog liegt hinter der Rechtepruefung, nicht oeffentlich:
in der Adressliste koennen interne Dienste stehen.

Zwei fehlende Importe in api.js gefunden und ergaenzt (moduleEnabled, tuning) —
der erste Aufruf der Route waere sonst mit ReferenceError gestorben.

Geprueft: alle 171 SQL-Abfragen gegen das Schema, die drei neuen inklusive.
Im Browser mit drei Faellen — laeuft, ist weg, noch nie geprueft: Punktfarbe,
Quote, Ausfallstunden und Messluecken stimmen, die ungepruefte Adresse zeigt
korrekt gar keinen Balken. Oeffentliche Server-Seite nach dem Umbau
gegengeprueft.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 05:28:22 +02:00
D4rkst3randClaude Fable 5 2d4afad3cb Level-Embeds aufgewertet, Server-Footer korrigiert
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Server-Monitor: Der Footer behauptete fest "alle 2 min", obwohl das Intervall
seit den einstellbaren Werten aus monitor_interval kommt. Wer im Panel etwas
anderes setzt, bekam eine falsche Angabe. Liest jetzt den echten Wert.

Level-Aufstieg: War als einziges im Community-Bereich reiner Text, waehrend
Geburtstage, Verlosungen und Bewerbungen laengst Embeds sind. Jetzt ueber
brandEmbed mit Avatar, Gesamt-XP und Rest bis zum naechsten Level. Der Text
bleibt die Vorlage aus dem Panel — nur der Rahmen ist neu.

Dazu zeigt das Embed die Belohnungs-Rolle: entweder die gerade vergebene oder
die naechste, auf die man hinspielt. grantRewards() gibt dafuer zurueck, was
wirklich dazukam.

Ist die Vorlage im Panel leer, wirft setDescription('') — der Aufstieg waere
still ausgefallen. Faellt jetzt auf eine schlichte Zeile zurueck.

/rank: Avatar als Thumbnail, Podest-Zeichen fuer die ersten drei, Rest-XP und
naechste Belohnung als eigene Felder. Das Feld erscheint nur, wenn es
Belohnungs-Rollen gibt — sonst stuende dort ein leeres Feld.

Nebenbei zwei veraltete ephemeral: true auf flags: MessageFlags.Ephemeral
umgestellt, wie es der Rest der Befehle schon macht.

Geprueft: alle vier Embeds wirklich gebaut und ausgegeben, discord.js
validiert dabei. Das Intervall im Footer zog den auf 7 gestellten Wert.
Die negative XP-Differenz im ersten Testlauf war ein inkonsistenter
Testdatensatz — mit 4200 XP ist man Level 9, nicht 7; ueber 0..60000 XP
durchgespielt wird die Differenz nie negativ.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 05:21:56 +02:00