Files
abdulive/docs/02-twitch-integration.md
ZeasyAlexandClaude Opus 5 5f6301268a Native Windows-Auslieferung statt Container
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
2026-09-09 17:39:26 +02:00

6.0 KiB
Raw Permalink Blame History

Twitch-Integration

Zwei Identitäten

Es werden zwei getrennte Twitch-Apps/Tokens gebraucht:

  1. Broadcaster-Token (Konto abdulive_) — für alles, was den eigenen Kanal betrifft: Clips erstellen, Titel ändern, Raids starten, EventSub abonnieren, Analytics lesen.
  2. 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:

  1. POST /helix/clips schneidet rückwirkend ~30 s ab dem Aufrufzeitpunkt (minus Stream-Delay). Man kann keinen Zeitbereich wählen.
  2. Der Clip ist nicht sofort verfügbar. Nach dem POST bekommt man id und edit_url; die Verarbeitung dauert typischerweise 5–20 s. Danach liefert GET /helix/clips?id= erst Metadaten.
  3. Die MP4-URL steht nirgends in der API. Sie wird aus thumbnail_url abgeleitet: alles ab -preview- abschneiden, .mp4 anhängen.
    https://clips-media-assets2.twitch.tv/AT-cm%7C123-preview-480x272.jpg
    → https://clips-media-assets2.twitch.tv/AT-cm%7C123.mp4
    
    Das ist undokumentiert und kann sich ändern — deshalb im Code an genau einer Stelle (packages/twitch/clips.ts) gekapselt und mit Fallback versehen.
  4. 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.
  5. 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-Kapitel
  • SaveReplayBuffer → Clipper
  • TriggerHotkeyByName, SetInputSettings → Chat-Steuerung, Loyalty-Shop
  • GetSourceScreenshot → Go-Live-Broadcaster (Screenshot für den Post)