Files
abdulive/docs/02-twitch-integration.md
T
ZeasyAlexandClaude Opus 5 d09e2b945a Struktur, Doku und S0-Toolchain fuer das abdulive-Toolkit
Monorepo-Geruest fuer neun Streamer-Tools plus den Ingest-Daemon, der
unter sechs davon liegt. Enthaelt:

- docs/: Ueberblick, Architektur, Twitch-Integration, Datenmodell,
  Roadmap, Setup, Hebel-Analyse und den MVP-Plan "Clip-Knopf"
- docs/projects/: je eine Spezifikation pro Tool
- docs/decisions/: vier ADRs (TS-Monorepo, SQLite, EventSub/IRC, Docker)
- docker/: Image mit ffmpeg und whisper.cpp, abhaengigkeitsfreier
  Entrypoint mit check-env, fetch-model und calibrate
- config/layout.yaml: aus einem VOD-Screenshot eingemessener
  Facecam-Ausschnitt (440x330 oben links, Chat-Overlay ab x=1270)

Noch kein Anwendungscode - Scheibe S0 aus docs/07-mvp-plan.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VTGjd92LsDDWF3frd9FTB
2026-09-09 08:56:09 +02:00

123 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)