import 'dart:async'; import 'package:http/http.dart' as http; import '../exceptions/exceptions.dart'; import '../videos/streams/streams.dart'; /// class YoutubeHttpClient extends http.BaseClient { final http.Client _httpClient = http.Client(); final Map _defaultHeaders = const { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36', 'accept-language': 'en-US,en;q=1.0', 'x-youtube-client-name': '1', 'x-youtube-client-version': '2.20200609.04.02', 'x-spf-previous': 'https://www.youtube.com/', 'x-spf-referer': 'https://www.youtube.com/', 'x-youtube-device': 'cbr=Chrome&cbrver=81.0.4044.138&ceng=WebKit&cengver=537.36' '&cos=Windows&cosver=10.0', 'x-youtube-page-label': 'youtube.ytfe.desktop_20200617_1_RC1' }; /// Throws if something is wrong with the response. void _validateResponse(http.BaseResponse response, int statusCode) { var request = response.request; if (request.url.host.endsWith('.google.com') && request.url.path.startsWith('/sorry/')) { throw RequestLimitExceededException.httpRequest(response); } if (statusCode >= 500) { throw TransientFailureException.httpRequest(response); } if (statusCode == 429) { throw RequestLimitExceededException.httpRequest(response); } if (statusCode >= 400) { throw FatalFailureException.httpRequest(response); } } /// Future getString(dynamic url, {Map headers, bool validate = true}) async { var response = await get(url, headers: headers); if (validate) { _validateResponse(response, response.statusCode); } return response.body; } @override Future get(dynamic url, {Map headers, bool validate = false}) async { var response = await super.get(url, headers: headers); if (validate) { _validateResponse(response, response.statusCode); } return response; } /// Future postString(dynamic url, {Map body, Map headers, bool validate = true}) async { var response = await post(url, headers: headers, body: body); if (validate) { _validateResponse(response, response.statusCode); } return response.body; } /// // TODO: Check why isRateLimited is not working. Stream> getStream(StreamInfo streamInfo, {Map headers, bool validate = true, int start = 0, int errorCount = 0}) async* { var url = streamInfo.url; // if (!streamInfo.isRateLimited()) { // var request = http.Request('get', url); // request.headers.addAll(_defaultHeaders); // var response = await request.send(); // if (validate) { // _validateResponse(response, response.statusCode); // } // yield* response.stream; // } else { var bytesCount = start; for (var i = start; i < streamInfo.size.totalBytes; i += 9898989) { try { final request = http.Request('get', url); request.headers['range'] = 'bytes=$i-${i + 9898989 - 1}'; final response = await send(request); if (validate) { _validateResponse(response, response.statusCode); } final stream = StreamController>(); response.stream.listen((data) { bytesCount += data.length; stream.add(data); }, onError: (_) => null, onDone: stream.close, cancelOnError: false); errorCount = 0; yield* stream.stream; } on Exception { if (errorCount == 5) { rethrow; } await Future.delayed(const Duration(milliseconds: 500)); yield* getStream(streamInfo, headers: headers, validate: validate, start: bytesCount, errorCount: errorCount + 1); break; } } // } } /// Future getContentLength(dynamic url, {Map headers, bool validate = true}) async { var response = await head(url, headers: headers); if (validate) { _validateResponse(response, response.statusCode); } return int.tryParse(response.headers['content-length'] ?? ''); } @override void close() => _httpClient.close(); @override Future send(http.BaseRequest request) { _defaultHeaders.forEach((key, value) { if (request.headers[key] == null) { request.headers[key] = _defaultHeaders[key]; } }); // print('Request: $request'); // print('Stack:\n${StackTrace.current}'); return _httpClient.send(request); } }