From b3758f1f769088dc9104b054b1ffa43a94bfdf8e Mon Sep 17 00:00:00 2001
From: D4rkst3r
Date: Sun, 2 Aug 2026 00:17:01 +0200
Subject: [PATCH] Screenshots zu Ideen, und der Bot legt die Forum-Tags selbst
an
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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
---
README.md | 6 ++-
frontend/src/pages/Settings.jsx | 26 ++++++++---
frontend/src/pages/WunschDetail.jsx | 31 ++++++++++++-
frontend/src/style.css | 6 +++
src/bot/bilder.js | 54 +++++++++++++++++++++++
src/bot/community.js | 30 +++----------
src/bot/devlog-archive.js | 34 +++------------
src/bot/wishes.js | 68 ++++++++++++++++++++++++++---
src/db.js | 21 +++++++--
src/templates.js | 3 +-
src/web/api.js | 22 +++++++++-
src/web/server.js | 10 +++++
12 files changed, 237 insertions(+), 74 deletions(-)
create mode 100644 src/bot/bilder.js
diff --git a/README.md b/README.md
index 6667305..13297ef 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ auf nichts mehr: keine Posts, keine Hintergrund-Prüfungen.
| 📈 **Level-System** | XP pro Nachricht (Cooldown, MEE6-Formel), `/rank`, Level-Up-Announce, Rollen-Belohnungen, öffentliche Bestenliste |
| ⭐ **Starboard** | Nachrichten mit genug ⭐-Reaktionen landen im Best-of-Kanal |
| 📸 **Screenshot-Galerie** | Bilder aus dem Screenshot-Kanal → öffentliche Galerie (lokal gespeichert, weil Discord-CDN-Links ablaufen) |
-| 💡 **Feature-Voting** | `/wunsch-setup` postet den Ideen-Aufruf: Bereich wählen, Formular ausfüllen → Voting-Post mit 👍 und **Thread zum Reden**. Wünsche haben einen **Stand** (wird geprüft · geplant · in Arbeit · umgesetzt · nicht geplant) samt Begründung — auf der Roadmap und im Discord-Post. Wer gestimmt hat, bekommt eine DM, sobald es umgesetzt ist. Die Roadmap filtert nach Stand und Bereich, sortiert nach Top / Bewegung / Neu und zeigt die Zahl der Beiträge im Thread. Ein Klick öffnet die **eigene Seite der Idee** mit der Unterhaltung aus dem Discord-Thread — gespiegelt, geschrieben wird weiter dort. Ist der Voting-Kanal ein **Forum**, wird jeder Wunsch ein Forums-Beitrag mit **Tags** für Bereich und Stand — der Stand zieht beim Ändern automatisch mit. Eine Stimme je Person, egal ob 👍 oder Web-Knopf; Doppler lassen sich zusammenführen |
+| 💡 **Feature-Voting** | `/wunsch-setup` postet den Ideen-Aufruf: Bereich wählen, Formular ausfüllen → Voting-Post mit 👍 und **Thread zum Reden**. Wünsche haben einen **Stand** (wird geprüft · geplant · in Arbeit · umgesetzt · nicht geplant) samt Begründung — auf der Roadmap und im Discord-Post. Wer gestimmt hat, bekommt eine DM, sobald es umgesetzt ist. Die Roadmap filtert nach Stand und Bereich, sortiert nach Top / Bewegung / Neu und zeigt die Zahl der Beiträge im Thread. Ein Klick öffnet die **eigene Seite der Idee** mit der Unterhaltung aus dem Discord-Thread — gespiegelt samt Screenshots, geschrieben wird weiter dort. Fehlende Forum-Tags legt das Panel auf Knopfdruck an. Ist der Voting-Kanal ein **Forum**, wird jeder Wunsch ein Forums-Beitrag mit **Tags** für Bereich und Stand — der Stand zieht beim Ändern automatisch mit. Eine Stimme je Person, egal ob 👍 oder Web-Knopf; Doppler lassen sich zusammenführen |
| 🗳️ **Umfragen** | `/umfrage` erzeugt eine **native Discord-Poll** — keine Reaktions-Bastelei, Discord zählt selbst |
| 🎉 **Giveaways** | `/giveaway` (Admin): Teilnahme-Button, automatische Ziehung; am Gewinner-Post 🔁 Neu auslosen + 👥 Teilnehmerliste |
| 🎂 **Geburtstage** | `/geburtstag` zum Eintragen; morgens Gratulation im Kanal + Tages-Rolle |
@@ -450,7 +450,7 @@ urllib.request.urlopen(req)
| `GET /api/servers` · `/api/status` | — | Game-Server-Status · öffentlicher Betriebsstatus (Namen, nie Adressen) |
| `GET /api/pages` · `/api/features` | — | Veröffentlichte Seiten · Funktionsliste für die Produktseite |
| `GET /feed.xml` | — | RSS |
-| `GET /devlog-assets/*` · `/gallery-assets/*` | — | Lokal gespeicherte Bilder |
+| `GET /devlog-assets/*` · `/gallery-assets/*` · `/wish-assets/*` | — | Lokal gespeicherte Bilder (Discord-Links laufen ab) |
| `GET /brand.css` · `/brand-nav.js` | — | Design-Tokens + Navigation für andere Apps (CORS offen) |
| `GET /api/legal` · `DELETE /api/profile` | — / Member | Impressums-Angaben · DSGVO-Löschung |
| `GET /api/profile` · `/api/myroles*` · `POST /api/wishes*` | Member | Profil · Rollen-Selfservice · Wunsch einreichen/voten |
@@ -511,6 +511,8 @@ d4rkbot/
│ │ ├── server-monitor.js # Game-Server (gamedig, 300+ Spiele)
│ │ ├── watchdog.js # URL-Checks, Störungen, DM-Alarm
│ │ ├── heartbeat.js # Eigene Erreichbarkeit aufzeichnen
+│ │ ├── wishes.js # Ideen: einreichen, Thread, Forum-Tags
+│ │ ├── bilder.js # Bilder aus Discord lokal sichern
│ │ ├── server-setup.js # Kanäle und Rollen anlegen
│ │ ├── welcome-card.js # Willkommens-Bild rendern
│ │ ├── social-notify.js # Twitch + YouTube
diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx
index 23494a3..6dcf9f9 100644
--- a/frontend/src/pages/Settings.jsx
+++ b/frontend/src/pages/Settings.jsx
@@ -1887,12 +1887,14 @@ export default function Settings({ me }) {
Ist der Voting-Kanal ein Forum, wird jeder Wunsch ein
- Forums-Beitrag und bekommt Tags. Leg dort Tags mit denselben Namen an wie hier
- die Bereiche, dazu Wird geprüft, Geplant, In Arbeit,
- Umgesetzt und Nicht geplant — dann kann man auch in Discord
- danach filtern, und der Stand zieht automatisch mit. Fehlt ein Tag, bleibt es
- einfach ohne.
+ Forums-Beitrag und bekommt Tags — dann kann man auch in Discord danach
+ filtern, und der Stand zieht beim Ändern automatisch mit. Der Knopf legt die
+ fehlenden Tags an: die Bereiche von hier plus die fünf Stände. Vorhandene
+ bleiben, wie sie sind.
+
+
+
{bereiche.length === 0 &&
Noch keine Bereiche.
}
@@ -2174,6 +2176,20 @@ export default function Settings({ me }) {
}
}
+ /** Fehlende Tags im Forum anlegen — Bereiche plus die fuenf Staende */
+ async function forumTags() {
+ try {
+ const res = await apiPost('/api/wish-categories/tags');
+ const teile = [];
+ if (res.angelegt.length) teile.push(`${res.angelegt.length} angelegt`);
+ if (res.schonDa.length) teile.push(`${res.schonDa.length} gab es schon`);
+ if (res.keinPlatz.length) teile.push(`${res.keinPlatz.length} passten nicht mehr rein`);
+ flash(`✓ ${teile.join(', ') || 'nichts zu tun'}`);
+ } catch (e) {
+ flash(`✗ ${e.body?.error ?? 'Fehlgeschlagen'}`);
+ }
+ }
+
async function bereichLoeschen(b) {
if (!window.confirm(`Bereich „${b.name}" entfernen? Die Wünsche darin bleiben, stehen dann nur ohne Schild da.`)) return;
try {
diff --git a/frontend/src/pages/WunschDetail.jsx b/frontend/src/pages/WunschDetail.jsx
index c87dac0..6ed04bb 100644
--- a/frontend/src/pages/WunschDetail.jsx
+++ b/frontend/src/pages/WunschDetail.jsx
@@ -8,6 +8,7 @@ import { useEffect, useState } from 'react';
import { useParams, Link } from 'react-router-dom';
import { apiGet, apiPost } from '../api.js';
import { SkeletonCards } from '../components/Skeleton.jsx';
+import Lightbox from '../components/Lightbox.jsx';
import { IconThumbsUp, IconMessage, IconArrowLeft } from '../icons.jsx';
import { useT, useFmt } from '../i18n.jsx';
@@ -25,6 +26,7 @@ export default function WunschDetail() {
const { datum } = useFmt();
const [daten, setDaten] = useState(null);
const [fehler, setFehler] = useState(false);
+ const [lightbox, setLightbox] = useState(null);
useEffect(() => {
setDaten(null);
@@ -130,7 +132,26 @@ export default function WunschDetail() {
})}
-
{k.content}
+ {k.content &&
{k.content}
}
+ {/* Bilder liegen lokal — Discord-Links
+ laufen ab und waeren morgen kaputt */}
+ {k.images?.length > 0 && (
+