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
129 lines
6.0 KiB
Markdown
129 lines
6.0 KiB
Markdown
# 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](decisions/ADR-0005-zugangsdaten.md).
|
||
|
||
## 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`](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)
|