af16bc1b185c431ece0cb0eb9f7e6e2533ae03ec
154
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
af16bc1b18 |
YouTube als zweite Tonquelle: yt-dlp, Warteschlange, Abbild
Das Fundament, noch ohne Anbindung ans Radio. yt-dlp holt selbst und schiebt rohe Bytes durch ein Rohr an ffmpeg. Der bequemere Weg waere `-g` und die fertige Adresse -- der ist eine Falle: googlevideo-Adressen laufen ab und haengen an der IP, die sie geholt hat. Das schlaegt nach zehn Minuten zu und sieht dann aus wie ein Netzproblem. Beim Stroemen nach stdout ist die Vorgabe von yt-dlp *mit* Bild; ohne ein ausdrueckliches `-f bestaudio/best` laedt ein Tonstrom das ganze Video. Nachgelesen im README der Fassung 2026.08.19. stderr wird aufgehoben statt weggeworfen. Genau hier entsteht sonst der Fehler, den niemand deuten kann: YouTube antwortet "Sign in to confirm", yt-dlp bricht ab, der Bot sitzt still im Kanal. Die letzte ERROR-Zeile geht ins Panel. Gemessen am 28.08.2026 gegen echtes YouTube (yt-dlp 2026.08.19, von hier aus, NICHT auf dem Zielhost): Einzelvideo, Suche und flache Playlist liefern die erwarteten Felder, eine kaputte Video-ID meldet "Video unavailable" und wird als Fehler durchgereicht statt als leere Liste. Die Warteschlange steht in der Datenbank, nicht nur im Speicher -- ein Deploy soll die Musik unterbrechen, nicht die Wuensche von fuenf Leuten wegwerfen. Die YouTube-Tafel bekommt eine eigene Tabelle, weil `radio_state` beim Stoppen geloescht wird und die Tafel das ueberleben soll. Nebenbei: der ffmpeg-Kommentar im Dockerfile beschrieb noch die alte Ogg/Opus-Kette. Seit der Lautstaerkeregelung laeuft dort PCM. |
||
|
|
19010e9fde |
Die Panel-Anbindung entfaellt -- das Panel postet jetzt selbst
Entscheidung des Betreibers am 19.08.2026: das d4rk_gameserver-Panel
baut und stellt seine Status-Embeds ab sofort selbst in den Discord.
Damit hat diese Anbindung keinen Zweck mehr.
WARUM SIE UEBERHAUPT WEG SOLL, und nicht bloss ungenutzt liegenbleibt:
sie war die zweite Meinung ueber denselben Zustand. `zustandText()` gab
`null` zurueck, wenn ein Server LIEF UND BEREIT war -- also im
Normalfall -- und der Monitor fiel dann auf seine gamedig-Abfrage
zurueck. Erreichte die den Server nicht, stand im Kanal "Offline",
waehrend dasselbe Embed RAM und CPU aus dem Panel anzeigte. Ein
Widerspruch, den man von keiner der beiden Seiten aufloesen konnte.
ENTFERNT:
src/panel.js die Anbindung
src/panel-abgleich.js das Anlegen/Wegraeumen von Eintraegen
tools/panel-pruefen.mjs die zugehoerigen Proben
tools/panel-abgleich-pruefen.mjs
panelUrl/panelZeichen in runtime-settings.js
panel_url/panel_zeichen als Einstellung und im Formular
Servertyp `panel` er fragte nicht selbst, sondern las ab
WAS DER MONITOR DAMIT VERLIERT, und es gehoert dazugesagt: die
Schonfrist beim Hochfahren. Ein startendes Modpack antwortet minutenlang
nicht -- am ATM10 gemessen 306 Sekunden -- und meldet dadurch wieder
einen Ausfall, wo vorher "startet gerade" stand. Die
Zwei-Fehlschlaege-Schwelle federt das teilweise ab, nicht ganz.
Fuer Server im Panel ist das kein Verlust: dort postet das Panel und
kennt den Unterschied. Fuer FiveM und LS25 hat es ihn nie gegeben.
Geprueft: alle 281 relativen Importe loesen auf, die geaenderten Dateien
sind syntaktisch in Ordnung, das Frontend baut. Der Bot selbst ist NICHT
neu gestartet worden -- das ist eine Entscheidung des Betreibers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
0d7628f14e |
Den Entwurf und das Logo-Original einsortieren
Drei Dateien lagen unversioniert im Wurzelverzeichnis herum. Alle drei gehoeren ins Repo, nur nicht dorthin: logo.png -> docs/brand/logo.png ChatGPT Image 23. Juni 2025 ... -> docs/brand/studie-kopf-2025-06.png design_handoff_redesign/ -> docs/redesign/ logo.png ist NICHT irgendein Logo, sondern das Original, von dem docs/brand/README.md die ganze Zeit spricht: "ein einziges Original, 2048x2048 mit Transparenz". Nachgerechnet statt geraten -- auf den Inhalt beschnitten ergibt es 1986x1963, exakt die Masse von lockup.png. Es lag also die Quelle der Marke unversioniert daneben. Dabei aufgefallen und in der README vermerkt, nicht repariert: build.py liest das Original ueber einen festen Pfad ausserhalb des Repos (QUELLE = A:/Users/DAR/logo.png). Jetzt sind das zwei Kopien, die auseinanderlaufen koennen. Umstellen kann nur, wer weiss, welche der beiden gepflegt wird. Das zweite Bild ist eine frueherere Kopf-Studie und nicht die Vorlage der heutigen Marke -- andere Zeichnung, flacher, ohne Fell und Schulter. Sie wird nirgends benutzt und liegt als Herkunft. Vom Entwurfs-Paket ist der assets/-Ordner entfallen: er enthielt Kopien von mark.png und lockup.png, die zwei Verzeichnisse weiter im Original liegen. Die .dc.html-Referenzen zeigen jetzt auf ../brand/ und stellen das Logo weiter dar, statt mit kaputten Bildern zurueckzubleiben. Der Kopf der README sagt ausserdem, dass der Entwurf umgesetzt ist und wo bewusst abgewichen wurde -- sonst liest sich das Dokument in einem Jahr wie eine offene Aufgabe. Dazu eine Zeile, die seit dem Redesign nicht mehr stimmte: build.py bezeichnet (10, 10, 10) als "--bg der Marke". Das ist seit gestern #131316. Wirkt erst, wenn jemand build.py laufen laesst -- die erzeugten Icons in frontend/public/ tragen bis dahin den alten Grund. Neu bauen konnte ich sie hier nicht, es gibt weder Python noch Pillow auf dieser Maschine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
25d36c7269 |
Webseiten-Redesign: Graphit + Orange, abgeschraegte Ecken
Umsetzung des Entwurfs aus design_handoff_redesign/ -- beide Auftritte (Produktseite und Community-Hub) plus Dashboard. Farbe #0a0a0a + Neon-Gelb -> #131316 + Orange-Verlauf aus dem Logo Schrift Bebas/Barlow/ShareTech -> Chakra Petch + IBM Plex Mono Form border-radius -> clip-path, oben links + unten rechts Rauschen, Scanlines, Hintergrundschrift, Kopf-Raster und die Partikel sind raus; der Schein von oben rechts ersetzt sie. Die Abschraegung ist ein System und keine sechzig Einzelregeln: eine clip-path-Regel mit --bv, jede Komponente setzt nur die Tiefe. Der Farbdurchstich ging durch alle ~55 Abschnitte der style.css, nicht nur durch die fuenf gezeichneten Seiten -- sonst waeren Galerie, Level, Composer und Emoji-Auswahl im alten Gelb stehen geblieben. Von --neon ist nichts uebrig. Die Schriften kommen weiter self-hosted ueber @fontsource. Der Entwurf schlaegt einen Google-Fonts-Link vor; das waere eine Fremdanfrage mehr, wo der Aufbau bisher ohne auskam. ZWEI FEHLER unterwegs gefunden, beide aelter als dieser Umbau: 1. .btn stand in der Datei NACH .btn-save und .btn-ghost. Bei gleicher Spezifitaet gewinnt die spaetere Regel, also hat ihr `background: none` jeden Verlauf ueberschrieben -- der Speichern-Knopf sah aus wie ein Ghost-Knopf, schon in der gelben Fassung. Die Grundform steht jetzt vor allen Abwandlungen. 2. .nav-logo ist ein Flex-Container, und `gap` legte sich zwischen die Textknoten "D4RKST" und <span>3R</span>. In der Leiste stand "D4RKST 3R". Der Abstand haengt jetzt am Bild statt am Container. DREI ABWEICHUNGEN vom Entwurf, bewusst: - Die Statuszeile im Hero zeigt nur "ONLINE", nicht "38 MS · UPTIME 99,2 %". Antwortzeit und Erreichbarkeit des Bots liegen ausschliesslich hinter /api/settings und damit hinter der Anmeldung; oeffentlich gibt es sie nirgends. Dafuer braeuchte es einen neuen Endpunkt -- erfunden wird hier nichts. - Keine Aura am Hauptknopf: clip-path schneidet den Schatten des eigenen Elements mit weg. Im Referenz-HTML stehen box-shadow und clip-path am selben Element, dort ist sie also ebenso wenig zu sehen. Tote Deklaration weggelassen statt mitgeschleppt. - Die Config-Bereiche bleiben scharfkantig statt abgeschraegt. In ihnen klappen Auswahllisten aus dem Panel heraus, die clip-path abschneiden wuerde. Deckt sich mit der Vorgabe, dass die uebrigen Tabs nur die Token uebernehmen; die Dashboard-Panels selbst sind abgeschraegt. Dazu: .content von 860 auf 1160 px, weil die dreispaltigen Raster es brauchen -- die Devlog-Timeline behaelt 880 px Lesebreite, sonst waeren die Zeilen zu lang. Der Punkt an den Timeline-Karten ist entfallen: er sass ausserhalb der Karte und waere von der Abschraegung weggeschnitten worden. Welches Wort in einer Ueberschrift den Verlauf bekommt, steht jetzt im Woerterbuch -- der Teil in eckigen Klammern ("Funk[tionen]", aber "Fea[tures]"). So entscheidet jede Sprache selbst, wo sich das Wort teilen laesst. Mitgezogen: brand.js, das geteilte Design-System fuer die eingebundenen Dienste. Neue Tokens und Abschraegungen, aber --neon/--neon2 bleiben als Zweitname stehen -- fremde Apps benutzen sie in ihrem eigenen CSS. Marken-Dateien nach frontend/public/brand/, verkleinert auf die Groessen, in denen sie wirklich dargestellt werden: 2,4 MB -> 97 KB. Geprueft: Build laeuft. Im Browser durchgesehen -- Landing, Funktionen, Befehle, Hub-Start, Devlogs, Server, Roadmap, Dashboard. Ungeprueft: das Dashboard mit echter Anmeldung. Eine Sitzung war hier nicht moeglich, gesehen habe ich es ueber gestubbte API-Antworten. Ebenso ungeprueft, wie sich die Schriftumstellung auf schmalen Schirmen macht. NICHT angefasst: die Embed-Farben in runtime-settings.js stehen weiter auf dem alten Gelb -- die wirken in Discord, nicht auf der Webseite. Und ist brand_color in der Datenbank gesetzt, behalten die eingebundenen Dienste ihre bisherige Farbe, unabhaengig von den neuen Vorgabewerten. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5394d5c0c6 |
Die zwei Sicherungs-Wachhunde abschalten
Auf Ansage des Betreibers vom 18.08.2026. Gemeldet hatten sie:
pruefeSicherung -> "Die Sicherung ist fehlgeschlagen"
(zuletzt: MKCOL Sicherungen -> HTTP 000)
pruefeZweitziel -> "Bei dir zu Hause kommt keine Sicherung mehr an"
Beide bewachen Ziele, die es nicht mehr geben soll: Nextcloud wird nicht mehr
benutzt, und abgeholt wird auch nichts mehr. Die Sicherung bleibt auf dem
Server, darum kuemmert sich das Gameserver-System.
Ein Wachhund, der ein aufgegebenes Ziel bewacht, meldet jeden Tag denselben
Fehler -- und genau davon lernt man, Meldungen dieses Bots zu ueberlesen. Das
ist teurer als die Meldung wert war.
NICHT GELOESCHT, sondern abgeschaltet: beide Funktionen stehen unveraendert
im Modul. Wer Nextcloud oder die Abholung wieder einrichtet, macht eine Zeile
frei. Geprueft: node --check sauber, und ausser der abgeschalteten Zeile ruft
niemand mehr pruefeSicherung auf.
Die taegliche Sicherung selbst ist NICHT angefasst -- sie laeuft weiter, und
ihr eigener Fehlerfall meldet weiter.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
c81e862992 |
Wo der Bot nicht hinkommt, fragt das Panel -- Typ panel
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".
|
||
|
|
e39d4b33b6 |
Protokoll: melden, was Moderation ist -- nicht alles, was passiert
Der Betreiber bekam "fuer alles einen Alert". Zu Recht: sechs verschiedene
Meldungen liefen in denselben Kanal, und dahinter stand ein einziger
Modulschalter -- entweder alles oder nichts.
geloescht / bearbeitet / Nick+Rollen / beigetreten / gegangen / AutoMod
Zwei davon waren der eigentliche Laerm, und beide sind gar keine
Moderationsvorgaenge:
1. Jede selbst zurueckgenommene Nachricht wurde gemeldet. Wer seinen eigenen
Tippfehler loescht, ist kein Vorfall.
2. Jede Rollenaenderung, die der Bot selbst ausgeloest hat -- Rollenmenue-Klick,
Level-Aufstieg, Sticky-Restore nach Wiedereintritt, Playtester-Knopf. Der
Bot hat sich selbst bei der Arbeit zugesehen und darueber Protokoll gefuehrt.
Beides wird jetzt gefiltert. Erkannt ueber Discords Audit-Log und nicht ueber
Merker an den vier Stellen, die Rollen vergeben -- so ist auch die fuenfte
erfasst, die jemand spaeter baut.
Dafuer musste werHatGeloescht() zuerst repariert werden. Sie gab fuer VIER
verschiedene Lagen dasselbe `null` zurueck: kein Server, kein Recht aufs
Audit-Log, Abfrage fehlgeschlagen, und "der Verfasser war es selbst". Zum
Anzeigen reichte das ("dann eben ohne Angabe"). Als Filter waere es die
schlimmste Sorte Fehler gewesen: fehlt dem Bot das Recht, saehe "selbst
geloescht" genauso aus wie "konnte nicht nachsehen" -- und das Protokoll
haette stillschweigend alles verschluckt, ohne dass es jemandem auffaellt.
Jetzt drei unterscheidbare Antworten: { wer } / { selbst } / { unklar, Grund }.
Verschwiegen wird nur, was sicher selbst geloescht wurde. Unklares wird
gemeldet, und der Grund steht im Embed, damit niemand die Zeile fuer "von
einem Mod geloescht" haelt. Fehlt das Recht, sagt das Log es einmal und dann
nicht wieder -- sonst waere die Meldung selbst wieder Rauschen.
Dazu vier Einzelschalter im Panel, weil "alles oder nichts" die Ursache war.
Vorgabe an: ein Protokoll, das nach einem Update stillschweigend aufhoert zu
protokollieren, waere die schlechtere Ueberraschung. Gelesen wird mit
!== '0', geschrieben werden sie einmalig als '1' -- das Panel liest
Feld-Schalter naemlich mit === '1', und ein Formular, das das Gegenteil
dessen zeigt, was gilt, ist schlimmer als gar keins.
Aufgepasst bei den Schaltern: an drei der Handler haengt mehr als nur die
Meldung. Begruessung und Auto-Rollen beim Beitritt, das Sichern der Rollen
beim Austritt, und das Nachfuehren des Nachrichten-Gedaechtnisses beim
Bearbeiten laufen weiter, auch wenn niemand die Meldung sehen will. Sonst
haette ein Protokoll-Schalter das Verhalten des Servers geaendert.
Geprueft mit gestellten Objekten, zwoelf Faelle: kein Server, kein Recht,
Abfrage wirft, kein Eintrag, Eintrag vom Autor selbst, Eintrag zu alt, Mod
war es -- und fuer die Rollen: Bot war es, Mensch war es, kein Eintrag, kein
Recht, Abfrage wirft. Im Zweifel wird immer gemeldet. Dazu die Schalter gegen
die Panel-Anzeige: beide sagen dasselbe.
Ungeprueft: der Lauf gegen ein echtes Audit-Log. Ob der Bot das Recht
"Audit-Log ansehen" hier ueberhaupt hat, steht nach dem Ausrollen im Log.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
582d68580b |
Drei Befunde aus der Durchsicht: /health, Herunterfahren, ephemeral
1. /health konnte nicht fehlschlagen
app.get('/health', async () => ({ status: 'ok' }));
Ein fester Wert, ohne irgendetwas anzusehen. Das meldet "sauber", solange
der HTTP-Faden laeuft -- und der laeuft auch dann noch, wenn die Verbindung
zu Discord weg ist. Ein Bot ohne Gateway ist fuer alle draussen genauso weg
wie ein abgestuerzter, nur merkt es niemand. NPM fragt die Adresse an, zwei
Hosts, alle paar Minuten; sie hat immer 200 gesagt.
Das Bittere: die Auskunft lag laengst vor. heartbeat.js schreibt alle zwei
Minuten client.isReady() mit (gemessen an der laufenden Datenbank: 9557
Zeilen, letzter Schlag ok=1, 104 ms). Der Herzschlag wusste es, /health hat
nicht gefragt.
Jetzt geprueft: steht die Verbindung, und antwortet die Datenbank. Sonst 503
mit Grund -- 503 und nicht 500, das ist kein Programmfehler, sondern ein
Zustand, der vorbeigeht. Der Grund steht dabei, sonst faengt das Raten wieder
von vorn an.
Dazu ein HEALTHCHECK im Dockerfile, denn sonst reagiert niemand darauf.
Gemessen: d4rkbot hatte keinen (<nil>), d4rk-media hat einen und steht auf
"healthy". curl und wget fehlen im slim-Abbild, also Node selbst; die
Exitcodes sind gegengeprueft (erreichbar -> 0, tote Adresse -> 1).
Fuer die Datenbankprobe eine echte Abfrage und nicht existsSync auf die
Datei: die Datei ist auch dann noch da, wenn das Dateisystem nur noch lesbar
ist.
2. Kein Handler fuers Beenden
Kein SIGTERM, kein SIGINT im ganzen Quelltext. Bei `docker stop` wird der
Prozess nach der Schonfrist abgeraeumt, und zwei Dinge geben danach falsche
Auskunft: der Bot steht in Discord noch eine Weile als online, und die
Statuszeile am Sprachkanal behauptet weiter, es liefe Musik -- die ueberlebt
den Container, der sie geschrieben hat.
Reihenfolge ist wichtig: erst das Radio, denn zum Abraeumen der Statuszeile
braucht es die Verbindung zu Discord noch. Dann Webserver, dann Discord.
Jeder Schritt einzeln abgesichert, sonst verhindert ein Fehler beim
Aufraeumen das restliche Aufraeumen. Nach acht Sekunden bricht es selbst ab,
weil Docker nach zehn schiesst.
radio_state bleibt dabei absichtlich stehen -- der Merker ist genau dafuer
da, dass der Bot nach einem Deploy von selbst zurueckkommt.
3. ephemeral: true
Einzige Stelle im ganzen Bot, ueberall sonst steht flags:
MessageFlags.Ephemeral. discord.js 14.27 wertet es noch aus
(InteractionResponses.js:118), warnt aber, und in v15 faellt es weg -- dann
waere ausgerechnet die Fehlermeldung ploetzlich oeffentlich.
Was die Durchsicht sonst ergab, gehoert genauso hierher: 125 Routen
durchgezaehlt, keine einzige aendernde ohne Wache. Keine Nutzereingabe in
SQL. Der Mod-Download prueft gegen die Modliste statt gegen ein Namensmuster.
Die Sicherung packt ihr Archiv wieder aus und laesst integrity_check laufen.
Keine Geheimnisse im Log. Daran war nichts zu verbessern.
Ungeprueft geblieben: 76 Stellen mit stillschweigend verschlucktem Fehler
(catch {}), das Frontend, und ob NPM schon drosselt -- eine
Ratenbegrenzung hat die API naemlich nicht. Entschaerft dadurch, dass alle
aendernden Routen hinter einer Wache liegen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
1154e7a6a5 |
Radio: Lautstaerke im Panel -- und leise reinkommen statt erschrecken
Der Anlass ist eine Beobachtung aus dem Betrieb: der Bot kam mit voller
Aussteuerung in den Kanal, und wer schon drin sass, ist erschrocken. Dass
sich jeder den Bot fuer sich leiser drehen kann, hilft erst *nachdem* es zu
laut war. Also liegt die Vorgabe jetzt bei 50 %, und lauter machen kostet
zwei Knopfdruecke.
Dafuer musste die Tonkette umgebaut werden. Bisher lief Opus unveraendert
durch (ffmpeg -> Ogg/Opus -> Discord). Das war eine Stufe sparsamer, aber an
fertigen Opus-Paketen laesst sich die Lautstaerke nicht drehen -- dafuer muss
man an die Abtastwerte. Jetzt: ffmpeg gibt PCM aus, die Sprachbibliothek
regelt und kodiert.
Der alte Einwand im Dateikopf ("sonst muesste @discordjs/opus mit ins Image,
und das will gebaut werden") stimmt weiterhin -- fuer @discordjs/opus.
Nachgemessen am 14.08.2026 in node:22-slim:
@discordjs/opus npm i bricht ab, kein Prebuild fuer node-v127/glibc-2.36,
node-gyp will bauen, im Image fehlen die Werkzeuge
opusscript reines JavaScript, installiert in 740 ms, kein Compiler
0,3 % eines Kerns fuers Kodieren eines Dauerstroms
Der Umweg ueber Opus-Dekodieren entfaellt dabei ganz, ffmpeg liefert ja schon
Abtastwerte. Deshalb 0,3 % und nicht 2,9 %, was Dekodieren und Kodieren
zusammen gekostet haetten.
Die Kette ist mit echtem ffmpeg gegen einen Sinuston geprueft: 200
Opus-Pakete fuer 4,0 s Ton, kein Versatz, und die Lautstaerke laesst sich
mitten im Strom aendern. Genau darum geht der Weg ueber PCM und nicht ueber
ffmpegs volume-Filter -- der haette bei jedem Klick einen Neustart des
Prozesses gebraucht, also eine hoerbare Luecke.
Am Panel zwei Knoepfe in der bestehenden Reihe, Schritte von 10, Bereich 10
bis 200. Am Anschlag graut der Knopf aus, statt sich druecken zu lassen und
nichts zu tun. Die Rueckmeldung ist die neu gezeichnete Tafel mit dem neuen
Prozentwert.
Der Wert steht in den Einstellungen, nicht in radio_state: der Merker dort
wird beim Stoppen geloescht, die Lautstaerke soll das ueberleben.
Dazu der Kanal-Status: was laeuft, steht jetzt auch in der Statuszeile des
Sprachkanals und wird mit dem Titel nachgefuehrt.
ACHTUNG, das ist ein UNDOKUMENTIERTER Endpunkt.
PUT /channels/{id}/voice-status -- Discord hat ihn nie in die API-Doku
aufgenommen, die Pull Requests dazu liegen seit 2023 offen, und discord.js
hat die Umsetzung als "not planned" geschlossen. Roher Aufruf ueber
client.rest also, und er kann ohne Ankuendigung verschwinden.
Deshalb ist er als Beiwerk gebaut: faellt er aus, sagt er einmal warum und
schweigt dann -- ein Fehler alle 30 Sekunden waere kein Hinweis mehr,
sondern Rauschen. Das Radio laeuft in jedem Fall weiter. Der naechste
Sender-Start versucht es erneut, falls das Recht inzwischen erteilt wurde.
Das Recht heisst "Kanalstatus festlegen" und sitzt auf Bit 48. discord.js
kennt es nicht einmal als Konstante, deshalb steht die Zahl im Quelltext.
Ungeprueft, weil dafuer ein echter Sprachkanal noetig ist: ob das Recht dem
Bot hier tatsaechlich erteilt ist. Faellt es aus, steht der Grund im Log.
Beim Stoppen wird die Zeile geleert -- sonst behauptet sie stundenlang, es
liefe ein Lied, das laengst vorbei ist.
Nebenbei: prism-media wurde direkt importiert, stand aber nie in der
package.json. Der Import ist mit dem Ogg-Auspacker weggefallen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
f08a0e8b35 |
Radio: es war nie UDP -- Discord verlangt DAVE
Der Mitschnitt von 12:33 hat es benannt: Netzteil: Websocket auf -> Anmeldung -> zu -> Websocket zu (4017) -> zu 4017 heisst DAVE. Discord erzwingt seit dem 01.03.2026 Ende-zu-Ende- Verschluesselung in allen Nicht-Stage-Sprachkanaelen und weist Gegenstellen, die das nicht koennen, sofort nach der Anmeldung ab -- lange bevor irgendein UDP-Paket fliegt. Nachgeschlagen bei Discord, nicht gedeutet. Gemessen, warum es uns traf: @discordjs/voice 0.18.0 kennt DAVE ueberhaupt nicht (null Fundstellen im Bundle) und spricht Sprach-Gateway v4. Es meldet deshalb max_dave_protocol_version: 0. 0.19.2 spricht v8, bringt @snazzah/davey als feste Abhaengigkeit mit und hat 48 Fundstellen. Also hochgezogen. Im gebauten Abbild gegengeprueft, weil das native Modul plattformabhaengig ist und der Lock auf Windows entstanden ist: DAVE Libraries - @snazzah/davey: 0.1.12 @snazzah/davey-linux-x64-gnu steht im Lock, node:22-slim ist Debian/glibc, das Modul laedt im Abbild. engines steht jetzt auf >=22.12.0 -- das verlangt 0.19.2, und ">=20" waere ab jetzt gelogen. Und ein Fehler in der frischen Diagnose selbst, den erst der Ernstfall zeigte: sie hat den Schritt nicht benannt, obwohl der Mitschnitt ihn enthielt. Das Netzteil schliesst sich erst und meldet dann den Code, also endet jeder Mitschnitt auf "zu" -- und genau den nahm die Auswertung als letzten Schritt. Damit lief alles in "Ursache offen". Jetzt wird "zu" uebersprungen; die Gegenprobe mit 4009 landet wieder beim UDP-Handschlag statt im Nichts. Nebenbei stand "zu" zweimal im Mitschnitt, einmal vor und einmal nach dem Schliesscode: die Entdopplung verglich mit dem letzten Eintrag statt mit dem letzten Schritt. Der Weg hierher gehoert zur Sache: erst hiess es "ausgehendes UDP dicht" -- geraten. Dann wurde gemessen, dass UDP rauskommt, und die Meldung sagte ehrlich "Ursache offen". Diese eine ehrliche Meldung hat den Schliesscode sichtbar gemacht, und der war die Antwort. Eine Pruefung, die nichts sehen kann, meldet nicht "sauber" und auch nicht "UDP". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
484f2edc62 |
Radio: den Schliesscode festhalten, statt UDP zu behaupten
Die Meldung "ausgehendes UDP dicht" war geraten. Am 14.08.2026 im laufenden Container gegengemessen, und sie stimmt nicht: UDP raus (DNS) 1.1.1.1:53 4 ms UDP auf hohem Port (STUN) stun.l.google.com:19302 9 ms TCP zum Sprachserver c-fra13-...discord.media:2096 offen Verschluesselung libsodium-wrappers 0.8.4 geladen UDP kommt also raus, auch auf hohen Ports und mit Rueckweg durchs NAT. Der Satz stand trotzdem da, weil "Weg enthaelt connecting" als Beweis genommen wurde. Ist es nicht: in @discordjs/voice 0.18 faellt die Verbindung bei *jedem* Websocket-Schluss ausser 4014 stumm nach "signalling" zurueck (onNetworkingClose) und wirft den Schliesscode dabei weg. Ob der UDP-Teil je begonnen hat, steht nirgends -- die Pruefung konnte den Unterschied nicht sehen und hat trotzdem geurteilt. Jetzt wird das Netzteil selbst mitgeschrieben: seine Schrittnummer (NetworkingStatusCode, nachgeschlagen -- Websocket auf / Anmeldung / UDP-Handschlag / Protokollwahl) und der Schliesscode. Nicht ueber `debug`, denn das druckt Token und Sitzungsschluessel mit. Daraus folgt die Diagnose: stehen bei UDP-Handschlag -> jetzt darf sie UDP sagen zu vor dem UDP-Teil -> Sitzung (4006/4009), kein Netzproblem stehen bei Protokollwahl -> Verschluesselung kein Mitschnitt -> "Ursache offen". Nicht mehr "UDP". Warum der UDP-Fall so aussieht, als haenge er: performIPDiscovery hat in 0.18 keine Zeitschranke, und die UDP-Keepalives werden nicht mehr ausgewertet. Bleibt die Antwort aus, steht der Schritt, bis Discord den Websocket zumacht. Drei Sachen noch, die beim Nachsehen auffielen: - Ein Fehler aus dem Netzteil hat den ganzen Prozess beendet. VoiceConnection reicht ihn als 'error' weiter, und ein EventEmitter ohne 'error'-Hoerer wirft. Genau dieser Weg ist im Fehlerfall offen -- die UDP-Erkennung meldet sich darueber. Jetzt gibt es einen Hoerer. - schritte.endpunkt wurde bei jedem VOICE_SERVER_UPDATE ueberschrieben. Bei einem Rueckfall nennt Discord den Server erneut, also verdeckte die zweite Meldung den Endpunkt, an dem es scheiterte. Jetzt gesammelt, und fuer die Diagnose zaehlt der erste echte. - tools/ landete nie im Abbild (COPY src ./src). Der Aufruf im Kopf von udp-pruefen.mjs -- "docker exec ... node tools/udp-pruefen.mjs" -- lief ins Leere. Jetzt kopiert das Dockerfile es mit. Die neun Diagnose-Zweige sind gegen die exportierten Funktionen im Container durchgespielt, nicht gegen eine Kopie. Was weiter offen bleibt: eine Sperre speziell auf Zielports 50000-65535 ist nicht widerlegt -- dafuer fehlt eine Gegenstelle, die dort antwortet. Der naechste Fehlversuch sagt es von selbst. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
228853febd |
Radio: die zweite Sackgasse von der ersten trennen — und UDP messbar machen
Aus dem Betrieb: alle drei Beitrittsschritte durch, trotzdem "signalling".
Das ist kein Widerspruch. In @discordjs/voice gibt es zwei Wege dorthin:
- `configureNetworking` steigt aus, wenn der Endpunkt leer ist. Discord
schickt das bei einem Regionswechsel; kommt keine zweite Meldung, bleibt
die Verbindung fuer immer stehen.
- Scheitert der Austausch mit dem Sprachserver, wirft die Bibliothek die
Verbindung nach "signalling" zurueck und faengt von vorn an. Von aussen
sieht das genauso aus.
Deshalb schreibt der Adapter jetzt den Endpunkt mit, und die Verbindung
protokolliert ihren Weg. War sie einmal bei "connecting" und ist
zurueckgefallen, ist es der UDP-Austausch — dann steht der Endpunkt in der
Meldung, damit man ihn von Hand pruefen kann.
Dazu tools/udp-pruefen.mjs: im Container aufrufen und es sagt, ob UDP
ueberhaupt rauskommt und ob auch auf hohen Ports. Genau das unterscheidet
"Firewall zu" von "Firewall laesst nur die ueblichen Ports durch" — und
Discord-Sprache braucht die hohen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
bbb56e1276 |
Radio: den Beitritt in drei Schritte zerlegen statt "signalling" zu melden
"signalling" sagt nur, dass etwas fehlt, nicht was. Ein Beitritt zum Sprachkanal ist ein Dreischritt: 1. Der Bot bittet Discord ueber die bestehende Verbindung um den Beitritt. 2. Discord antwortet mit dem eigenen Sprachstatus (darin die Sitzungs-ID). 3. Discord nennt den Sprachserver. Der Adapter zwischen Verbindung und Sprachbibliothek haelt jetzt fest, welcher davon stattgefunden hat. Aus den drei Haekchen wird ein Satz, der sagt, was zu tun ist — fehlende Berechtigung, fehlender Intent, oder ein Netz, das UDP nicht rauslaesst. Fuenf Faelle, fuenf verschiedene Antworten; der Test besteht darauf, dass keine zwei gleich lauten. Dazu eine Pruefung vorweg, die genau dasselbe Bild erzeugt haette: ein voller Sprachkanal. Discord nimmt die Anfrage entgegen und antwortet dann einfach nicht mehr — das sieht aus wie ein Rechteproblem. Wer Mitglieder verschieben darf, kommt am Limit vorbei, sonst nicht. Nachgesehen und ausgeschlossen: discord.js 14.27 verdrahtet beide Sprachereignisse (VOICE_STATE_UPDATE ueber die Action, VOICE_SERVER_UPDATE ueber den Handler), und GuildVoiceStates steht in den Intents. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e246dadbec |
Zwei Befunde aus dem Betriebslog
1. /api/auditlog antwortete mit 500. `displayAvatarURL({ size: 48 })` — 48 ist
keine Zweierpotenz, Discord nimmt nur 16 bis 4096. Ueberall sonst im Code
stehen 64 oder 128, nur hier nicht. Der Wurf lief durch das map, also riss
ein einziges Avatar die ganze Liste mit. Jetzt 64, und die Bildsuche faengt
ihre Fehler selbst ab. Dazu eine Pruefung, die verbotene Groessen im
Quelltext findet, bevor sie jemand im Log findet.
2. "Sprachverbindung kam nicht zustande" sagt jetzt, WO sie stehen blieb.
Das entscheidet naemlich die Ursache: bleibt sie bei `signalling`, hat
Discord nie einen Sprachserver genannt (Rechte, Gateway). Bleibt sie bei
`connecting`, ist der Sprachserver bekannt und der UDP-Austausch mit ihm
scheitert — praktisch immer gesperrtes ausgehendes UDP. Beides steht jetzt
im Log und in der Antwort auf /radio.
Dazu der dokumentierte Umgang mit Close-Code 4014: rausgeworfen oder
verschoben heisst nicht wiederverbinden, sondern aufhoeren.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
4d71122820 |
Radio: Schalter dorthin, wo man ihn sucht — und Stille sagt jetzt warum
Drei Sachen, alle vom selben Bericht ausgeloest. 1. Der Schalter war nur im Modul-Reiter zu finden, die Meldung schickte aber nach "Community". Beim Nachsehen kam heraus: Modul-Schalter haben in /api/settings gar keinen Weg — jeder stand von Hand in api.js, und fuenf fehlten dort (radio, automod, anti_raid, linked_roles, ls_farm). Die liessen sich nur ueber den Modulreiter umlegen. Jetzt kommen sie wie Kanaele, Rollen und Texte aus dem Register; jeder neue ist damit von allein speicherbar. Dazu steht der Haken jetzt oben im Radio-Abschnitt, wo auch die Sender stehen. 2. Der Bot sass im Sprachkanal und schwieg. Ursache im Code: nach joinVoiceChannel wurde sofort losgespielt. Der Aufruf kommt zurueck, sobald der Bot sichtbar im Kanal steht — der UDP-Weg wird danach erst ausgehandelt. Wer vorher sendet, sendet ins Leere. Jetzt wird auf VoiceConnectionStatus.Ready gewartet, und schlaegt das fehl, sagt der Befehl es statt still dazustehen. 3. Stille erklaert sich. Fehlt ffmpeg im Container, setzte sich der Bot bisher hin und schwieg — nichts stuerzte ab, nichts fehlte sichtbar. ffmpeg wird jetzt beim Start geprueft, /radio verweigert mit klarer Ansage, und kommt binnen acht Sekunden kein Ton, steht der Grund in der Tafel: fehlendes ffmpeg oder ein Sender, der nichts liefert. Nebenbei: -analyzeduration stand hinter -i und wurde damit als Ausgabe-Option gelesen, also ignoriert. Geprueft: die Tonkette laeuft lokal gegen einen echten Sender durch bis zum Abspieler (4,1 s Ton in 6 s Laufzeit, Status bleibt playing). Alle 16 Modul-Schalter gehen angemeldet durch /api/settings hin und zurueck. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
89f6dfcd29 |
Radio: Webradio im Sprachkanal, 44 Sender zum Durchschalten
/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> |
||
|
|
b7f54185f7 |
fix: ein schon von Hand eingetragener Server wird uebernommen, nicht uebergangen
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> |
||
|
|
0982e8cd1b |
feat: Server aus dem Panel uebernehmen -- und nur die auch wieder loeschen
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> |
||
|
|
21eab627b9 |
fix: die Panel-Anbindung steht jetzt dort, wo man sie sucht
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> |
||
|
|
d6124fc2cd |
feat: der Monitor fragt das Panel, wenn eine Abfrage nicht ausreicht
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.
|
||
|
|
bce75f8c65 |
docs: Befund 1 geschlossen -- die Sicherung verlaesst die Maschine
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>
|
||
|
|
6057bdb0d7 |
docs: Befund 1 -- was gebaut wurde und was ausserhalb des Repos liegt
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> |
||
|
|
720785c175 |
backup: die Sicherung verlaesst das Volume -- tools/abholen.ps1 plus Wachhund
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>
|
||
|
|
40ede9c56a |
docs: Richtigstellung -- der Auto-Deploy funktioniert, ich war zu ungeduldig
Zwei falsche Diagnosen hintereinander, beide aus einem Zwischenstand
geschlossen, waehrend der Vorgang noch lief. Was wirklich passiert ist:
12:48:18 Push
|
||
|
|
b98f4e15c9 |
fix: pull_policy build -- der Auto-Deploy zog, baute aber nie
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>
|
||
|
|
b63d4cae3b |
docs: der Auto-Deploy hat nicht ausgeloest -- gemessen, nicht vermutet
Nach dem Push von
|
||
|
|
93156e5205 |
backup: gegenpruefen, nachholen, und endlich Bescheid sagen
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>
|
||
|
|
1808b7c40b |
docs: Durchsicht -- die Sicherung liegt neben dem Original, und niemand merkt ihren Ausfall
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>
|
||
|
|
5ac079699c |
watchdog: sagen WARUM, nebeneinander pruefen, Gespenster aufraeumen
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>
|
||
|
|
1ef06f747c |
Modliste: melden was sich geaendert hat, einzeln laden
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> |
||
|
|
9b681754fa |
Ein Embed je Hof, das sich selbst aktualisiert
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> |
||
|
|
0875dff127 |
Server-Embed aufgeraeumt: volle Reihen statt halber
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> |
||
|
|
ea523b76b3 |
Servername aus dem Spiel und ein Schloss fuer Passwort oder Whitelist
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> |
||
|
|
4ce3c10d76 |
Kein Monatswechsel mehr aus einer kaputten Antwort
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> |
||
|
|
b69cb6c5e4 |
Freie Links je Server statt fester Felder
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> |
||
|
|
5de172f862 |
LS bekommt einen festen Platz unter Gaming
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> |
||
|
|
9f929fe0e4 |
Eigene Kategorie Gaming fuer Game-Server und LS
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> |
||
|
|
1075b91121 |
Kanaele werden auch angezeigt, Monate statt Schluessel, Sorten auf Deutsch
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> |
||
|
|
c5e13182c0 |
Modul-Felder speichern wieder, und die Feed-Adresse ist einstellbar
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> |
||
|
|
768fcda666 |
Karte: die Welt ist doppelt so breit wie mapSize
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> |
||
|
|
e215cd4c7c |
LS-Modul: Besitzkarte auf dem Luftbild und Preiskurven
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> |
||
|
|
eec170ff41 |
Connect-Links im Discord anklickbar, und Mods bekommen einen eigenen Knopf
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>
|
||
|
|
8981eb91b8 | Ganze Abfrage-URL einfuegen statt drei Felder tippen | ||
|
|
4e7fc99999 |
Server-Seite ueberarbeitet, und die Spielauswahl kommt jetzt aus gamedig
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> |
||
|
|
22054c109e |
Mod-Log: eigenes Gedaechtnis, damit beim Loeschen nicht "unbekannt" dasteht
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>
|
||
|
|
b6b767acb7 |
Bis fuenf Eintraege Knoepfe statt Auswahlmenue
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> |
||
|
|
b3758f1f76 |
Screenshots zu Ideen, und der Bot legt die Forum-Tags selbst an
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> |
||
|
|
5073e7bcf4 |
Jede Idee bekommt ihre eigene Seite mit der Unterhaltung aus dem Thread
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> |
||
|
|
1856a1ed83 |
Foren waehlbar, Emoji-Tafel repariert, Balken sagt seit wann er misst
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> |
||
|
|
cc7a51f02e |
Emojis zum Anklicken, und Wuensche als Forums-Beitraege mit Tags
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>
|