Files
abdulive/docs/projects/discord-community.md
T
ZeasyAlexandClaude Opus 5.5 5494c6d24d Discord: eigener Kanal fuer Beitritte, Spielvorschlaege und Reactions als Forum
Beitrittsmeldungen liefen nach #allgemein, weil Discord ohne Angabe den
ersten Kanal nimmt. Jetzt gehen sie nach #begruessung (SYSTEM_CHANNEL).

Die von Abdu angelegten Textkanaele spielvorschlaege und
reaction-einsendungen sind jetzt Foren im Bauplan: ein Beitrag je Spiel
bzw. Clip, 👍 als Standard-Reaktion, Stand-Tags nur fuer Mods. apply.ts
kann dafuer Tags und Standard-Reaktion anlegen und nachziehen; fremde
Tags bleiben mit ihrer ID stehen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:52:02 +02:00

303 lines
14 KiB
Markdown

# Discord-Community
**Wirkrichtung:** Bindung. Twitch ist der Ort, an dem Leute zuschauen. Discord
ist der Ort, an dem sie zwischen den Streams noch da sind. Ohne den zweiten Ort
faengt jeder Stream bei null an.
Der Server ist kein Tool aus der Neunerliste, aber drei davon posten dorthin:
[Go-Live-Broadcaster](go-live-broadcaster.md), [Stream-Recap](stream-recap.md)
und [Clip-to-Shorts](clip-shorts.md). Deshalb steht die Struktur hier im Repo
und nicht nur in einer Oberflaeche.
## Leitgedanke: klein anfangen
Der Kanal hat 5 bis 30 gleichzeitige Zuschauer. Daraus werden realistisch 20
bis 100 Discord-Mitglieder, von denen eine Handvoll schreibt.
Ein Server mit zwanzig Kanaelen sieht bei dieser Groesse nicht nach einer
grossen Community aus, sondern nach einer verlassenen. Zwanzig leere Kanaele
sagen jedem Neuankoemmling: hier ist nichts los. Fuenf Kanaele, in denen etwas
steht, sagen das Gegenteil.
Also: **fuenf sichtbare Kanaele.** Wachstum kommt spaeter, und nur dann, wenn
ein bestehender Kanal wirklich zu voll wird. Wann das so weit ist, steht weiter
unten unter [Wann ein Kanal dazukommt](#wann-ein-kanal-dazukommt).
## Die Struktur
Der Bauplan liegt in [`packages/discord/src/plan.ts`](../../packages/discord/src/plan.ts)
und ist die massgebliche Fassung. Was hier steht, ist die Begruendung dazu.
### Kanaele
| Kategorie | Kanal | Wer schreibt | Wofuer |
|---|---|---|---|
| 📌 Info | `👋│willkommen` | niemand | Die drei Regeln, einmal gepostet |
| 📌 Info | `🙋│begrüßung` | alle | Discords Beitrittsmeldungen |
| 📌 Info | `📢│ankündigungen` | nur Webhooks | Stream-Start und Recap; Diskussion in Threads |
| 🎮 Community | `💬│allgemein` | alle | Der Hauptkanal. Im Zweifel hier |
| 🎮 Community | `🎬│clips` | alle + Clip-Knopf | Gute Momente |
| 🎮 Community | `🎮│spielvorschläge` | alle (Forum) | Ein Beitrag je Spiel, abgestimmt per 👍 |
| 🎮 Community | `👀│reaction-einsendungen` | alle (Forum) | Ein Beitrag je Clip, auf den Abdu reagieren soll |
| 🎮 Community | `🔊 Zusammen zocken` | — | Sprachkanal |
| 🔒 Intern | `🔨│mod-chat` | Mods | Fuer alle anderen unsichtbar |
| 🔒 Intern | `🤖│technik-log` | Toolkit | Probelaeufe, Fehler, AutoMod-Treffer |
Zu den Namen: Discord schreibt Textkanaele klein und macht aus Leerzeichen
Bindestriche. Emoji, Umlaute und das Trennzeichen `│` (U+2502) bleiben stehen -
am echten Server nachgemessen, nicht angenommen. `normalizeName()` bildet das
ab, damit nicht bei jedem Lauf dieselbe Umbenennung erneut rausgeht.
Und zur Schreibweise: im Quelltext bleibt es bei `ae/oe/ue` wie im uebrigen
Repo, aber alles, was ein Mitglied auf Discord liest, steht in richtigem
Deutsch. „Sei anstaendig" liest sich wie ein Tippfehler, und der Text im
Server ist der erste Eindruck, den jemand davon bekommt.
`📢│ankündigungen` ist schreibgeschuetzt, erlaubt aber Reaktionen und Threads.
So bleibt die Kanalansicht eine saubere Liste von Streams, und die Diskussion
ueber das Recap haengt trotzdem direkt daran.
`🙋│begrüßung` gibt es, weil Discord die Beitrittsmeldungen sonst in den
ersten Kanal des Servers schreibt, und das war `💬│allgemein`. Dort gingen die
Meldungen im Gespräch unter und das Gespräch in den Meldungen. Der Kanal ist
beschreibbar, sonst tut der Winke-Knopf unter der Meldung nichts. Welcher
Kanal es ist, steht in `SYSTEM_CHANNEL`.
Die beiden Foren hat Abdu als Textkanäle angelegt, seit 2026-10-17 stehen sie
als Foren im Bauplan. In einem Textkanal schlägt nach drei Wochen jemand
dasselbe Spiel noch einmal vor, weil der erste Vorschlag weggescrollt ist. Im
Forum gibt es einen Beitrag je Spiel, darunter wird per 👍 abgestimmt. Die
Tags für den Stand („Wird gespielt", „Gespielt", „Eher nicht" bzw. „Kommt
dran", „Angeschaut") setzen nur Mods. Sonst markiert jeder sein eigenes Spiel
als „wird gespielt". Discord wandelt einen Textkanal nicht in ein Forum um:
die leeren Textkanäle wurden einmalig von Hand gelöscht, das Skript selbst
löscht weiterhin nichts. Foren gehen auch ohne Community-Server, am echten
Server nachgeprüft.
Der Sprachkanal ist bewusst dabei, obwohl er anfangs leer sein wird. Ein
Sprachkanal, den zwei Leute nutzen, ist der Anfang; einer, den es nicht gibt,
wird nie einer.
### Rollen
Die Reihenfolge ist zugleich die Rangfolge auf dem Server.
| Rolle | Sichtbar | Wofuer |
|---|---|---|
| `Abdu` | ja | Der Kanal selbst. Administrator |
| `Mod` | ja | Loeschen, stummschalten, rauswerfen |
| `Stammgast` | ja | Auszeichnung fuer Leute, die regelmaessig da sind |
| `Live-Ping` | nein | Wird bei Stream-Start angepingt, selbst vergeben |
**`Abdu`** traegt `Administrator`. Das Recht schliesst alle anderen ein und
setzt jede Kanal-Ausnahme ausser Kraft, auch kuenftige. Es gehoert deshalb an
genau ein Konto und an kein zweites. Ein Test im Repo haelt fest, dass keine
andere Rolle es bekommt.
**`Live-Ping` ist die wichtigste der vier.** Ein Rundruf an alle bei jedem
Stream ist der schnellste Weg, dass die Leute den Server stummschalten - und
ein stummgeschalteter Server ist ein toter Server. Angepingt wird nur, wer sich
die Rolle selbst geholt hat. Das sind weniger Leute, aber es sind die, die
kommen.
### Der Knopf fuer Live-Ping
`Live-Ping` nuetzt nur, wenn man sie sich selbst geben kann. Discord bietet
dafuer **Onboarding** an, aber das laesst sich hier nicht einschalten:
```
HTTP 400 Cannot enable onboarding, requirements are not met (350000)
sichtbar fuer @everyone: 5 davon beschreibbar: 3
Discord verlangt: 7 davon beschreibbar: 5
```
Zwei Kanaele anzulegen, damit eine Schwelle passt, macht den Server leerer
statt besser - genau das, wogegen der ganze Entwurf gebaut ist. Also
stattdessen ein **Knopf unter dem Willkommenstext**. Ein Druck holt die Rolle,
der naechste gibt sie wieder ab, und die Antwort sieht nur, wer geklickt hat.
Der Knopf braucht einen laufenden Bot: Discord liefert Klicks entweder ans
Gateway oder an eine oeffentlich erreichbare HTTPS-Adresse, und der Stream-PC
hat keine. Die Gateway-Verbindung haengt deshalb im Ingest-Daemon mit drin
([`gateway.ts`](../../packages/discord/src/gateway.ts)) und meldet sich als
`discordConnected` auf der Statusseite. Zum Ausprobieren einzeln:
```powershell
abdulive discord:bot
```
Die Verbindung fordert **keine** Absichten an (`intents: 0`). Interaktionen
kommen trotzdem an, aber Nachrichteninhalte und Mitgliederlisten sieht der Bot
nicht - was man nicht anfordert, kann man auch nicht versehentlich mitlesen.
Welche Knoepfe es gibt, steht in `SELF_ROLES` in `plan.ts`. Die `custom_id`
steht dauerhaft in der Nachricht und darf sich nie aendern, sonst tun alte
Knoepfe nichts mehr.
**`Stammgast`** hat fast keine zusaetzlichen Rechte, und das ist Absicht. Der
Wert liegt darin, dass der Name farbig ist und in der Mitgliederliste oben
steht. Bei dieser Kanalgroesse ist sichtbare Zugehoerigkeit der wirksamste
Hebel, den es gibt, und er kostet nichts.
**`Mod`** bekommt kein `Server verwalten`, kein `Rollen verwalten` und kein
`Kanaele verwalten`. Moderation heisst aufraeumen, nicht umbauen. Wer umbauen
soll, bekommt das Recht einzeln.
### Was @everyone darf
Gesetzt wird eine vollstaendige Liste statt Discords Vorgabe - die aendert sich
mit der Zeit, und dann steht irgendwann ein Recht drin, das niemand vergeben
hat. Ausdruecklich **nicht** enthalten: `@everyone` erwaehnen, TTS-Nachrichten,
private Threads, saemtliche Verwaltungsrechte.
Ausdruecklich **enthalten**: Einladungen erstellen. Ein Server, in den
Mitglieder niemanden einladen koennen, waechst nur so schnell, wie Abdu selbst
Links verteilt.
### Serverweit
- **Sicherheitsstufe „mittel"** - das Konto muss aelter als fuenf Minuten sein.
Kostet echte Leute nichts und haelt Wegwerf-Konten draussen. Ein oeffentlich
von Twitch aus verlinkter Server bekommt die.
- **Benachrichtigungen nur bei Erwaehnung.** Sonst klingelt jede Nachricht bei
jedem. Siehe oben, stummgeschalteter Server.
- **Zwei AutoMod-Regeln**: Erwaehnungs-Spam (mehr als fuenf auf einmal) und
Discords eigene Spam-Erkennung. Beide blocken die Nachricht und melden sie in
`🤖│technik-log`. Kein Wortfilter - der kostet bei dieser Groesse mehr Aerger
durch Fehlalarme, als er an Spam verhindert.
## Einrichten
### 1. Von Hand: Server und Bot
Beides geht nur ueber die Oberflaeche, ein Skript darf das nicht.
1. **Server anlegen.** In Discord links unten auf `+`, „Eigenen erstellen",
Name und Bild setzen.
2. **Entwicklermodus einschalten** (Einstellungen > Erweitert), dann Rechtsklick
auf den Server > „Server-ID kopieren".
3. **App anlegen** unter <https://discord.com/developers/applications>:
„New Application", dann links „Bot". Dort „Reset Token", Token kopieren.
Er wird genau einmal angezeigt.
4. **Bot einladen.** Client-ID aus „General Information" einsetzen:
```
https://discord.com/oauth2/authorize?client_id=DEINE_CLIENT_ID&permissions=805375024&scope=bot
```
Die Zahl sind genau die sieben Rechte, die das Skript braucht: Kanaele,
Rollen, Webhooks und Server verwalten, lesen, schreiben, Verlauf lesen.
Kein Administrator.
### 2. Der Befehl
```powershell
abdulive discord:setup --server <SERVER-ID> --vorschau
```
Die Vorschau liest den Server aus und zeigt Zeile fuer Zeile, was passieren
wuerde. Sie schreibt nichts. Sieht das gut aus, dasselbe ohne `--vorschau`:
```powershell
abdulive discord:setup --server <SERVER-ID>
```
Der Bot-Token wird beim ersten Lauf abgefragt (die Eingabe wird nicht
angezeigt) und danach zusammen mit der Server-ID verschluesselt abgelegt -
siehe [ADR-0005](../decisions/ADR-0005-zugangsdaten.md). Ab dem zweiten Lauf
genuegt `abdulive discord:setup`.
Die drei Webhook-URLs landet das Skript direkt im selben Speicher, unter
`goLiveWebhook`, `recapWebhook` und `clipsWebhook`. Sie werden nie ausgegeben
und nie geloggt - Broadcaster, Recap und Clipper lesen sie von dort.
### 3. Von Hand: der Rest
Drei Dinge kann die API nicht oder nicht sinnvoll:
1. **Dir selbst `Abdu` geben** und die Bot-Rolle in den Servereinstellungen
ueber `Abdu` ziehen. Eine Rolle kann nur Rollen unterhalb der eigenen
verschieben - auch mit Administrator. Discord haengt die Bot-Rolle beim
Einladen ganz unten ein, dort ist also kein Platz fuer vier Rollen darunter.
Bis das erledigt ist, meldet das Skript die Rangfolge als uebersprungen und
nennt den Grund.
2. **Einladungslink** erzeugen, ohne Ablaufdatum, und ins Twitch-Panel, in die
Kanalinfo und in einen Chat-Befehl eintragen. Ein Server, den im Stream
niemand erwaehnt, bleibt leer.
3. **Twitch-Konto verbinden** (Servereinstellungen > Integrationen). Danach
bekommen Subs automatisch eine Rolle.
Onboarding steht bewusst nicht in der Liste - warum, steht oben unter
[Der Knopf fuer Live-Ping](#der-knopf-fuer-live-ping).
## Was das Skript nicht tut
**Es loescht nichts.** Keine Kanaele, keine Rollen, keine Nachrichten. Ein
Einrichtungsskript, das aufraeumt, raeumt irgendwann das Falsche weg. Was von
Hand dazugekommen ist, bleibt stehen - auch eigene Rechte-Ausnahmen in einem
Kanal, den das Skript anfasst.
**Es ueberschreibt den Willkommenstext nicht.** Gepostet wird nur, wenn
`#willkommen` leer ist. Danach gehoert der Text der Hand, die ihn zuletzt
angefasst hat.
**Es laesst von Discord verwaltete Rollen in Ruhe.** Rollen, die zu einer App
oder zur Twitch-Anbindung gehoeren, meldet es als uebersprungen.
Wiederholtes Ausfuehren ist der Normalfall: gefunden wird ueber den Namen,
danach wird angeglichen statt neu angelegt. Der zweite Lauf aendert nichts.
## Wann ein Kanal dazukommt
Nicht auf Verdacht. Jeweils erst, wenn die Bedingung wirklich eintritt:
| Neuer Kanal | Erst wenn |
|---|---|
| `#vorschlaege` | In `#allgemein` gehen Vorschlaege zwischen anderem unter |
| `#fotos` oder `#offtopic` | `#allgemein` besteht ueberwiegend aus einem Thema, das nicht der Stream ist |
| Spiel-eigener Kanal | Zu einem Spiel gibt es ueber Wochen taeglich Gespraech |
| Zweiter Sprachkanal | Der erste ist regelmaessig belegt, wenn jemand dazu will |
| Forum statt `#clips` | Mehr als etwa zehn Clips pro Woche |
Umgekehrt gilt genauso: ein Kanal, in dem vier Wochen nichts steht, kommt weg.
## Fallstricke
- **Die Bot-Rolle steht anfangs unten.** Discord haengt sie beim Einladen
unterhalb bestehender Rollen ein. Dann schlaegt das Setzen der Rangfolge
fehl. Das Skript bricht deswegen nicht ab, sondern meldet es - nachziehen und
noch einmal laufen lassen.
- **Von Hand angelegte Webhooks geben ihren Token nicht heraus.** Wer einen
Webhook in der Oberflaeche neu anlegt, bekommt eine URL, die das Skript nicht
auslesen kann. Es meldet ihn als uebersprungen; loeschen und noch einmal
laufen lassen loest es.
- **Kanalnamen lassen sich nur zweimal in zehn Minuten aendern.** Discord
verlangt danach bis zu neun Minuten Pause. Der Lauf wartet hoechstens eine
Minute, meldet den Kanal als uebersprungen und macht weiter - spaeter noch
einmal ausfuehren holt es nach.
- **Umbenennen statt verdoppeln.** Aendert sich ein Name im Bauplan, gehoert
der alte in `aliases`. Sonst findet das Skript den Kanal nicht wieder und
legt einen zweiten daneben an. Ein Test besteht darauf, dass jeder Eintrag
einen frueheren Namen hinterlegt hat.
- **Ankuendigungskanaele im Discord-Sinn** (die, denen andere Server folgen
koennen) brauchen einen Community-Server, und der braucht Regelkanal,
Update-Kanal und einige Einstellungen. Fuer diese Groesse bringt das nichts,
deshalb ist `#ankuendigungen` ein normaler Textkanal mit Schreibschutz.
- **Ein Ping ist keine Reichweite.** Wer `Live-Ping` nicht anbietet und
stattdessen `@everyone` benutzt, hat beim dritten Mal einen stummen Server.
## Abnahmekriterien
- `abdulive discord:setup --vorschau` laeuft auf einem frischen Server durch und
meldet keinen uebersprungenen Punkt.
- Nach dem echten Lauf existieren drei Kategorien, sieben Kanaele, vier Rollen
und drei Webhooks; `👋│willkommen` enthaelt genau eine Nachricht.
- Ein zweiter Lauf meldet ausschliesslich „steht" und legt nichts doppelt an.
- Ein Konto ohne Rolle sieht `Intern` nicht und kann in `#ankuendigungen` nicht
schreiben, aber einen Thread eroeffnen.
- Die Webhook-URLs stehen im verschluesselten Speicher und tauchen in keiner
Ausgabe und keinem Log auf.
- Der Knopf unter dem Willkommenstext vergibt `Live-Ping` und nimmt sie beim
zweiten Druck wieder weg; die Antwort sieht nur, wer geklickt hat.