# 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. Beide Tokens liegen verschlüsselt in `data/db/tokens.json`, werden über Refresh Tokens automatisch erneuert (`packages/twitch/tokenStore.ts`). Client-Secret kommt aus `.env`, niemals aus der DB, niemals ins Repo. ## 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 einmalig über `docker compose run --rm --service-ports toolkit auth` → lokaler Redirect auf `http://localhost:4780/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`](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)