Raid-Scout, Auswertung, VOD-Kapitel, Community-Gedaechtnis, Punkteshop und Recap samt Platzhaltern, Spezifikationen und Konfiguration. Der Hub schickt am Chat zusaetzlich die Sendezeit mit, fuer die Uhrzeit im Pult. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
623 lines
32 KiB
Markdown
623 lines
32 KiB
Markdown
# Übergabe
|
||
|
||
Stand **2026-10-27**, letztes gebautes Paket 0.5.7. Wer hier weitermacht,
|
||
liest zuerst diese Seite, dann [`07-mvp-plan.md`](07-mvp-plan.md). Für Abdu
|
||
selbst gibt es das [Handbuch](handbuch/handbuch.pdf).
|
||
|
||
## Wo das Projekt steht
|
||
|
||
Vier von sieben Scheiben des MVP „Clip-Knopf" sind gebaut. Die Kette vom
|
||
Auslöser bis zum fertigen Short steht im Code vollständig. Geprüft ist sie
|
||
überall dort, wo es ohne Abdus Stream-PC ging.
|
||
|
||
| Scheibe | Stand | Nachweis |
|
||
|---|---|---|
|
||
| S0 Toolchain + Auslieferung | fertig | Paket gebaut und installiert, `check-env` ohne harte Fehler, Updater einmal komplett durchgelaufen |
|
||
| S1 Einrichtung, Auth, DB, Chat | fertig | Daemon läuft, Chat verbunden, Secrets verschlüsselt |
|
||
| S2 OBS + `!clip` | Code fertig, **nicht abgenommen** | braucht laufendes OBS |
|
||
| S3 9:16-Render | fertig | Durchlauf mit künstlicher Aufnahme |
|
||
| S4 Untertitel | fertig | Durchlauf mit echtem deutschen Clip, Ränder nachgemessen |
|
||
| S5 Onboarding + Sichtung | offen | |
|
||
| S6 Erstchatter-Begrüßung | offen | |
|
||
|
||
322 Tests, alle grün. Was noch fehlt, steht vollständig in
|
||
[`offene-punkte.md`](offene-punkte.md).
|
||
|
||
## Neu: der Dienst läuft zentral
|
||
|
||
Seit 2026-09-17 läuft ein Teil des Toolkits nicht mehr auf Abdus Rechner,
|
||
sondern in einem LXC auf dem Proxmox-Host. Der Schnitt und seine Begründung
|
||
stehen in [ADR-0008](decisions/ADR-0008-geteilter-betrieb.md); die Kurzfassung
|
||
ist: alles, was nur Internet braucht, gehört dorthin, wo durchgehend Strom
|
||
ist.
|
||
|
||
| | Hub (LXC 141, `192.168.2.15`) | Ingest (Abdus PC) |
|
||
|---|---|---|
|
||
| Befehl | `abdulive hub` | `abdulive ingest` |
|
||
| Chat, Zuschauerzahlen | ja | ja |
|
||
| Discord-Knopf, Go-Live-Ping | **ja** | nein |
|
||
| Overlay für OBS | **ja** | nein |
|
||
| OBS, `!clip`, Rendern | nein | **ja** |
|
||
|
||
Erreichbar unter `https://abdu.dimension47.de`, hinter dem vorhandenen Nginx
|
||
Proxy Manager (Proxy-Eintrag 42, eigenes Let's-Encrypt-Zertifikat, Pufferung
|
||
abgeschaltet - ohne das sammelt nginx die Overlay-Ereignisse und liefert sie
|
||
gebündelt aus). Ausgerollt wird mit `scripts/deploy-hub.sh`, gestartet von
|
||
systemd (`abdulive-hub`). Der Container hat keinen Zugang zum Repo - das
|
||
Skript schiebt den Arbeitsstand als Archiv hinüber.
|
||
|
||
Der Discord-Teil der Zugangsdaten ist übertragen, der Bot hängt am Hub und
|
||
meldet beim Start "Rollen-Knöpfe sind scharf". **Abdu hat am 2026-09-23 sein
|
||
Konto verbunden** (`abdulive_`, alle 9 Scopes). Damit laufen Helix mit
|
||
Nutzer-Token und EventSub. Der Chat liest weiter anonym mit, weil das
|
||
Bot-Konto noch nicht verbunden ist.
|
||
|
||
**Das kostet ADR-0005 seine Hauptbegründung.** "Bindet nur auf 127.0.0.1" gilt
|
||
für den Hub nicht mehr. An seine Stelle treten ein Passwort für alles, was ein
|
||
Mensch bedient, und ein Schlüssel in der Adresse für die Browser-Sources.
|
||
|
||
## Der Go-Live-Ping geht raus
|
||
|
||
War in der letzten Übergabe noch Vorarbeit, ist jetzt gebaut
|
||
(`apps/broadcaster`). Ablauf: neuer Stream erkannt, eine Minute warten, Titel
|
||
und Kategorie frisch holen, Webhook posten, Rolle `Live-Ping` anpingen.
|
||
|
||
Der Ping funktioniert nur mit `allowed_mentions` im Nachrichtenkörper. `<@&ID>`
|
||
allein färbt die Erwähnung ein und benachrichtigt niemanden - das ist der
|
||
Fehler, den man erst nach drei leeren Streams bemerkt, und dagegen steht ein
|
||
eigener Test.
|
||
|
||
Gegen den doppelten Ping stehen drei Dinge, denn wer zweimal in zehn Minuten
|
||
angepingt wird, nimmt sich die Rolle wieder ab: die Tabelle `posts` mit einem
|
||
eindeutigen Index je Stream, eine Abklingzeit von sechs Stunden, und der
|
||
Umstand, dass der Fortsetzungszweig im Polling gar nicht erst auslöst.
|
||
|
||
Der Hub steht auf `mode: draft` - der Post geht vollständig, aber in
|
||
`#technik-log` und ohne Ping. Ein Probelauf ist dort gelandet, der Weg zum
|
||
Webhook ist damit nachgewiesen. Zum Scharfschalten: `mode: live` in
|
||
`/var/lib/abdulive/config/config.yaml`.
|
||
|
||
**Ausgelöst hat er noch nie.** Bis zum 2026-09-23 fehlte das
|
||
Broadcaster-Token; das ist jetzt da. Der nächste Stream ist der erste echte
|
||
Durchlauf, im Entwurfsmodus.
|
||
|
||
## Das Overlay steht
|
||
|
||
Browser-Sources unter `apps/overlay-web/public`, ausgeliefert vom
|
||
Overlay-Server: Einblendungen, Chat, Kamerarahmen, Statusleiste, Effekte,
|
||
Pausenbild, Vorlesen, Liegestütze. Kein Build, kein Framework - OBS lädt die
|
||
Dateien direkt.
|
||
|
||
**Nächste Sitzung: das Overlay wird neu gemacht.** Gestaltung von Grund auf,
|
||
nicht nur der Kamerarahmen. Was bleiben sollte, weil es Fehler verhindert, die
|
||
man erst im Stream bemerkt: `client.js` (Neuverbinden, Wachhund,
|
||
Schnappschuss, `Overlay.esc`), der durchsichtige Hintergrund und die Kontur
|
||
aus `theme.css`, `?probe=1` zum Ansehen im Browser. Die Ereignisse auf dem Bus
|
||
(`apps/overlay-server/src/bus.ts`) sind die Schnittstelle, an der sich die
|
||
neuen Seiten festhalten können.
|
||
|
||
**Chat-Abzeichen sind seit 2026-10-27 Twitchs echte Bilder** statt farbiger
|
||
Punkte. IRC liefert nur `moderator/1`, `subscriber/3012` usw.; der Hub löst das
|
||
über Helix (global und Kanal, Kanal gewinnt) zu Bildadressen auf und hängt sie
|
||
an jede Chatzeile (`packages/twitch/src/badges.ts`, Feld `badges` im
|
||
`ChatEvent`). Geladen beim Start und alle sechs Stunden, beim Ausrollen
|
||
kamen 552. Unbekanntes fällt weg, die Seite nimmt nur Adressen von
|
||
`static-cdn.jtvnw.net`. Im echten Stream noch nicht angesehen; zeigt OBS
|
||
weiter Punkte, die Browser-Source einmal aktualisieren.
|
||
|
||
Die Ereignisse kommen über Server-Sent Events statt WebSocket. Warum, und was
|
||
gegen die tote Browser-Source nach einem Neustart getan ist, steht in
|
||
[`apps/overlay-server/README.md`](../apps/overlay-server/README.md).
|
||
|
||
Abos, Raids und Bits kommen über IRC-USERNOTICE herein, das wird jetzt
|
||
ausgewertet. **Follows fehlen** - die gibt es nur über EventSub. EventSub
|
||
läuft inzwischen, abonniert ist aber nur `channel_points_custom_reward_redemption.add`.
|
||
Follows wären ein zweites Abonnement (`channel.follow`, Version 2, braucht
|
||
`moderator:read:followers`, der Scope ist schon da).
|
||
|
||
**Kamerarahmen, Entwurf** (`/kamera`, 520×380 auf 0,0): ein Rahmen rechts und
|
||
unten um die Facecam, darunter eine Platte mit Kanal, Kategorie, Zuschauern
|
||
und Laufzeit. Soll die Statusleiste ersetzen, die als unschön empfunden wird.
|
||
Über der Kamera liegt bewusst nichts, weil genau dieser Bereich für die
|
||
Shorts ausgeschnitten wird. Noch nicht ausgerollt, Rückmeldung zur Richtung
|
||
steht aus. Ansehen: `node apps/cli/main.ts overlay --demo`, dann
|
||
`http://127.0.0.1:4791/kamera?probe=1`.
|
||
|
||
Zum Ansehen ohne Stream: auf der Übersichtsseite gibt es unter
|
||
**Ausprobieren** für jede Einblendung, jede Chat-Variante und jeden Effekt
|
||
einen Knopf. Die Ereignisse gehen nur an die Browser-Sources. Dasselbe im
|
||
Dauerbetrieb: `abdulive overlay --demo` oder der Knopf „Vorführung starten".
|
||
|
||
## Das Pult: Handy als Stream Deck
|
||
|
||
Seit 2026-10-26 auf dem Hub ausgerollt und im Paket 0.5.7. **Seit 2026-10-27
|
||
hängt Abdus Stream-PC dran:** obs-websocket an, Passwort auf der
|
||
Einrichtungsseite hinterlegt, der PC (`DESKTOP-DGJ4HC6`) hat sich beim Hub
|
||
gemeldet und ist freigegeben, im Hub-Log steht `Stream-PC verbunden`. Ob
|
||
Szenenwechsel, Stummschalter, Sounds und das Anlegen der Quelle in OBS
|
||
greifen, ist damit noch nicht beobachtet. Auf Abdus Rechner ist nichts auszuführen: das Update kommt über
|
||
die tägliche Aufgabe, danach meldet sich der PC von selbst, Abdu tippt im Pult
|
||
einmal „Freigeben“, und die Sound-Quelle legt `ingest` selbst in OBS an. `/pult` auf dem Hub ist eine installierbare Web-App (PWA) mit
|
||
Soundboard, Szenenwechsel, Clip-Knopf, Stummschaltern, Liegestützen,
|
||
Vorlese-Freigabe und den Testknöpfen. Beschreibung in
|
||
[`apps/overlay-web/README.md`](../apps/overlay-web/README.md#das-pult-pult).
|
||
|
||
**Seit 2026-10-27 mit Chat, nur lesend.** Eigener Reiter am Handy; ab
|
||
Tablet-Größe (760×500) steht der Chat fest rechts neben dem gewählten
|
||
Reiter, also etwa Szenen und Chat auf einem Bildschirm. Reiner Client-Code in
|
||
`pult.js`: der Chat kam schon immer über den Ereignisstrom des Pults, es
|
||
braucht weder neue Routen noch Rechte. Neu am Bus ist nur `ts` an der
|
||
Chatzeile, für die Uhrzeit. Geprüft im Browser mit `overlay --demo` in vier
|
||
Größen (Handy hoch und quer, iPad hoch und quer); auf einem echten Tablet
|
||
und im Stream noch nicht.
|
||
|
||
- **Sounds lädt Abdu selbst hoch**, am Handy über „+ Sound“. Sie liegen auf
|
||
dem Hub unter `<db>/sounds/`, gespielt wird über die Browser-Source
|
||
`abdulive Sounds`, die `ingest` selbst in OBS anlegt.
|
||
- **Szenen** laufen über die Leitung Stream-PC → Hub. Der PC meldet sich
|
||
beim ersten Start von selbst, die Freigabe ist ein Tipp im Pult. In OBS
|
||
muss nur obs-websocket an sein.
|
||
- **Restrisiko der Freigabe:** Anfragen kann jeder, der die Adresse kennt.
|
||
Freigeben kann nur, wer im Pult angemeldet ist, und das Pult zeigt Name und
|
||
Adresse des Rechners. Wer etwas freigibt, das er nicht erwartet, gibt einem
|
||
Fremden Szenen, Stummschalter und Clip-Knopf - nicht mehr.
|
||
- Geprüft: 319 Tests, Typprüfung, Durchläufe im Browser gegen einen
|
||
nachgebauten Stream-PC mit falschem OBS (Freigabe, Einrichten der
|
||
Sound-Quelle, Szenenwechsel, Stummschalten, Hochladen, Abspielen); 8 MB
|
||
Upload durch den Proxy. Ungeprüft: echtes OBS (vor allem das Anlegen der
|
||
Quelle), iPhone/Android-Installation.
|
||
- **Die Adresse in der Anfragekarte** stimmt: bei Abdus echter Anfrage stand
|
||
seine öffentliche Adresse da, nicht die Docker-Brücke (`172.18.0.1` kam nur
|
||
bei der Probe aus dem eigenen Netz). Sie ist reine Anzeige - die Freigabe
|
||
hängt am Schlüssel, eine wechselnde IP stört nicht, eine feste braucht er
|
||
nicht.
|
||
- Auf dem Hub stand am 2026-10-26 kurz eine Probe-Anfrage „PRUEFUNG-CLAUDE“.
|
||
Sie ist nach zehn Minuten von selbst verfallen und wurde nicht freigegeben.
|
||
|
||
## Kanalpunkte: „Nachricht vorlesen" läuft
|
||
|
||
Seit 2026-09-23 scharf. Der Weg: EventSub meldet die Einlösung, der Hub
|
||
prüft (`apps/hub/src/rewards.ts`), Piper spricht, `/tts` spielt ab.
|
||
|
||
| | |
|
||
|---|---|
|
||
| Belohnung | „Nachricht vorlesen", 1000 Punkte, Texteingabe Pflicht |
|
||
| Freigabe | keine, wird direkt vorgelesen |
|
||
| Schutz | Wortfilter (29 Einträge), 200 Zeichen, 3 je Person und Stunde, „Sofort still" auf der Übersichtsseite |
|
||
| Stimme | Piper, `de_DE-thorsten-medium` (mls klang verwaschen) |
|
||
|
||
**Die Stimme entsteht im Hub, nicht im Browser.** Zuerst lief es über
|
||
`speechSynthesis` in der Browser-Quelle. Das hat in OBS gesprochen, aber
|
||
amerikanisch: OBS' Browser sah keine deutsche Stimme und die Seite nahm die
|
||
erste beliebige. Piper liegt unter `/opt/piper` im LXC (2 Kerne: 200 Zeichen
|
||
in 0,7 s), das Stimmenmodell unter `models/piper/`. Beides überlebt jedes
|
||
Ausrollen; eingerichtet mit `scripts/install-piper.sh`.
|
||
|
||
**Die erste Stimme war `de_DE-mls-medium`, Sprecher 0**, und klang im Stream
|
||
„wie jemand mit Schlaganfall". Die Phoneme stimmten (`piper --debug`), es lag
|
||
am Modell: Hörbuch-Aufnahmen von 236 Laien, Sprecher 0 ist ein beliebiger.
|
||
Seit 2026-09-24 läuft `de_DE-thorsten-medium`, ein Sprecher, darum kein
|
||
`sprecher` mehr in der Konfiguration. Das mls-Modell liegt noch daneben.
|
||
|
||
**Die Belohnung muss über die API angelegt sein** (`abdulive rewards:anlegen`).
|
||
Nur die App, die eine Belohnung angelegt hat, darf deren Einlösungen
|
||
abschließen oder erstatten. Im Dashboard sieht man davon nichts. Titel,
|
||
Kosten und Abklingzeit darf man dort ändern; löschen und neu anlegen nicht,
|
||
dann ist die ID weg, die in der Hub-Konfiguration steht.
|
||
|
||
**Der Wortfilter normalisiert vorher**: Leetspeak, Punkte zwischen den
|
||
Buchstaben, gezogene Buchstaben, Akzente. Einträge ab 5 Buchstaben werden im
|
||
zusammengezogenen Text gesucht, kürzere nur am Wortanfang, sonst stünde
|
||
„nazi" in „kann azubi". Die Liste steht nur in der Hub-Konfiguration, nicht
|
||
im Repo.
|
||
|
||
**Der Filter hat eine Stunde lang fast alles abgelehnt.** `www` in der Liste
|
||
wurde zusammengezogen zu `w` und traf damit jedes Wort, das mit w anfängt
|
||
(„wie", „weg"). Die Punkte gingen zurück, im Log stand nur `Wortfilter`.
|
||
Behoben am 2026-09-24: was nach dem Zusammenziehen unter 3 Buchstaben hat,
|
||
trifft nur noch als ganzes Wort. **Noch offen:** lange Einträge treffen über
|
||
Wortgrenzen hinweg, „Spaß tut gut" fliegt wegen `spast` raus, „eine Gerade"
|
||
wegen `neger`. Wer eine Ablehnung nachvollziehen will: der Text steht im
|
||
Ereignis `points.redeemed` in der Datenbank.
|
||
|
||
**Der YAML-Leser konnte keine Listen aus Maps.** `- titel: X` zerfiel in
|
||
Zeichenketten, jede Belohnung war ohne Meldung unwirksam - auch das Beispiel
|
||
in `config.example.yaml`. Behoben und getestet; die bestehenden Dateien
|
||
werden nachweislich gleich gelesen wie vorher.
|
||
|
||
## Kanalpunkte: Liegestütze
|
||
|
||
Seit 2026-10-04 scharf. Die Belohnung „5 Liegestütze" gab es auf Twitch schon
|
||
(von Hand angelegt, 61 Einlösungen bis dahin). Sie steht jetzt in der
|
||
Hub-Konfiguration mit `aktion: liegestuetze`, `anzahl: 5`, zugeordnet über den
|
||
Titel. Wird sie auf Twitch umbenannt, zählt der Hub nicht mehr mit.
|
||
|
||
| | |
|
||
|---|---|
|
||
| Zähler | `apps/hub/src/liegestuetze.ts`, Tabelle `zaehler` (Migration 003), übersteht Neustarts |
|
||
| Offen | Einlösung +5. Auf der Übersicht −1/−5/−10, Setzen, auf 0 |
|
||
| Gemacht | Was mit −1/−5/−10 abgezogen wird. Setzen zählt nicht, das ist Korrektur. Eigenes Feld zum Korrigieren |
|
||
| Neuer Stream | Gemacht auf 0, offen bleibt. Nicht beim Neustart des Hubs mitten im Stream, nicht bei einer Lücke bis 5 Minuten |
|
||
| Overlay | `/liegestuetze`, 420×200. `?verstecken=1`, `?gemacht=0`, `?titel=...` |
|
||
|
||
**Ungeprüft im Stream.** Getestet mit Probeserver und Browser. Weder eine
|
||
echte Einlösung noch das Nullen beim Streamstart ist seit dem Ausrollen
|
||
passiert. Der Zähler startete bei 0 offen, die 61 alten Einlösungen sind nicht
|
||
übernommen. In OBS ist die Quelle noch nicht eingetragen.
|
||
|
||
**Die Belohnung ist nicht über die API angelegt.** Der Hub kann ihre
|
||
Einlösungen deshalb nicht als erledigt melden und schreibt bei jeder eine
|
||
Warnung ins Log. Für das Zählen egal. Eine ältere Belohnung „10 Liegestütze"
|
||
(12 Einlösungen bis 2026-09-24) steht nicht in der Konfiguration.
|
||
|
||
**Nebenbei behoben: die Vorlese-Freigabe auf der Übersicht war tot.**
|
||
`POST /tts` landete bei der Overlay-Seite `/tts` statt bei der Freigabe, auf
|
||
dem Hub ohne Schlüssel sogar mit 403. „Vorlesen", „Ablehnen" und „Sofort
|
||
still" haben damit nie gewirkt. Overlay-Seiten werden jetzt nur bei GET
|
||
ausgeliefert. Gegen eine echte Warteschlange nicht nachgeprüft.
|
||
|
||
## Der Discord-Server steht
|
||
|
||
Dazugekommen seit der letzten Übergabe, und kein Teil des MVP: der
|
||
Community-Server ist eingerichtet, und zwar als Code statt von Hand. Der
|
||
Bauplan liegt in [`packages/discord/src/plan.ts`](../packages/discord/src/plan.ts),
|
||
aufgetragen wird er mit `abdulive discord:setup`. Wiederholbar: gefunden wird
|
||
über Namen und frühere Namen, danach wird angeglichen. Der zweite Lauf ändert
|
||
nichts, gelöscht wird nie.
|
||
|
||
Das ist Vorarbeit für drei Werkzeuge. Die Webhooks für
|
||
[Go-Live](projects/go-live-broadcaster.md), Recap (inzwischen gestrichen)
|
||
und [Clips](projects/clip-shorts.md) liegen fertig im verschlüsselten Speicher
|
||
unter `goLiveWebhook`, `recapWebhook` und `clipsWebhook`. Wer diese Werkzeuge
|
||
baut, muss sich um Discord nicht mehr kümmern, sondern nur noch posten.
|
||
|
||
Zehn Kanäle, vier Rollen, zwei AutoMod-Regeln. Seit 2026-10-17 dabei:
|
||
`🙋│begrüßung` für Discords Beitrittsmeldungen (liefen vorher in
|
||
`#allgemein`), und die Foren `🎮│spielvorschläge` und
|
||
`👀│reaction-einsendungen`. Beide hatte Abdu als Textkanäle angelegt, sie sind
|
||
jetzt Foren mit Tags, die nur Mods setzen, und 👍 als Standard-Reaktion.
|
||
Discord wandelt keinen Textkanal in ein Forum um: die beiden leeren
|
||
Textkanäle wurden einmalig über die API gelöscht, das Setup-Skript selbst
|
||
löscht weiterhin nichts.
|
||
|
||
Im Spielvorschläge-Forum stehen neun Beiträge, einer je Spiel aus Abdus
|
||
Liste, vom Bot angelegt (Titel vorher nachgeschlagen, „involkyr" heißt
|
||
Invokyr). In `📢│ankündigungen` hat der Bot das Forum angekündigt, ohne Ping.
|
||
Der Bot darf dort trotz Schreibsperre für `@everyone` posten.
|
||
|
||
**Der Hub ist nicht neu ausgerollt.** Das Setup lief aus einer Kopie im
|
||
Container, der laufende Prozess hat noch den Stand davor. Für den Server ist
|
||
das egal, für den Knopf auch. Beim nächsten Ausrollen kommt es mit. Der Entwurf und seine
|
||
Begründung stehen in [`projects/discord-community.md`](projects/discord-community.md);
|
||
die Kurzfassung ist, dass bei 5 bis 30 Zuschauern fünf sichtbare Kanäle besser
|
||
sind als zwanzig leere.
|
||
|
||
Ein Teil davon läuft dauerhaft: der Knopf unter dem Willkommenstext, mit dem
|
||
man sich die Rolle `Live-Ping` selbst gibt. Er hängt als Gateway-Verbindung mit
|
||
im Prozess und meldet sich als `discordConnected` auf der Statusseite. Seit dem
|
||
Umzug in den Hub läuft er durch, auch wenn Abdus Rechner aus ist.
|
||
|
||
Dazugekommen ist ein vierter Webhook: `technikWebhook` in `#technik-log`. Dort
|
||
landet der Go-Live-Post im Entwurfsmodus - dieselbe Nachricht wie im Betrieb,
|
||
nur ohne Ping und nur für Mods sichtbar. Wer den Server vor dieser Fassung
|
||
eingerichtet hat, muss `abdulive discord:setup` einmal erneut laufen lassen.
|
||
|
||
## Was jemand wissen muss, bevor er etwas anfasst
|
||
|
||
**Keine Abhängigkeiten, kein Build.** Node 24 bringt SQLite, WebSocket,
|
||
`fetch` und die Ausführung von TypeScript mit. `node apps/cli/main.ts` läuft
|
||
direkt. Deshalb gilt: nur *löschbare* TypeScript-Syntax, also keine `enum`,
|
||
keine Parameter-Properties im Konstruktor, keine `namespace`.
|
||
`erasableSyntaxOnly` in der `tsconfig.json` erzwingt das und hat schon einen
|
||
echten Fehler gefangen.
|
||
|
||
**Alles läuft in einem Prozess** - aber es gibt jetzt zwei davon, Hub und
|
||
Ingest (siehe oben). Schwere Arbeit (ffmpeg, whisper) läuft als Kindprozess und
|
||
darf abstürzen. Der Chat-Prozess darf nie sterben. Was er nicht mitschreibt,
|
||
ist für immer weg.
|
||
|
||
**Zugangsdaten kommen aus der Weboberfläche**, nicht aus einer Datei. Sie
|
||
liegen verschlüsselt neben der Datenbank, aber bewusst nicht *in* ihr. Die
|
||
Datenbank wird gesichert und weitergereicht, die Geheimnisse nicht.
|
||
|
||
**Das Repo ist seit 2026-09-09 öffentlich.** Damit funktioniert der Einzeiler
|
||
zur Installation, und der Updater braucht keinen Token. Es heißt aber auch:
|
||
nichts Vertrauliches mehr hineinschreiben, auch nicht in die Doku.
|
||
|
||
**Es gibt zwei Remotes**, beide mit demselben Inhalt: `origin`
|
||
(`ZeasyAlex/abdulive`) und `discord` (`ZeasyAlex/abdulive_-discord`). Wer
|
||
pusht, pusht auf beide, sonst laufen sie auseinander.
|
||
|
||
**`deploy-hub.sh` schiebt den Arbeitsstand, nicht den Commit.** Liegt
|
||
Unfertiges herum, landet es im LXC. Sauber ausrollen aus einem eigenen
|
||
Checkout:
|
||
|
||
```bash
|
||
git worktree add --detach ../abdulive-deploy HEAD
|
||
../abdulive-deploy/scripts/deploy-hub.sh
|
||
git worktree remove ../abdulive-deploy
|
||
```
|
||
|
||
Die Begründungen stehen in [`decisions/`](decisions/), acht ADRs. Wer eine
|
||
davon umwerfen will, liest sie vorher. ADR-0004 (Docker) ist durch ADR-0007
|
||
ersetzt; die Analyse darin bleibt lesenswert, weil zwei der vier
|
||
beschriebenen Fallen mit dem Container weggefallen sind statt gelöst worden
|
||
zu sein.
|
||
|
||
## Loslegen
|
||
|
||
Auf Abdus Rechner, eine native Windows-Anwendung:
|
||
|
||
```powershell
|
||
irm https://git.zeasy.dev/ZeasyAlex/abdulive/raw/branch/main/scripts/install.ps1 | iex
|
||
```
|
||
|
||
Der Installer lädt das Paket, prüft die Prüfsumme, holt das Sprachmodell,
|
||
läuft den Selbsttest, startet den Dienst und öffnet die Einrichtungsseite.
|
||
Ohne Administratorrechte. Ausführlich in [`05-setup.md`](05-setup.md).
|
||
|
||
Zum Entwickeln reicht ein Checkout und Node 24 oder neuer:
|
||
|
||
```powershell
|
||
node apps/cli/main.ts check-env
|
||
node --test "packages/**/*.test.ts" "apps/**/*.test.ts"
|
||
node apps/cli/main.ts overlay --demo # Overlay ohne Stream ansehen
|
||
node scripts/build-package.ts --version 0.5.5 # baut dist/
|
||
```
|
||
|
||
Den Hub ausrollen (braucht SSH auf den Proxmox-Host):
|
||
|
||
```bash
|
||
scripts/deploy-hub.sh
|
||
```
|
||
|
||
Typprüfung, das Einzige wofür npm gebraucht wird:
|
||
|
||
```powershell
|
||
npm i typescript@5.9 @types/node
|
||
npx tsc --noEmit
|
||
```
|
||
|
||
## Ausliefern
|
||
|
||
Zwei Wege, je nachdem was sich ändert: Hub-Teil (Overlay, Pult, Discord, Chat)
|
||
ist mit `deploy-hub.sh` sofort live, Abdu muss nichts tun. Der PC-Teil
|
||
(`ingest`) kommt nur mit einem neuen Paket an, also nach einem Versions-Tag;
|
||
ein Push auf `main` allein erreicht seinen Rechner nicht.
|
||
|
||
`build-package.ts` lädt Node, ffmpeg und whisper.cpp bei ihren Herausgebern,
|
||
packt alles nach `dist/` und schreibt `abdulive.json`. Dieses Manifest fragt
|
||
`abdulive update` auf Abdus Rechner ab.
|
||
|
||
Die Veröffentlichung `latest` trägt beide Dateien und wird bei jedem Bau
|
||
ersetzt, damit die Adresse gleich bleibt.
|
||
`.gitea/workflows/release.yml` macht das beim Tag automatisch. **Seit
|
||
2026-10-26 läuft sie** (0.5.6, danach 0.5.7). Der erste Lauf (`v0.5.5`) scheiterte daran,
|
||
dass whisper.cpp seine Windows-Programme nicht mehr an `latest` hängt, sondern
|
||
an eigene `b…`-Veröffentlichungen; `githubAsset()` sucht jetzt die neueste
|
||
mit passendem Archiv. Unter `v0.5.5` liegt kein Paket.
|
||
|
||
## Windows-Fallen, die schon zugeschlagen haben
|
||
|
||
Alle vier haben Zeit gekostet, und keine sieht im Fehlerfall nach ihrer
|
||
Ursache aus. Wer an den PowerShell-Skripten arbeitet, sollte sie kennen.
|
||
|
||
**stderr beendet das Skript.** Windows PowerShell 5.1 verpackt jede
|
||
stderr-Zeile eines nativen Programms in einen `ErrorRecord`. Zusammen mit
|
||
`$ErrorActionPreference = 'Stop'` bricht das sofort alles ab. Der Daemon
|
||
schreibt im Normalbetrieb Warnungen auf stderr, der Starter hat ihn also bei
|
||
seiner ersten Logzeile umgebracht: Aufgabe meldete Code 1, das Protokoll war
|
||
leer. Deshalb läuft der Start über `Start-Process` mit getrennten
|
||
Umleitungen, und `install.ps1` ruft native Programme über eine Hilfsfunktion
|
||
auf, die `ErrorActionPreference` kurz zurückstellt.
|
||
|
||
**`& $programm; return $LASTEXITCODE` gibt auch die Ausgabe zurück.** Die
|
||
Funktion liefert dann ein Array aus allen Ausgabezeilen plus Exit-Code, und
|
||
jeder Vergleich damit ist falsch, ohne dass es auffällt. Ein erfolgreiches
|
||
`fetch-model` galt so als fehlgeschlagen. Ausgabe mit `| Out-Host`
|
||
durchreichen.
|
||
|
||
**`localhost` kostet zwei Sekunden.** Der Dienst lauscht nur auf IPv4,
|
||
Windows probiert bei `localhost` zuerst `::1`. Gemessen: 2066 ms bis zum
|
||
Rückfall gegenüber 15 ms bei `127.0.0.1`. Eine Bereitschaftsprüfung mit zwei
|
||
Sekunden Zeitlimit läuft dadurch immer ab, obwohl der Dienst längst läuft.
|
||
|
||
**Windows-Pfade im ffmpeg-Filtergraphen brauchen drei Schritte.** An echtem
|
||
ffmpeg 8.1 durchprobiert:
|
||
|
||
| Form | Ergebnis |
|
||
|---|---|
|
||
| `ass=filename=C:/x/s.ass` | `No option name near '/x/s.ass'` |
|
||
| `ass=filename=C\:/x/s.ass` | ebenso |
|
||
| `ass=filename='C:/x/s.ass'` | ebenso |
|
||
| `ass=filename='C\:/x/s.ass'` | läuft |
|
||
|
||
Vorwärtsschrägstriche, escapter Doppelpunkt **und** Anführungszeichen. Je
|
||
zwei davon reichen nicht. `filterPath()` in `packages/media/src/ffmpeg.ts`
|
||
macht das, mit Tests.
|
||
|
||
## Untertitel: was gemessen ist
|
||
|
||
**Die Schrift muss mitgeliefert werden.** Im Container kam DejaVu Sans aus
|
||
dem Betriebssystem, Windows hat sie nicht. Fehlt `fontsdir`, setzt libass
|
||
wortlos eine Ersatzschrift ein. Gemessen: dieselbe Zeile wird 720 statt 826
|
||
Pixel breit, ohne jede Fehlermeldung. Die Dateien liegen unter
|
||
`assets/fonts/`.
|
||
|
||
**Die Zeilenlänge ist eingemessen, nicht geschätzt.** Vorher stand dort fest
|
||
`maxChars: 30`. Das sind bei Schriftgröße 76 rund 1085 px, nutzbar sind 940.
|
||
Zusammen mit `WrapStyle: 2`, bei dem libass gar nicht umbricht, lief der Text
|
||
links und rechts aus dem Bild. Jetzt wird die Grenze aus Breite und
|
||
Schriftgröße abgeleitet, Versalien zählen breiter, und einzelne zu lange
|
||
Wörter bekommen eine kleinere Schrift, weil libass innerhalb eines Wortes
|
||
nicht umbricht. Deutsche Komposita sind regelmäßig breiter als das Bild.
|
||
|
||
Wer die Schrift wechselt, muss `CHAR_WIDTH_RATIO` in `subtitles.ts` neu
|
||
einmessen. Sonst bricht die Zeile wieder an der falschen Stelle.
|
||
|
||
## Discord: was gemessen ist
|
||
|
||
Vier Dinge, die nicht in der Dokumentation standen, in der man zuerst sucht.
|
||
Alle am echten Server nachgeprüft.
|
||
|
||
**Kanalnamen lassen sich nur zweimal in zehn Minuten ändern.** Danach verlangt
|
||
Discord eine Pause, gemessen wurden 529 Sekunden. Ein Einrichtungslauf, der
|
||
brav auf ein 429 wartet, steht dann neun Minuten. Der Client wartet deshalb
|
||
höchstens eine Minute, meldet den Kanal als übersprungen und macht weiter.
|
||
|
||
**Eine Rolle kann nur Rollen unterhalb der eigenen verschieben** - auch mit
|
||
Administrator. Discord hängt die Rolle eines frisch eingeladenen Bots ganz
|
||
unten ein, also gibt es dort keinen Platz. Der erste Lauf quittierte das mit
|
||
`Missing Permissions`, was nach einem fehlenden Recht aussieht und keines ist.
|
||
`rolePositions()` rechnet den Platz jetzt aus und sagt, was zu tun ist.
|
||
|
||
**Discords Onboarding braucht einen größeren Server.**
|
||
|
||
```
|
||
HTTP 400 Cannot enable onboarding, requirements are not met (350000)
|
||
|
||
sichtbar für @everyone: 5 davon beschreibbar: 3
|
||
Discord verlangt: 7 davon beschreibbar: 5
|
||
```
|
||
|
||
Zwei Kanäle anzulegen, damit eine Schwelle passt, hätte den Server leerer
|
||
gemacht statt besser. Daher der Knopf.
|
||
|
||
**Kanalnamen werden normalisiert, aber nicht so weit wie erwartet.**
|
||
Textkanäle werden kleingeschrieben und Leerzeichen zu Bindestrichen; Emoji,
|
||
Umlaute und das Trennzeichen `│` (U+2502) bleiben unverändert stehen.
|
||
`normalizeName()` bildet das ab. Ohne diesen Abgleich geht bei jedem Lauf
|
||
dieselbe Umbenennung erneut raus - und läuft nach zwei Läufen in das Limit von
|
||
oben.
|
||
|
||
## Was Abdu tun muss
|
||
|
||
1. ~~**Broadcaster-Konto verbinden.**~~ Erledigt am 2026-09-23. Der Rest
|
||
dieses Punkts bleibt als Nachschlag stehen, falls neu verbunden werden
|
||
muss.
|
||
|
||
Die beiden Twitch-Apps (dev.twitch.tv/console/apps) kann auch jemand anders
|
||
anlegen, sie hängen nicht am Kanal. Seit dem Umzug in den Hub brauchen sie
|
||
als Redirect-URI **`https://abdu.dimension47.de/callback`**; die alte
|
||
`http://localhost:4792/callback` bleibt daneben stehen, wenn auf dem
|
||
Stream-PC weiter eingerichtet werden soll. Twitch vergleicht zeichengenau.
|
||
|
||
Die App ist inzwischen hinterlegt. Was nur Abdu kann: auf
|
||
`https://abdu.dimension47.de/setup` eingeloggt als er selbst auf „Mit
|
||
Twitch verbinden" klicken. Der Vorteil gegenüber vorher: das geht von jedem
|
||
Gerät, der Stream-PC muss nicht laufen.
|
||
|
||
Ob die Redirect-URI in der App richtig eingetragen ist, zeigt sich erst bei
|
||
diesem Klick. Twitch prüft sie erst nach dem Login und gibt vorher auch bei
|
||
einer falschen URI ein freundliches 302 zurück - von außen ist das nicht
|
||
nachweisbar.
|
||
2. **OBS einrichten**: obs-websocket an (Port 4455), Wiedergabepuffer 90 s,
|
||
Hotkey auf „Wiedergabepuffer speichern", getrennte Audiospuren.
|
||
3. **Layout umstellen** nach [`obs-layout.md`](obs-layout.md), danach
|
||
`abdulive calibrate` zur Kontrolle.
|
||
4. ~~**Stream-PC im Pult freigeben.**~~ Erledigt am 2026-10-27, ebenso
|
||
obs-websocket und das Passwort aus Punkt 2. Offen aus Punkt 2 bleiben
|
||
Wiedergabepuffer, Hotkey und Audiospuren - nicht nachgeprüft.
|
||
|
||
Das Bot-Konto ist noch nicht verbunden: bis dahin liest das Toolkit den
|
||
Chat anonym mit und kann nichts senden.
|
||
Ohne Punkt 2 gibt es keine Clips. Ohne Punkt 3 sitzt der Ausschnitt schief.
|
||
|
||
Dazu eine Kleinigkeit mit Wirkung: das Bot-Konto sollte Moderator sein
|
||
(`/mod <botname>`). Ohne das gilt ein Limit von 20 Chat-Nachrichten je 30
|
||
Sekunden statt 100, und im Follower-only-Modus kommt der Bot gar nicht durch.
|
||
|
||
Und drei für Discord, die nicht über die API gehen:
|
||
|
||
1. **Sich selbst die Rolle `Abdu` geben.** Das Skript legt Rollen an, aber es
|
||
hängt sie niemandem um.
|
||
2. **Einladungslink** ohne Ablaufdatum erzeugen und ins Twitch-Panel, in die
|
||
Kanalinfo und in einen Chat-Befehl eintragen. Ein Server, den im Stream
|
||
niemand erwähnt, bleibt leer. Das ist der eine Handgriff, an dem hängt, ob
|
||
der Server überhaupt jemanden bekommt.
|
||
3. **Tags in den Foren pflegen.** „Wird gespielt", „Gespielt", „Eher nicht"
|
||
bzw. „Kommt dran", „Angeschaut" setzen nur Abdu und Mods. Ein Vorschlag,
|
||
der sichtbar umgesetzt wird, bringt den nächsten.
|
||
|
||
## Wo ich weitermachen würde
|
||
|
||
**S2 an einem laufenden OBS abnehmen.** Das ist die einzige ungeprüfte Stelle
|
||
in der ganzen Kette, und alles Weitere hängt daran. Seit es `abdulive sources`
|
||
gibt, ist auch die Frage beantwortbar, welche OBS-Quelle fremden Text ins Bild
|
||
malt. Das war vorher nicht zu erraten.
|
||
|
||
**Das Pult an Abdus OBS abnehmen.** Die Freigabe ist erteilt. Jetzt nachsehen, ob die
|
||
Szene `abdulive Ton` mit der Quelle `abdulive Sounds` angelegt und in jede
|
||
Szene eingehängt ist, ob ein Sound im Stream zu hören ist und ob Szenenwechsel
|
||
und Stummschalter greifen. Das fällt mit der Abnahme von S2 zusammen, beides
|
||
braucht nur ein laufendes OBS.
|
||
|
||
**Den Go-Live-Ping scharf schalten.** Er steht auf `draft`. Einmal
|
||
`abdulive golive:test --echt` laufen lassen, prüfen ob das Handy klingelt, dann
|
||
`mode: live`. Das ist der kleinste verbleibende Handgriff mit der größten
|
||
Wirkung.
|
||
|
||
**Das Overlay neu machen** (nächste Sitzung, siehe oben). Der Kamerarahmen
|
||
als Entwurf geht darin auf, die Statusleiste soll ersetzt werden. Danach die
|
||
Liegestütze-Quelle in OBS eintragen und im Stream einmal beobachten.
|
||
|
||
**Recap und die übrigen geplanten Werkzeuge sind gestrichen** (2026-10-27).
|
||
Raid-Scout, Auswertung, VOD-Kapitel, Community-Gedächtnis, Punkteshop und
|
||
Recap sind samt Platzhaltern, Spezifikationen und Konfigurationsabschnitten
|
||
entfernt. Der Hub schreibt keine Chatnachrichten und Zuschauerproben mehr in
|
||
die Datenbank, gelesen haben die nur diese Werkzeuge; die Tabellen bleiben
|
||
stehen. Der angefangene Recap liegt nur im Git-Stash
|
||
`recap WIP (vor dem Rueckbau 2026-10-27)` des Rechners, auf dem er
|
||
angefangen wurde. Der Webhook `recapWebhook` steht weiter im Discord-Bauplan,
|
||
weil das Setup-Skript nie löscht.
|
||
|
||
**Danach S5.** Nicht wegen des Codes, sondern weil das Werkzeug ohne
|
||
Sichtungsseite für Abdu nicht bedienbar ist. Die Kette funktioniert, aber sie
|
||
hat keine Oberfläche, auf der man Clips durchgeht und aussortiert.
|
||
Abnahmekriterium steht im Plan: *Ein Fremder kommt von „nichts eingerichtet"
|
||
bis zum ersten eigenen Clip, ohne die Dokumentation zu lesen.* Installer und
|
||
Handbuch decken davon inzwischen ein gutes Stück ab.
|
||
|
||
## Womit ich nicht zufrieden bin
|
||
|
||
**Das Aufräumen im Arbeitsordner fehlt komplett.** Rohdateien sammeln sich
|
||
unbegrenzt an, mehrere hundert MB je Clip. Das ist die Lücke, die als Erstes
|
||
weh tun wird.
|
||
|
||
**Die Untertitel sind brauchbar, nicht gut.** `ggml-small` verhört sich
|
||
hörbar („Danke" wurde „Dank es"), DejaVu Sans ist unauffällig, und das
|
||
fortlaufende Einfärben ist nicht der Stil, der auf Shorts trägt. Sechs
|
||
konkrete Verbesserungen stehen in [`offene-punkte.md`](offene-punkte.md).
|
||
|
||
**Die CI läuft, aber nur einmal erfolgreich.** Beim nächsten Tag kurz hinsehen.
|
||
|
||
**Das Paket ist nicht signiert.** SmartScreen warnt bei jeder Installation.
|
||
Solange nur Abdu installiert, reicht ein Klick. Sobald jemand Drittes es
|
||
bekommt, nicht mehr.
|
||
|
||
**Der Discord-Knopf hängt am Daemon** - das war der Punkt aus der letzten
|
||
Übergabe und ist mit dem Hub erledigt. Dafür gibt es jetzt einen neuen: der
|
||
Bot darf nur an **einer** Stelle laufen. Steht auf dem Stream-PC ein
|
||
Discord-Bot-Token im Speicher und läuft dort `ingest`, antworten zwei Prozesse
|
||
auf denselben Klick. Auf dem Stream-PC bleibt das Discord-Token leer.
|
||
|
||
**Der Overlay-Schlüssel steht in der Adresse.** Damit steht er in jedem
|
||
OBS-Profil und auf jedem Screenshot der Einstellungen. Neu würfeln geht auf
|
||
der Übersichtsseite, aber danach müssen alle Adressen in OBS neu
|
||
eingetragen werden.
|
||
|
||
**Das Overlay hat nie unter Last gelaufen.** Geprüft ist es gegen den
|
||
Vorführmodus und im Browser. Wie es sich bei 200 Nachrichten pro Minute
|
||
verhält, und was der Emote-Regen beim Encoder kostet, ist offen.
|
||
|
||
**Der Discord-Bot ist nie unter Last gelaufen.** Getestet sind die Bausteine
|
||
und ein Klickweg gegen einen nachgebauten Server; am echten Server ist die
|
||
Verbindung nachgewiesen und der Rollenwechsel einmal in beide Richtungen
|
||
durchgespielt. Was bei gleichzeitigen Klicks oder nach Tagen Laufzeit
|
||
passiert, ist offen.
|
||
|
||
**Es gibt keine Einstellung für den OBS-Wiedergabepuffer-Ordner.** Im
|
||
Container kam der Pfad über `OBS_REPLAY_DIR`, nativ fehlt der Weg. Für die
|
||
Clip-Kette ist das egal, weil OBS den Pfad über WebSocket meldet. Aber
|
||
`calibrate` findet so keine Aufnahmen, und `check-env` warnt zu Recht.
|