5ac079699ca0f5d9cd09ced97409e79926bf3108
126
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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>
|
||
|
|
edf8fa14c5 |
Ideen bekommen Bereiche und einen Thread zum Reden
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> |
||
|
|
fb61e7279d |
Wuensche bekommen einen Ausgang statt nur einer Punktzahl
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> |
||
|
|
1c5c7d2470 |
Playtester werden jetzt beworben, nicht angeklickt
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> |
||
|
|
ddd7610d6c |
Playtester bekommen einen eigenen Bereich, Module ziehen um
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> |
||
|
|
bb2aee7818 |
Tickets: Anliegen zur Auswahl, und das Team kann uebernehmen
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> |
||
|
|
2292dedeaa |
README auf den heutigen Stand bringen und neu ordnen
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> |
||
|
|
e32152b5ac |
Eigener Herzschlag: der Bot kann jetzt auch ueber sich selbst Auskunft geben
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> |
||
|
|
3080f52e71 |
AutoMod sichtbar machen: Treffer ins Protokoll, Regeln ins Panel
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> |
||
|
|
c8b1f604f0 |
Umfragen: /umfrage als native Discord-Abstimmung
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> |
||
|
|
52a735f83a |
SPA-Fallback beantwortet auch HEAD
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> |
||
|
|
d9f13b4d92 |
Leere Seiten behoben: IconLink war benutzt, aber nicht importiert
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> |
||
|
|
137452da11 |
Linked Roles: Rollen, die an geprueften Werten haengen
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> |
||
|
|
55e9ef57e2 |
Oeffentliche Statusseite nach Statuspage-Vorbild
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>
|
||
|
|
fd2d31f07f |
Watchdog: Embeds statt Textzeilen, dazu eine Uebersicht im Panel
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> |
||
|
|
2d4afad3cb |
Level-Embeds aufgewertet, Server-Footer korrigiert
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>
|
||
|
|
8fa43a3e47 |
Code-Review: drei Fehler behoben
Session-Cookie ohne secure-Flag (src/web/auth.js)
Das Anmelde-Cookie war httpOnly, sameSite und signiert — aber ohne secure.
Beim ersten Aufruf ueber http, bevor der Proxy auf https umleitet, geht es
damit im Klartext mit. Auf einem fremden WLAN ist das die Sitzung.
Das Flag laesst sich nicht aus request.protocol ableiten: hinter dem
Reverse-Proxy kommt jede Anfrage als http an, trustProxy ist nicht gesetzt.
Stattdessen aus der konfigurierten Adresse des Hosts, mit derselben
Zuordnung, die der OAuth-Redirect schon benutzt. Lokal ueber
http://localhost bleibt es aus, sonst kaeme man beim Entwickeln nicht rein.
Geprueft gegen sechs Hosts: hub und bot ergeben secure, localhost nicht.
Gilt auch fuer das OAuth-State- und das SSO-Ruecksprung-Cookie.
Unbehandelte Rejection beim Start (src/bot/client.js)
registerCommands() faengt nichts ab, der Aufrufer im ClientReady-Handler
auch nicht. Seit Node 15 beendet eine unbehandelte Rejection den Prozess —
ein Rate-Limit von Discord beim Start haette den Bot also abgeschossen und
mit restart: unless-stopped in eine Neustart-Schleife geschickt. Dabei
stehen die zuvor registrierten Befehle bei Discord weiter, und alles
andere koennte laufen. Jetzt wird der Fehler geloggt und der Bot bleibt an.
Number('') ist 0 (src/bot/anti-raid.js)
Beim Aufheben der Raid-Sperre wird die gemerkte Verifizierungsstufe
zurueckgesetzt. Fehlt die Notiz, ergab Number('') die 0 — und 0 ist eine
gueltige Stufe, naemlich "keine". Der Server waere also auf offen gestellt
worden statt so gelassen, wie er war. Jetzt wird nur zurueckgesetzt, was
auch wirklich als Zahl notiert ist.
Ohne Befund geprueft: alle 168 SQL-Abfragen gegen das echte Schema inklusive
Migrationen, alle 109 Routen auf Rechtepruefung (53 von 54 schreibenden haben
eine, die 54. haengt am timing-safe verglichenen Secret), Farbwerte vor dem
Einbetten in ausgeliefertes CSS, das Escaping der OG-Meta-Tags, Timer-Cleanup
im Frontend und die uebrigen async-Handler.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
499761a0e2 |
Test-Harness aus dem Repo nehmen und ignorieren
Die Datei stubbt die API, damit das Panel ohne laufenden Bot rendert. Sie wird beim Entwickeln erzeugt und ist mir beim letzten Commit mit reingerutscht. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
55085d7e2b |
NaN im Dashboard: zwei Zeitstempel-Formate, ein Parser
Bei Devlogs und Backup stand "vor NaN Tagen". Ursache war meine Annahme, die
Datenbank speichere Zeit durchgehend im SQLite-Format. Stimmt nur zur Haelfte:
audit.ts "2026-07-30 12:34:56" datetime('now'), UTC ohne Zone
devlogs.posted_at "2026-07-30T12:34:56.789Z" toISOString()
Einstellung last_backup ebenso
Mein Code haengte immer ein Z an. Beim ersten Format ist das noetig, sonst
liest der Browser die Zeit als Ortszeit. Beim zweiten ergibt es "...ZZ" und
damit ein ungueltiges Datum — daher das NaN. Auf der Team-Seite fiel es nicht
auf, weil dort nur das Protokoll steht, und das hat das erste Format.
Jetzt erkennt alsDatum() die Zone am String und ergaenzt nur, was fehlt.
Unlesbares ergibt null statt NaN, die Anzeige faellt dann auf ihren Ersatztext
zurueck. Die Funktion liegt einmal auf Modulebene und wird von beiden Stellen
benutzt, damit sie nicht wieder auseinanderlaufen.
Geprueft mit acht Faellen direkt gegen die ausgelieferte Funktion, nicht gegen
eine Kopie: beide echten Formate ergeben denselben Zeitpunkt, dazu Offset
statt Z, und null/leer/Unsinn ergeben null. Danach im Browser mit dem Format
gegengeprueft, an dem es wirklich gescheitert ist — der erste Testlauf lief
noch mit dem SQLite-Format und waere gruen gewesen, ohne etwas zu beweisen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
91397d8a44 |
Raid-Sperre von Hand setzen, aufheben oder nachsehen
Der fehlende Befehl zum Raid-Schutz. Drei Unterbefehle: status ist gerade gesperrt, und wie lange noch an jetzt sperren — fuer die Welle, die man kommen sieht aus sofort aufheben — fuer den Fehlalarm um drei Uhr nachts Rechte: ManageGuild, weil die Sperre genau das anfasst — Verifizierung und Invites. Antworten sind ephemeral. Die Sperr-Massnahmen sind in dichtmachen() herausgezogen, damit automatisch und von Hand denselben Weg nehmen und sich nicht auseinanderentwickeln. Die Meldung sagt jeweils dazu, wodurch sie ausgeloest wurde und wer es war. Ist der Raid-Schutz ausgeschaltet, verweist der Befehl aufs Panel statt stillschweigend nichts zu tun. Dazu: der Befehl steht auf der oeffentlichen Befehls-Seite, in beiden Sprachen, und die Zahl auf der Produktseite geht von 16 auf 17. Geprueft: 17 Befehle laden, keine doppelten Namen, Rechte-Bitmaske 32 (ManageGuild), alle neuen Exporte vorhanden, kein Sprachschluessel fehlt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
aef708c405 |
Raid-Schutz: erkennt Beitritts-Wellen und sperrt voruebergehend
Neues Modul anti_raid, standardmaessig AUS. Zaehlt Beitritte in einem Zeitfenster; reisst es, geht der Server dicht: Verifizierung auf Hoch, Einladungen gesperrt, DM an den Owner und Eintrag ins Protokoll. Bewusst zurueckhaltend — es wird niemand gekickt oder gebannt. Bei einem Fehlalarm, etwa nach einem Stream-Shoutout, waere ein Massenkick schlimmer als der Raid. Beide Massnahmen sind reversibel und treffen niemanden, der schon drin ist. Der Sperrzustand liegt in den Einstellungen, nicht nur im Speicher: startet der Bot mitten in einer Sperre neu, waere der Timer sonst weg und der Server bliebe fuer immer dicht. Beim Start wird die Restzeit abgesessen oder, wenn laengst abgelaufen, sofort aufgehoben. Drei Werte im Panel: Schwelle (8), Fenster (20 s), Sperrdauer (15 min). disableInvites() gibt es erst ab discord.js 14.4 und nur mit ManageGuild — beides wird geprueft, statt im Ernstfall einen Fehler zu werfen. Die Fenster-Logik ist als reine Funktion herausgezogen und mit neun Faellen geprueft: langsame Beitritte loesen nicht aus, die Welle schon, der Rand des Zeitfensters zaehlt nicht mehr mit, und nach Ruhe faengt das Fenster von vorn. Offen: ein Befehl zum sofortigen Aufheben. Bis dahin laeuft die Sperre aus oder man setzt die Verifizierung in Discord selbst zurueck. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fb7ee78548 |
Status-Seite wird ein Dashboard
Statt sieben flacher Kacheln jetzt: Kopfzeile mit Bot-Identitaet und Laufzeit, vier Kennzahlen mit Nebenzeile, Aktivitaetsverlauf und Modul-Zustand pro Bereich. Die Bereichs-Zeilen sind klickbar und springen in den Modul-Ueberblick. Fast alles war schon da und musste nur zusammengestellt werden. Neu am Server sind zwei Zeitstempel in archiveStats(): lastDevlog und lastCommit. Reine Zaehler sagen nicht, ob noch etwas passiert — "142 Devlogs, zuletzt vor zwei Tagen" schon. Der Mitglieder-Zuwachs kommt aus joins/leaves der Tagesdaten, der Verlauf fuellt fehlende Tage mit 0 statt sie wegzulassen, sonst staucht er sich. Beim Bauen gefunden: die Spalte in commits heisst committed_at, nicht ts. Da better-sqlite3 beim Vorbereiten sofort auswertet, waere der Bot beim Start abgestuerzt. Beide neuen Abfragen gegen das echte Schema geprueft. Dazu ein Dev-Server-Eintrag in launch.json: der Build ist minifiziert, im Fehlerfall steht dort nur "at bp". Mit dem Dev-Server kommt der Komponenten- und Zeilenverweis — genau das hat den Weg zur Ursache abgekuerzt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1e2d295665 |
Erreichbarkeits-Balken auf der Server-Seite
Ein Strich pro Stunde ueber 7 Tage, gruen/gelb/rot je nachdem, ob in der Stunde alle, ein Teil oder keine Messung erreichbar war. Die Daten lagen laengst da: player_history haelt 7 Tage vor, die API schnitt aber auf 24 Stunden ab. Statt Rohdaten (ueber eine Woche rund 5000 Zeilen pro Server) fasst uptimeBuckets() in SQL stundenweise zusammen — hoechstens 168 Zeilen. - Stunden ohne Messung werden als Luecke gezeichnet, nicht als "online". Sonst saehe eine Bot-Auszeit aus wie ein einwandfrei laufender Server. - Die alte 24h-Uptime-Zahl neben dem Peak ist raus: zweimal dieselbe Groesse ueber verschiedene Zeitraeume verwirrt nur. - Tooltip zeigt Ortszeit. Die Eimer werden in UTC abgeglichen (SQLite rechnet so), angezeigt wird lokal — sonst stuende dort eine Stunde, die niemand wiedererkennt. Zwei Fehler beim Bauen gefunden und behoben: - .srv-card ist ein Grid-Item mit min-width:auto und wuchs durch die 168 Striche auf 724 statt 335 px, die Karte sprengte auf dem Handy das Raster. - "Uhr" stand hart deutsch im Tooltip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c0a56c1eb5 |
Logo auf der Willkommens-Karte und in geteilten Links
Zwei Stellen, an denen die Marke bisher fehlte. Willkommens-Karte: Das Wasserzeichen war der Markenname als Text mit 5 % Deckkraft — jetzt der Katzenkopf. Die Datei wird einmal beim Start gelesen statt bei jedem Beitritt, und fehlt sie, rendert die Karte wie bisher ohne Wasserzeichen. Der Schalter heisst entsprechend "Logo im Hintergrund". Link-Vorschau: og:image war nur gesetzt, wenn ein Devlog einen Screenshot hatte. Startseite, Roadmap, Funktionen und jedes Devlog ohne Bild kamen in Discord als graue Textzeile. Jetzt faellt alles ohne eigenes Bild auf og-default.png zurueck (1200x630, Lockup auf Schwarz mit Akzentstreifen), und twitter:card ist durchgehend summary_large_image. Die Laufzeit-Datei liegt unter src/bot/assets/, nicht in docs/ — das Dockerfile kopiert nur src/ und frontend/dist/. Geprueft: librsvg zeichnet das eingebettete <image> wirklich (42726 Pixel unterscheiden sich gegen eine Fassung ohne), das OG-Bild landet im dist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6a47cb43ab |
Marken-Doku: es ist eine Katze, plus fehlende favicon.png fuer Gitea
Zwei Korrekturen am vorigen Commit: - Ich hatte das Maskottchen durchgehend als Wolfskopf beschrieben. Es ist eine Katze. Betrifft README und die Kommentare in build.py. - Gitea setzt neben favicon.svg noch ein <link rel="alternate icon"> auf favicon.png. Die Datei fehlte, dort stand also weiter die Gitea-Tasse — am Livesystem geprueft, das Bild kam in Gitea-Gruen (96,153,38) zurueck. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
33c48cb898 |
Logo einbinden: Favicons, Apple-Icon und Marken-Quellen
Das Logo lag bisher nirgends im Einsatz — das Favicon war ein handgeschriebenes Prisma in Gold/Orange, das Wasserzeichen der Willkommens-Karte der blosse Markenname als Text. - docs/brand/ mit lockup.png (volles Logo) und mark.png (nur der Wolfskopf) plus build.py, das beide und alle abgeleiteten Icons erzeugt - frontend: favicon.svg ersetzt, apple-touch-icon.png dazu und verlinkt - Zwei Fassungen, weil das Lockup bei 16 px zu einem orangen Balken wird; der Kopfzuschnitt wurde gegen drei Varianten geprueft - SVG mit eingebettetem PNG statt Vektorisierung: das Original hat Verlaeufe und Schattierung, nachgezeichnet wuerde es matschen - Die Icons auf dunklem Grund sind deckend, sonst legt iOS hinter transparente Lesezeichen Schwarz oder Weiss und der Wolf ist selbst fast schwarz Geprueft: beide Dateien werden mit korrektem MIME-Typ ausgeliefert und das SVG rendert wirklich (128x128), die Link-Tags stehen im gebauten HTML. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5360c8229f |
Englisch als zweite Sprache fuer die oeffentlichen Seiten
Hub und Bot-Produktseite lassen sich jetzt auf Englisch umstellen. Die Wahl haengt am Browser (Deutsch nur, wenn der Browser es will) und bleibt im localStorage stehen. - src/i18n.jsx: Context mit t(), tOr(), Datums- und Zahlenformat der Sprache. Keine Bibliothek, die Woerterbuecher werden mitgebaut — kein Nachlade-Blitzer. - Platzhalter im %s-Stil, dieselbe Schreibweise wie in den Bot-Texten. - Umschalter in der Navigation, zusaetzlich im Schubladen-Menue: .auth ist auf schmalen Schirmen ausgeblendet, sonst waere der Wechsel dort unerreichbar. - Datums- und Zahlenformat folgen mit (31.07.2026 / 31/07/2026, 45.231 / 45,231). - Die Funktionsliste kommt weiter deutsch vom Server; englische Modulnamen liegen unter feat.<id> im Woerterbuch. Fehlt einer, bleibt der Servertext stehen — ein neues Modul verschwindet nie von der Seite. Das Config-Panel und die Discord-Texte bleiben deutsch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
570e45fe60 |
Team-Bereich aufgehuebscht: Avatare, Rechte-Chips, Protokoll mit relativer Zeit
Der Team-Reiter war funktional, aber roh: Rechte standen als "content,community"
da, es gab keine Gesichter, und das Protokoll war eine Wand aus Zeitstempeln.
- /api/webadmins und /api/auditlog liefern jetzt Avatar und Anzeigename aus dem
Member-Cache — kostet keinen zusaetzlichen Discord-Aufruf
- Owner-Zeile mit eigenem Chip "Alle Bereiche", damit klar ist, wer alles darf
- Bereiche als lesbare Chips statt roher IDs; die Auswahl beim Anlegen sind
klickbare Karten mit Beschreibung
- Protokoll zeigt relative Zeit ("vor 37 Min"), erst 12 Zeilen, Rest auf Klick
- Toenungen ueber color-mix aus --neon: stimmt im Hub (gold) wie auf der
Bot-Seite (orange)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
c026b2a603 |
Wichtige Funktionen direkt in der Seitenleiste
Nach dem Umbau lagen alle Funktionen hinter dem Raster — für die vier, fünf, die man dauernd anfasst, ist das ein Umweg zu viel. Oben in der Seitenleiste steht jetzt „Meine Funktionen": ein Klick öffnet die Seite direkt. Welche dort stehen, entscheidet der Stern auf der Modul-Karte oder auf der Seite selbst. Bewusst nicht alle 35: Deckel bei acht, sonst ist der Gewinn gegenüber dem Raster wieder weg. Der Standard ist eine Vermutung (Devlogs, Willkommen, Tickets, Starboard, Galerie) und ausdrücklich zum Ändern gedacht. Die Auswahl liegt als Einstellung in der Datenbank, nicht im Browser — sie gilt damit auf jedem Gerät und fürs ganze Team. Eine geleerte Auswahl wird als solche gespeichert, sonst käme beim nächsten Laden der Standard zurück. Der Stern ist ungeheftet nur angedeutet und wird erst beim Überfahren deutlich — 35 Karten mit vollen Sternen sähen aus wie ein Sternenhimmel. Nebenbei: bei geöffneter Modul-Seite war sowohl „Module" als auch die Funktion in der Leiste hervorgehoben. Jetzt nur noch die Funktion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |