Docker faellt weg. Das Projekt hat keine npm-Abhaengigkeiten und Node fuehrt TypeScript direkt aus - der Container schrieb nur noch drei Programme fest, und dafuer braucht es keine Virtualisierung, sondern einen Ordner. Der Anlass war, dass ADR-0004 zwar `docker compose pull` versprach, es aber nie ein veroeffentlichtes Image gab. Wer installieren wollte, musste whisper.cpp aus dem Quelltext uebersetzen. Plattformschicht (packages/core/src/platform.ts): - Programme werden neben der Anwendung gesucht, nicht im PATH - Daten unter %LOCALAPPDATA%, damit Installation und Update ohne Administratorrechte auskommen - Schriften mitgeliefert: ohne fontsdir setzt libass wortlos eine Ersatzschrift ein - gemessen 720 statt 826 Pixel Zeilenbreite Windows-Eigenheiten, an echtem ffmpeg 8.1 durchprobiert: - Pfade im Filtergraphen brauchen alle drei Schritte (Vorwaertsschraegstriche, escapter Doppelpunkt, Anfuehrungszeichen); je zwei davon reichen nicht - `nice` gibt es nicht, ersetzt durch os.setPriority Auslieferung: - scripts/build-package.ts baut das Paket, laeuft auch unter Linux - install.ps1 ohne Administratorrechte, Autostart ueber den Aufgabenplaner - update legt neue Fassungen bereit; eingespielt wird beim naechsten Start, nie mitten im Stream - ZIP lesen und schreiben ohne Abhaengigkeiten (packages/core/src/zip.ts) Kein `irm ... | iex`-Einzeiler: git.zeasy.dev antwortete instanzweit mit einer Umleitung auf /user/login, und `iex` fuehrte die Anmeldeseite als PowerShell aus. Der Updater verfolgt Umleitungen jetzt nicht und hat keine erfundene Vorgabeadresse mehr. Ausserdem: backup.ps1 war an zwei Stellen dokumentiert, aber nie geschrieben. ADR-0007 ersetzt ADR-0004. 84 Tests, Typpruefung sauber. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhVSfMFdWnXKCv5JfLnArZ
6.0 KiB
Twitch-Integration
Zwei Identitäten
Es werden zwei getrennte Twitch-Apps/Tokens gebraucht:
- Broadcaster-Token (Konto
abdulive_) — für alles, was den eigenen Kanal betrifft: Clips erstellen, Titel ändern, Raids starten, EventSub abonnieren, Analytics lesen. - Bot-Token (eigenes Bot-Konto, z. B.
abdulive_bot) — für Chat-Nachrichten. Trennung, damit Bot-Nachrichten als Bot erkennbar sind und der Broadcaster- Token nicht im Chat-Client hängt.
Client-IDs, Client-Secrets und beide Tokens liegen verschlüsselt in
/db/secrets.json im Docker-Volume und werden über Refresh Tokens automatisch
erneuert (packages/twitch/secrets.ts). Eingegeben werden sie im
Einrichtungs-Assistenten unter http://localhost:4792/setup, nicht in einer
.env und nie im Repo — siehe ADR-0005.
Scopes
| Scope | Wofür | Token |
|---|---|---|
chat:read |
IRC lesen | Bot |
chat:edit |
IRC schreiben | Bot |
user:write:chat |
Chat über Helix senden (Alternative zu IRC) | Bot |
moderator:read:chatters |
Zuschauerliste im Chat | Broadcaster |
clips:edit |
Clips erstellen | Broadcaster |
channel:manage:broadcast |
Titel/Kategorie ändern, Marker setzen | Broadcaster |
channel:read:subscriptions |
Sub-Events | Broadcaster |
moderator:read:followers |
Follow-Events (EventSub) | Broadcaster |
channel:manage:raids |
Raid starten/abbrechen | Broadcaster |
channel:read:redemptions |
Channel-Point-Einlösungen | Broadcaster |
channel:manage:redemptions |
eigene Punkte-Rewards verwalten | Broadcaster |
channel:manage:polls |
Umfragen | Broadcaster |
analytics:read:games |
Game-Analytics-Report | Broadcaster |
bits:read |
Bits-Events | Broadcaster |
Der Autorisierungs-Flow läuft über den Einrichtungs-Assistenten:
Start-ScheduledTask -TaskName abdulive, dann http://localhost:4792/setup
öffnen und je einmal
für Broadcaster- und Bot-Konto auf „Mit Twitch verbinden" klicken. Die
Redirect-URI, die in beiden Twitch-Apps hinterlegt sein muss, lautet
http://localhost:4792/callback.
EventSub (WebSocket)
Abonniert werden beim Start:
| Subscription | Version | Nutzen für |
|---|---|---|
stream.online |
1 | Broadcaster, Analytics, Recap |
stream.offline |
1 | Recap, VOD-Kapitel |
channel.update |
2 | VOD-Kapitel (Kategorie/Titel), Analytics |
channel.follow |
2 | Community, Recap |
channel.subscribe / .message / .gift |
1 | Recap, Overlay |
channel.cheer |
1 | Recap, Overlay |
channel.raid (to + from) |
1 | Raid-Tool |
channel.channel_points_custom_reward_redemption.add |
1 | Loyalty, Overlay |
channel.poll.begin/progress/end |
1 | Overlay |
channel.chat.message |
1 | optional, siehe unten |
channel.chat.notification |
1 | Community (Erstzuschauer-Marker) |
Chat: IRC oder EventSub? Beides funktioniert. Wir lesen den Chat über
IRC (irc-ws.chat.twitch.tv:443), weil die Tags dort (first-msg,
returning-chatter, emotes, badges, msg-id) reichhaltiger und stabiler
sind als im EventSub-Payload, und weil Rate Limits beim Senden dort klar
dokumentiert sind. EventSub übernimmt alles Nicht-Chat.
Verbindungsregeln: Eine WS-Session hält max. 300 Subscriptions. Twitch
sendet alle ~10 s ein Keepalive; bleibt es aus, neu verbinden.
session_reconnect liefert eine neue URL — dort verbinden, dann erst die
alte Verbindung schließen. Nach jedem Reconnect: Lücke per Helix-Polling füllen.
Helix — die relevanten Endpunkte
| Endpunkt | Tool |
|---|---|
GET /helix/streams?user_id= |
Ingest (Viewer-Sampling, alle 60 s) |
GET /helix/streams?game_id=&first=100 |
Raid-Scout |
GET /helix/channels / PATCH /helix/channels |
Loyalty (Titel-Shop), VOD-Kapitel |
POST /helix/clips |
Clipper |
GET /helix/clips?id= |
Clipper (Metadaten + Thumbnail-URL) |
GET /helix/videos?user_id=&type=archive |
VOD-Kapitel |
POST /helix/streams/markers |
VOD-Kapitel, Clipper |
GET /helix/chat/chatters |
Community (Anwesenheit, alle 5 min) |
GET /helix/channels/followers |
Recap, Community |
POST /helix/raids |
Raid-Tool |
GET /helix/users?login= |
überall (Login → ID) |
Rate Limits: 800 Punkte/Minute pro Client-ID (Standard: 1 Punkt pro Request).
Der Helix-Client in packages/twitch führt einen Token-Bucket und drosselt
selbst; Ratelimit-Remaining aus dem Response-Header wird ausgewertet.
Bei 429 → Backoff nach Ratelimit-Reset.
Clips: die Fallstricke
Das ist der Teil, an dem die Clip-Pipeline scheitert, wenn man ihn nicht kennt:
POST /helix/clipsschneidet rückwirkend ~30 s ab dem Aufrufzeitpunkt (minus Stream-Delay). Man kann keinen Zeitbereich wählen.- Der Clip ist nicht sofort verfügbar. Nach dem POST bekommt man
idundedit_url; die Verarbeitung dauert typischerweise 5–20 s. Danach liefertGET /helix/clips?id=erst Metadaten. - Die MP4-URL steht nirgends in der API. Sie wird aus
thumbnail_urlabgeleitet: alles ab-preview-abschneiden,.mp4anhängen.Das ist undokumentiert und kann sich ändern — deshalb im Code an genau einer Stelle (https://clips-media-assets2.twitch.tv/AT-cm%7C123-preview-480x272.jpg → https://clips-media-assets2.twitch.tv/AT-cm%7C123.mp4packages/twitch/clips.ts) gekapselt und mit Fallback versehen. - Der Clip enthält das fertig komposite Bild (1920×1080), nicht die Einzelquellen. Facecam und Gameplay lassen sich nur per Crop trennen, und nur wenn das OBS-Layout fest ist.
- Bitrate/Qualität entspricht dem Transcode, nicht dem Original.
Deshalb: OBS-Replay-Buffer ist der bessere Primärweg. Details in
docs/projects/clip-shorts.md.
OBS-WebSocket
obs-websocket v5 auf ws://127.0.0.1:4455. Genutzt für:
CurrentProgramSceneChanged→ VOD-KapitelSaveReplayBuffer→ ClipperTriggerHotkeyByName,SetInputSettings→ Chat-Steuerung, Loyalty-ShopGetSourceScreenshot→ Go-Live-Broadcaster (Screenshot für den Post)