Files
Melo/docs/superpowers/specs/2026-08-26-youtube-guest-quota-design.md
T
Hermes (Server)andClaude Sonnet 5 2d94a3aa26 Spec: YouTube-Gast-Zugang mit Tages-Limit
Design-Dokument für den neuen anonymen Gast-Zugang zum YouTube-Proxy
(5 Downloads/Tag, keine Cookies) neben dem bestehenden
Baka-Auth-Cloud-Zugang. Mit Dustin im Brainstorming abgestimmt:
geräte-gebundener Gast-Token, bewusste Wahl in der UI, Reset nach
Server-Kalendertag, Suche bleibt unbegrenzt. Server-seitiger Teil
(yt_proxy.py) liegt außerhalb dieses Worktrees — die Spec beschreibt
den Vertrag dafür.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CcDiyJdVRqh1TtJk5JiabX
2026-08-26 20:16:27 +02:00

216 lines
9.5 KiB
Markdown

# YouTube-Tab: Gast-Zugang mit Tages-Limit
Status: Approved (Dustin, 2026-08-26) — bereit für Implementierungsplan.
## Kontext
Der YouTube-Downloader/die YT-Suche laufen über den Baka-Proxy
(`yt.baka-net.de``yt_proxy.py`), der für **jede** Anfrage einen gültigen
Baka-Auth-Bearer-JWT verlangt (`auth_common.verify_token`). Seit dem Fix vom
2026-08-25/26 melden sich Server-User (Dustin, Baka, Tinker) automatisch im
Hintergrund an (`BakaAuth.autoAnmelden`, siehe `lib/services/baka_auth.dart`),
Gäste sehen weiterhin den manuellen „Beim Baka-Konto anmelden“-Dialog.
Neue Anforderung (Dustin, 2026-08-26): Jeder Cloud-Account-User (die
baka-auth-User: baka, dustin, tinker — und künftig per Einladungscode
registrierte weitere User) bekommt automatisch Dustins YouTube-Cookies und
hat kein Limit. **Gäste ohne Cloud-Account sollen die App trotzdem nutzen
können, aber mit einem Tages-Limit von 5 Downloads und ohne Cookies.**
Heute ist das serverseitig unmöglich: Ohne gültigen Bearer-Token antwortet
jeder Endpunkt mit 401. Es gibt keinen Weg, einen Gast über mehrere
Anfragen hinweg wiederzuerkennen.
## Ziel
Ein dritter, anonymer Zugangsweg („Gast-Token“) neben dem bestehenden
Baka-Auth-JWT, mit serverseitig durchgesetztem Tages-Limit für Downloads.
Suche bleibt für Gäste unbegrenzt. Cookies bleiben Cloud-Accounts
vorbehalten.
## Nicht-Ziele
- Kein Ersatz für das bestehende Baka-Auth-System — beide Wege bestehen
parallel.
- Kein Schutz gegen App-Neuinstallation/Token-Löschen — ein Gast kann sich
durch Neuinstallation ein neues Kontingent holen. Das ist eine bewusste
Hürde, kein harter Schutz (siehe Entscheidung unten).
- Keine Änderung an „nur Audio“ — das gilt schon für alle User (yt-dlp
extrahiert immer MP3, es gibt keine Video-Download-Option in der App).
- Keine UI/Server-Änderung an bestehenden Cloud-Account-Flows außer der
Cookie-Beschränkung (die war vorher implizit für alle gleich, jetzt
explizit an den Zugangsweg gekoppelt).
## Entscheidungen (aus dem Brainstorming, mit Dustin abgestimmt)
| Frage | Entscheidung |
|---|---|
| Gast-Identität | Geräte-gebundener, anonymer Token — kein IP-basiertes Limit, keine Einladungscode-Pflicht für Gäste |
| UX-Fluss | Bewusste Wahl: Gast sieht „Anmelden“ UND „Als Gast fortfahren (5/Tag)“ als zwei Buttons, kein automatisches Freischalten |
| Reset-Zeitpunkt | Kalendertag nach Server-Zeit (Aingrad), kein rollierendes 24h-Fenster |
| Suche fürs Limit | Suche bleibt für Gäste unbegrenzt — nur `POST /api/yt-dl` zählt |
| Cookies für Gäste | Nein — nur Cloud-Accounts bekommen Dustins YouTube-Cookies; der `cookies`-Parameter wird bei Gast-Token-Anfragen ignoriert |
## Architektur
Drei Zugangsklassen beim Proxy:
1. **Cloud-Account** (`Authorization: Bearer <JWT>`, wie heute) — unbegrenzt,
Cookies automatisch.
2. **Gast** (`X-Guest-Token: <token>`, neu) — 5 Downloads/Kalendertag,
Suche unbegrenzt, keine Cookies.
3. **Kein Zugang** — weder Bearer noch gültiger Gast-Token → 401 wie heute.
## Server-seitige Komponenten
**Betrifft `/home/dustin/scripts/yt_proxy.py` und ggf. `auth_common.py`
liegt im `claude-server`-Worktree, nicht in `~/mello-dev/app`. Diese Spec
beschreibt den Vertrag, den die App-Seite braucht; die Umsetzung macht die
Server-Session.**
### Neuer Endpunkt: `POST /api/guest-token`
Kein Auth-Header nötig. Erzeugt einen neuen, zufälligen Token
(`secrets.token_hex(24)`, 48 Hex-Zeichen) und legt eine Zeile in einer
neuen SQLite-Tabelle an:
```sql
CREATE TABLE IF NOT EXISTS guest_quota (
token TEXT PRIMARY KEY,
count INTEGER NOT NULL DEFAULT 0,
count_date TEXT NOT NULL, -- 'YYYY-MM-DD', Server-Zeit
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
```
Antwort `200`: `{"guest_token": "<token>"}`.
Speicherort: neue Datei `/home/dustin/yt-cache/guest_quota.db` (analog zum
bestehenden `DOWNLOAD_DIR`-Cache, getrennt vom Lieder-Register und von
`baka_auth.db`, da funktional unabhängig).
### `_checke_auth()` erweitert
Bisher: `True`/`False`. Neu: gibt zurück, welche Zugriffsart vorliegt
(z. B. ein kleines Ergebnis-Objekt `{"cloud": bool, "guest_token": str|None}`)
statt nur ja/nein:
- `Authorization: Bearer <JWT>` gültig → Cloud-Zugriff (wie heute).
- Sonst, falls `X-Guest-Token` gesetzt und in `guest_quota` vorhanden →
Gast-Zugriff mit dem jeweiligen Token.
- Sonst → kein Zugriff, 401 wie heute.
### `GET /api/search`
Cloud- **und** Gast-Zugriff erlaubt, keine Zählung für Gäste.
### `POST /api/yt-dl`
- Cloud-Zugriff: wie heute, `cookies`-Parameter aus dem Body wirksam.
- Gast-Zugriff: `cookies` wird serverseitig auf `False` erzwungen
(Body-Wert ignoriert). Vor dem Download: `count_date` der Token-Zeile
mit dem heutigen Server-Datum vergleichen — weicht es ab, `count` auf 0
und `count_date` auf heute zurücksetzen (Tageswechsel). Ist danach
`count >= 5``429` mit
`{"error": "Tages-Limit erreicht (5/Tag) — ab morgen wieder verfügbar", "cooldown": <Sekunden bis Mitternacht>}`.
Sonst: Download wie bisher, bei Erfolg `count += 1` in derselben
Transaktion wie die Antwort (kein Doppelzählen bei Netzwerkfehlern nach
dem Schreiben — Erfolg zuerst prüfen, dann zählen).
- Erfolgsantwort bekommt zusätzlich `"guest_remaining": <int>` (nur bei
Gast-Zugriff; bei Cloud-Zugriff entfällt das Feld), damit die App den
Zähler ohne Extra-Anfrage aktualisieren kann.
### `GET /api/dl/<file>`
Cloud- und Gast-Zugriff erlaubt (die Datei wurde beim `yt-dl`-Aufruf schon
gezählt, hier nur Auslieferung).
### Fehlerfall: unbekannter/ungültiger Gast-Token
Antwort wie „kein Zugriff“ (401 mit `{"error": "Unauthorized"}` — nicht von
einem abgelaufenen Cloud-Token zu unterscheiden nötig, die App behandelt
beide gleich: neuen Gast-Token holen, siehe unten).
## App-seitige Komponenten (diese Seite implementiere ich)
### Neuer Service `GastZugang` (`lib/services/gast_zugang.dart`)
Analog zu `BakaAuth`, aber ohne Passwort:
```dart
class GastZugang extends ChangeNotifier {
String? _token;
int? _verbleibend; // zuletzt vom Server gemeldeter Stand, nur fürs UI
bool get hatToken => _token != null;
int? get verbleibend => _verbleibend;
Map<String, String> get gastHeader =>
{if (_token != null) 'X-Guest-Token': _token!};
Future<String?> holeToken(); // POST /api/guest-token, speichert Token sicher
void merkeVerbleibend(int n); // aus der yt-dl-Antwort übernehmen
}
```
Token-Speicherung: `flutter_secure_storage`, eigener Schlüssel
`guest_token` (gleiches Muster wie `BakaAuth`/`TokenSpeicher`).
### `YtDownloadService` / `YtSearchService`
Bekommen einen optionalen zweiten Header-Lieferanten: statt nur
`auth.authHeader` können sie auch `gast.gastHeader` verwenden, je nachdem
welcher Zugang aktiv ist. `herunterladen()` liest bei Gast-Zugriff
`daten['guest_remaining']` aus der Erfolgsantwort und ruft
`gast.merkeVerbleibend(...)`. Ein `429` wird wie die bestehenden
Fehlerfälle über `_scheitere(text)` gemeldet (Klartext aus der
Server-Antwort, kein neuer Fehlerpfad nötig).
### UI (`_YouTubeBereich` in `downloads_screen.dart`, `YoutubeSearchScreen`)
Für echte Gäste (`!_serverUser && !auth.istAngemeldet && !gast.hatToken`):
zwei Buttons nebeneinander/untereinander — „Beim Baka-Konto anmelden“
(bestehend) und „Als Gast fortfahren (5 Downloads/Tag)“ (neu, ruft
`gast.holeToken()`). Nach erfolgreichem Gast-Token: Adressfeld/Suchfeld wie
gewohnt freigeschaltet, zusätzlich ein kleiner Text „noch N von 5 heute“
(aus `gast.verbleibend`, `null` vor dem ersten Download = kein Zähler
sichtbar, erst nach dem ersten Download bekannt).
Der bestehende Cookie-Schalter (`SwitchListTile`, „YouTube-Cookies des
Servers verwenden“) wird für Gäste ausgeblendet — der Server ignoriert den
Wert ohnehin, ein sichtbarer aber wirkungsloser Schalter wäre irreführend.
## Fehlerfälle (App-Seite)
- **Gast-Token vom Server abgelehnt** (401 trotz gesetztem `X-Guest-Token`,
z. B. weil die Server-DB zurückgesetzt wurde): App holt automatisch
einen neuen Token und meldet den fehlgeschlagenen Versuch wie einen
normalen Fehler („bitte erneut versuchen“) — kein automatischer Retry
des Downloads selbst, um keine Doppel-Downloads auszulösen.
- **429 Tages-Limit erreicht**: Klartext-Fehlermeldung aus der
Server-Antwort, kein Absturz, kein automatischer Retry.
- **App-Neuinstallation**: neuer Gast-Token, neues Kontingent — bewusst
akzeptiert (siehe Nicht-Ziele).
- **Netzwerkfehler bei `POST /api/guest-token`**: wie bestehende
„Proxy nicht erreichbar“-Fehler behandelt.
## Tests
- **Server** (schreibt die `claude-server`-Session): Unit-Tests für
`guest_quota`-Zählung, Tageswechsel-Reset, Cookie-Ausschluss bei
Gast-Zugriff, 429 bei erreichtem Limit, Suche ohne Zählung.
- **App** (schreibe ich, TDD): `GastZugang` (Token holen, speichern,
`merkeVerbleibend`), Widget-Tests für die zwei Gast-Buttons und den
Zähler-Text, `YtDownloadService`/`YtSearchService` mit Gast-Header statt
Bearer-Header, 429-Fehlertext-Anzeige.
## Offene Abhängigkeit
Diese Spec beschreibt einen Vertrag zwischen App und Server. Die
App-seitige Implementierung (Service, UI) kann erst sinnvoll gegen den
echten Server getestet werden, wenn `POST /api/guest-token` und die
erweiterte `_checke_auth()`-Logik in `yt_proxy.py` existieren. Bis dahin
entwickle ich die App-Seite gegen einen gemockten Proxy (wie bei
`BakaAuth`/`YtDownloadService` schon üblich) und dokumentiere die exakten
Endpunkt-Erwartungen oben so genau, dass beide Seiten unabhängig
implementiert werden können.