diff --git a/lib/services/yt_download_service.dart b/lib/services/yt_download_service.dart index 006bdf8..ce33304 100644 --- a/lib/services/yt_download_service.dart +++ b/lib/services/yt_download_service.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'baka_auth.dart'; +import 'gast_zugang.dart'; /// Entfernt alles, was in einem Dateinamen Ärger macht. String sichererDateiname(String titel) => @@ -35,12 +36,13 @@ class YtErgebnis { /// Lädt Musik über den Baka-YouTube-Proxy herunter. Der Proxy erledigt die /// eigentliche Arbeit und liefert eine fertige MP3; die App speichert sie nur. class YtDownloadService extends ChangeNotifier { - YtDownloadService({required this.auth, http.Client? client}) + YtDownloadService({required this.auth, this.gast, http.Client? client}) : _client = client ?? http.Client(); static const proxyUrl = 'https://yt.baka-net.de'; final BakaAuth auth; + final GastZugang? gast; final http.Client _client; bool _laeuft = false; @@ -95,7 +97,8 @@ class YtDownloadService extends ChangeNotifier { final abbruch = _abbruch = Completer(); notifyListeners(); - if (!auth.istAngemeldet) { + final gastAktiv = !auth.istAngemeldet && (gast?.hatToken ?? false); + if (!auth.istAngemeldet && !gastAktiv) { _scheitere('Bitte zuerst beim Baka-Konto anmelden'); return null; } @@ -103,6 +106,7 @@ class YtDownloadService extends ChangeNotifier { _scheitere('Das ist keine YouTube-Adresse'); return null; } + final zugangsHeader = gastAktiv ? gast!.gastHeader : auth.authHeader; // ─── 1/2: Proxy fragen — er lädt das Video und wandelt es um ─── _melde('Proxy wird gefragt …'); @@ -113,7 +117,7 @@ class YtDownloadService extends ChangeNotifier { .post( Uri.parse('$proxyUrl/api/yt-dl'), headers: { - ...auth.authHeader, + ...zugangsHeader, 'Content-Type': 'application/json' }, body: jsonEncode({'url': url, 'cookies': cookies}), @@ -137,7 +141,10 @@ class YtDownloadService extends ChangeNotifier { } if (antwort.statusCode == 401) { - _scheitere('Anmeldung abgelaufen — bitte neu anmelden'); + if (gastAktiv) unawaited(gast!.holeToken()); + _scheitere(gastAktiv + ? 'Gast-Zugang abgelaufen — bitte erneut versuchen' + : 'Anmeldung abgelaufen — bitte neu anmelden'); return null; } if (antwort.statusCode != 200) { @@ -149,6 +156,10 @@ class YtDownloadService extends ChangeNotifier { final titel = daten['titel'] as String? ?? 'Unbekannt'; final dauer = (daten['dauer'] as int?) ?? 0; final mp3Url = daten['mp3_url'] as String? ?? '/api/dl/audio.mp3'; + final gastRest = daten['guest_remaining'] as int?; + if (gastAktiv && gastRest != null) { + gast!.merkeVerbleibend(gastRest); + } // ─── 2/2: fertige MP3 abholen und speichern ─── _melde('Lade „$titel“ …'); @@ -161,7 +172,7 @@ class YtDownloadService extends ChangeNotifier { try { final mp3 = await _abbrechbar( _client - .get(Uri.parse('$proxyUrl$mp3Url'), headers: auth.authHeader) + .get(Uri.parse('$proxyUrl$mp3Url'), headers: zugangsHeader) .timeout(const Duration(seconds: 180)), abbruch, ); diff --git a/test/services/yt_download_test.dart b/test/services/yt_download_test.dart index b915abe..002894e 100644 --- a/test/services/yt_download_test.dart +++ b/test/services/yt_download_test.dart @@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:melo/services/baka_auth.dart'; +import 'package:melo/services/gast_zugang.dart'; import 'package:melo/services/yt_download_service.dart'; class _MemorySpeicher implements TokenSpeicher { @@ -284,4 +285,99 @@ void main() { expect((jsonDecode(auftrag!) as Map)['cookies'], isFalse); }); + + group('Gast-Zugang', () { + test('Mit Gast-Token statt Anmeldung wird der Proxy trotzdem gefragt', + () async { + final speicher = _MemorySpeicher(); + speicher.werte['guest_token'] = 'gast-xyz'; + final gast = GastZugang( + client: MockClient((_) async => http.Response('', 500)), + speicher: speicher, + ); + await gast.laden(); + + final dienst = YtDownloadService( + auth: BakaAuth( + client: MockClient((_) async => http.Response('', 500)), + speicher: _MemorySpeicher()), + gast: gast, + client: MockClient((req) async { + if (req.url.path == '/api/yt-dl') { + expect(req.headers['X-Guest-Token'], 'gast-xyz'); + expect(req.headers.containsKey('Authorization'), isFalse); + return http.Response( + jsonEncode({ + 'titel': 'Gast-Lied', + 'dauer': 30, + 'mp3_url': '/api/dl/g.mp3', + 'guest_remaining': 4, + }), + 200); + } + return http.Response.bytes([1], 200); + }), + ); + + final ergebnis = await dienst.herunterladen('https://youtu.be/abc', + zielOrdner: ziel.path); + + expect(ergebnis, isNotNull); + expect(gast.verbleibend, 4); + }); + + test('Ohne Anmeldung UND ohne Gast-Token wird gar nicht erst gefragt', + () async { + var aufrufe = 0; + final dienst = YtDownloadService( + auth: BakaAuth( + client: MockClient((_) async => http.Response('', 500)), + speicher: _MemorySpeicher()), + client: MockClient((_) async { + aufrufe++; + return http.Response('', 200); + }), + ); + + final ergebnis = await dienst.herunterladen('https://youtu.be/abc', + zielOrdner: ziel.path); + + expect(ergebnis, isNull); + expect(dienst.fehler, 'Bitte zuerst beim Baka-Konto anmelden'); + expect(aufrufe, 0); + }); + + test('401 bei Gast-Zugang holt automatisch einen neuen Gast-Token', + () async { + var tokenAufrufe = 0; + final speicher = _MemorySpeicher(); + speicher.werte['guest_token'] = 'alter-token'; + final gast = GastZugang( + client: MockClient((_) async { + tokenAufrufe++; + return http.Response( + jsonEncode({'guest_token': 'neuer-token'}), 200); + }), + speicher: speicher, + ); + await gast.laden(); + + final dienst = YtDownloadService( + auth: BakaAuth( + client: MockClient((_) async => http.Response('', 500)), + speicher: _MemorySpeicher()), + gast: gast, + client: MockClient((_) async => http.Response('Unauthorized', 401)), + ); + + final ergebnis = await dienst.herunterladen('https://youtu.be/abc', + zielOrdner: ziel.path); + // Das Token-Nachholen läuft unawaited im Hintergrund. + await Future.delayed(Duration.zero); + + expect(ergebnis, isNull); + expect(dienst.fehler, 'Gast-Zugang abgelaufen — bitte erneut versuchen'); + expect(tokenAufrufe, 1); + }); + }); }