diff --git a/lib/provider/proxy_playlist/skip_segments.dart b/lib/provider/proxy_playlist/skip_segments.dart index 2d90eea6..7f3d1e9a 100644 --- a/lib/provider/proxy_playlist/skip_segments.dart +++ b/lib/provider/proxy_playlist/skip_segments.dart @@ -1,12 +1,11 @@ -import 'dart:convert'; - import 'package:catcher_2/catcher_2.dart'; +import 'package:dio/dio.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:http/http.dart'; import 'package:spotube/models/skip_segment.dart'; import 'package:spotube/provider/server/active_sourced_track.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_state.dart'; +import 'package:spotube/services/dio/dio.dart'; class SourcedSegments { final String source; @@ -30,29 +29,35 @@ Future> getAndCacheSkipSegments(String id) async { ); } - final res = await get(Uri( - scheme: "https", - host: "sponsor.ajay.app", - path: "/api/skipSegments", - queryParameters: { - "videoID": id, - "category": [ - 'sponsor', - 'selfpromo', - 'interaction', - 'intro', - 'outro', - 'music_offtopic' - ], - "actionType": 'skip' - }, - )); + final res = await globalDio.getUri( + Uri( + scheme: "https", + host: "sponsor.ajay.app", + path: "/api/skipSegments", + queryParameters: { + "videoID": id, + "category": [ + 'sponsor', + 'selfpromo', + 'interaction', + 'intro', + 'outro', + 'music_offtopic' + ], + "actionType": 'skip' + }, + ), + options: Options( + responseType: ResponseType.json, + validateStatus: (status) => (status ?? 0) < 500, + ), + ); - if (res.body == "Not Found") { + if (res.data == "Not Found") { return List.castFrom([]); } - final data = jsonDecode(res.body) as List; + final data = res.data as List; final segments = data.map((obj) { final start = obj["segment"].first.toInt(); final end = obj["segment"].last.toInt(); diff --git a/lib/provider/spotify/lyrics/synced.dart b/lib/provider/spotify/lyrics/synced.dart index afb27a6b..04a2ddca 100644 --- a/lib/provider/spotify/lyrics/synced.dart +++ b/lib/provider/spotify/lyrics/synced.dart @@ -9,29 +9,34 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier Track get _track => arg!; Future getSpotifyLyrics(String? token) async { - final res = await http.get( - Uri.parse( - "https://spclient.wg.spotify.com/color-lyrics/v2/track/${_track.id}?format=json&market=from_token", - ), + final res = await globalDio.getUri( + Uri.parse( + "https://spclient.wg.spotify.com/color-lyrics/v2/track/${_track.id}?format=json&market=from_token", + ), + options: Options( headers: { "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36", "App-platform": "WebPlayer", "authorization": "Bearer $token" - }); + }, + responseType: ResponseType.json, + validateStatus: (status) => true, + ), + ); if (res.statusCode != 200) { return SubtitleSimple( lyrics: [], name: _track.name!, - uri: res.request!.url, + uri: res.realUri, rating: 0, provider: "Spotify", ); } - final linesRaw = Map.castFrom( - jsonDecode(utf8.decode(res.bodyBytes)), - )["lyrics"]?["lines"] as List?; + final linesRaw = + Map.castFrom(res.data)["lyrics"] + ?["lines"] as List?; final lines = linesRaw?.map((line) { return LyricSlice( @@ -44,7 +49,7 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier return SubtitleSimple( lyrics: lines, name: _track.name!, - uri: res.request!.url, + uri: res.realUri, rating: 100, provider: "Spotify", ); @@ -55,7 +60,7 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier Future getLRCLibLyrics() async { final packageInfo = await PackageInfo.fromPlatform(); - final res = await http.get( + final res = await globalDio.getUri( Uri( scheme: "https", host: "lrclib.net", @@ -67,23 +72,26 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier "duration": _track.duration?.inSeconds.toString(), }, ), - headers: { - "User-Agent": - "Spotube v${packageInfo.version} (https://github.com/KRTirtho/spotube)" - }, + options: Options( + headers: { + "User-Agent": + "Spotube v${packageInfo.version} (https://github.com/KRTirtho/spotube)" + }, + responseType: ResponseType.json, + ), ); if (res.statusCode != 200) { return SubtitleSimple( lyrics: [], name: _track.name!, - uri: res.request!.url, + uri: res.realUri, rating: 0, provider: "LRCLib", ); } - final json = jsonDecode(utf8.decode(res.bodyBytes)) as Map; + final json = res.data as Map; final syncedLyricsRaw = json["syncedLyrics"] as String?; final syncedLyrics = syncedLyricsRaw?.isNotEmpty == true @@ -97,7 +105,7 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier return SubtitleSimple( lyrics: syncedLyrics!, name: _track.name!, - uri: res.request!.url, + uri: res.realUri, rating: 100, provider: "LRCLib", ); @@ -111,7 +119,7 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier return SubtitleSimple( lyrics: plainLyrics, name: _track.name!, - uri: res.request!.url, + uri: res.realUri, rating: 0, provider: "LRCLib", ); diff --git a/lib/provider/spotify/spotify.dart b/lib/provider/spotify/spotify.dart index 816420f6..ac83ba72 100644 --- a/lib/provider/spotify/spotify.dart +++ b/lib/provider/spotify/spotify.dart @@ -1,10 +1,10 @@ library spotify; import 'dart:async'; -import 'dart:convert'; import 'package:catcher_2/catcher_2.dart'; import 'package:collection/collection.dart'; +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:intl/intl.dart'; @@ -23,9 +23,9 @@ import 'package:spotube/models/spotify_friends.dart'; import 'package:spotube/provider/custom_spotify_endpoint_provider.dart'; import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; +import 'package:spotube/services/dio/dio.dart'; import 'package:spotube/services/wikipedia/wikipedia.dart'; import 'package:spotube/utils/persisted_state_notifier.dart'; -import 'package:http/http.dart' as http; import 'package:wikipedia_api/wikipedia_api.dart'; diff --git a/lib/services/custom_spotify_endpoints/spotify_endpoints.dart b/lib/services/custom_spotify_endpoints/spotify_endpoints.dart index 553f6824..4bc78f8a 100644 --- a/lib/services/custom_spotify_endpoints/spotify_endpoints.dart +++ b/lib/services/custom_spotify_endpoints/spotify_endpoints.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:http/http.dart' as http; +import 'package:dio/dio.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/models/spotify/home_feed.dart'; import 'package:spotube/models/spotify_friends.dart'; @@ -9,9 +9,21 @@ import 'package:timezone/timezone.dart' as tz; class CustomSpotifyEndpoints { static const _baseUrl = 'https://api.spotify.com/v1'; final String accessToken; - final http.Client _client; + final Dio _client; - CustomSpotifyEndpoints(this.accessToken) : _client = http.Client(); + CustomSpotifyEndpoints(this.accessToken) + : _client = Dio( + BaseOptions( + baseUrl: _baseUrl, + responseType: ResponseType.json, + headers: { + "content-type": "application/json", + if (accessToken.isNotEmpty) + "authorization": "Bearer $accessToken", + "accept": "application/json", + }, + ), + ); // views API @@ -65,44 +77,34 @@ class CustomSpotifyEndpoints { if (country != null) 'country': country.name, }.entries.map((e) => '${e.key}=${e.value}').join('&'); - final res = await _client.get( + final res = await _client.getUri( Uri.parse('$_baseUrl/views/$view?$queryParams'), - headers: { - "content-type": "application/json", - "authorization": "Bearer $accessToken", - "accept": "application/json", - }, ); if (res.statusCode == 200) { - return jsonDecode(utf8.decode(res.bodyBytes)); + return res.data; } else { throw Exception( '[CustomSpotifyEndpoints.getView]: Failed to get view' '\nStatus code: ${res.statusCode}' - '\nBody: ${res.body}', + '\nBody: ${res.data}', ); } } Future> listGenreSeeds() async { - final res = await _client.get( + final res = await _client.getUri( Uri.parse("$_baseUrl/recommendations/available-genre-seeds"), - headers: { - "content-type": "application/json", - if (accessToken.isNotEmpty) "authorization": "Bearer $accessToken", - "accept": "application/json", - }, ); if (res.statusCode == 200) { - final body = jsonDecode(utf8.decode(res.bodyBytes)); + final body = res.data; return List.from(body["genres"] ?? []); } else { throw Exception( '[CustomSpotifyEndpoints.listGenreSeeds]: Failed to get genre seeds' '\nStatus code: ${res.statusCode}' - '\nBody: ${res.body}', + '\nBody: ${res.data}', ); } } @@ -152,30 +154,18 @@ class CustomSpotifyEndpoints { } final pathQuery = "$_baseUrl/recommendations?${parameters.entries.map((e) => '${e.key}=${e.value}').join('&')}"; - final res = await _client.get( - Uri.parse(pathQuery), - headers: { - "content-type": "application/json", - if (accessToken.isNotEmpty) "authorization": "Bearer $accessToken", - "accept": "application/json", - }, - ); - final result = jsonDecode(utf8.decode(res.bodyBytes)); + final res = await _client.getUri(Uri.parse(pathQuery)); + final result = res.data; return List.castFrom( result["tracks"].map((track) => Track.fromJson(track)).toList(), ); } Future getFriendActivity() async { - final res = await _client.get( + final res = await _client.getUri( Uri.parse("https://guc-spclient.spotify.com/presence-view/v1/buddylist"), - headers: { - "content-type": "application/json", - "authorization": "Bearer $accessToken", - "accept": "application/json", - }, ); - return SpotifyFriends.fromJson(jsonDecode(utf8.decode(res.bodyBytes))); + return SpotifyFriends.fromJson(res.data); } Future getHomeFeed({ @@ -190,50 +180,39 @@ class CustomSpotifyEndpoints { 'origin': 'https://open.spotify.com', 'referer': 'https://open.spotify.com/' }; - final response = await http.get( - Uri( - scheme: "https", - host: "api-partner.spotify.com", - path: "/pathfinder/v1/query", - queryParameters: { - "operationName": "home", - "variables": jsonEncode({ - "timeZone": tz.local.name, - "sp_t": spTCookie, - "country": country.name, - "facet": null, - "sectionItemsLimit": 10 - }), - "extensions": jsonEncode( - { - "persistedQuery": { - "version": 1, + final response = await _client.getUri( + Uri( + scheme: "https", + host: "api-partner.spotify.com", + path: "/pathfinder/v1/query", + queryParameters: { + "operationName": "home", + "variables": jsonEncode({ + "timeZone": tz.local.name, + "sp_t": spTCookie, + "country": country.name, + "facet": null, + "sectionItemsLimit": 10 + }), + "extensions": jsonEncode( + { + "persistedQuery": { + "version": 1, - /// GraphQL persisted Query hash - /// This can change overtime. We've to lookout for it - /// Docs: https://www.apollographql.com/docs/graphos/operations/persisted-queries/ - "sha256Hash": - "eb3fba2d388cf4fc4d696b1757a58584e9538a3b515ea742e9cc9465807340be", - } - }, - ), - }, - ), - headers: headers, - ); - - if (response.statusCode >= 400) { - throw Exception( - "[RequestException] " - "Status: ${response.statusCode}\n" - "Body: ${response.body}", - ); - } + /// GraphQL persisted Query hash + /// This can change overtime. We've to lookout for it + /// Docs: https://www.apollographql.com/docs/graphos/operations/persisted-queries/ + "sha256Hash": + "eb3fba2d388cf4fc4d696b1757a58584e9538a3b515ea742e9cc9465807340be", + } + }, + ), + }, + ), + options: Options(headers: headers)); final data = SpotifyHomeFeed.fromJson( - transformHomeFeedJsonMap( - jsonDecode(utf8.decode(response.bodyBytes)), - ), + transformHomeFeedJsonMap(response.data), ); return data; @@ -252,7 +231,7 @@ class CustomSpotifyEndpoints { 'origin': 'https://open.spotify.com', 'referer': 'https://open.spotify.com/' }; - final response = await http.get( + final response = await _client.getUri( Uri( scheme: "https", host: "api-partner.spotify.com", @@ -280,21 +259,12 @@ class CustomSpotifyEndpoints { ), }, ), - headers: headers, + options: Options(headers: headers), ); - if (response.statusCode >= 400) { - throw Exception( - "[RequestException] " - "Status: ${response.statusCode}\n" - "Body: ${response.body}", - ); - } - final data = SpotifyHomeFeedSection.fromJson( transformSectionItemJsonMap( - jsonDecode(utf8.decode(response.bodyBytes))["data"]["homeSections"] - ["sections"][0], + response.data["data"]["homeSections"]["sections"][0], ), ); diff --git a/lib/services/dio/dio.dart b/lib/services/dio/dio.dart new file mode 100644 index 00000000..cddf1979 --- /dev/null +++ b/lib/services/dio/dio.dart @@ -0,0 +1,3 @@ +import 'package:dio/dio.dart'; + +final globalDio = Dio(); diff --git a/lib/utils/service_utils.dart b/lib/utils/service_utils.dart index 1432eb53..aa2cd985 100644 --- a/lib/utils/service_utils.dart +++ b/lib/utils/service_utils.dart @@ -1,13 +1,12 @@ -import 'dart:convert'; - +import 'package:dio/dio.dart'; import 'package:go_router/go_router.dart'; import 'package:html/dom.dart' hide Text; import 'package:spotify/spotify.dart'; import 'package:spotube/components/library/user_local_tracks.dart'; import 'package:spotube/components/root/update_dialog.dart'; import 'package:spotube/models/logger.dart'; -import 'package:http/http.dart' as http; import 'package:spotube/models/lyrics.dart'; +import 'package:spotube/services/dio/dio.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:spotube/utils/primitive_utils.dart'; @@ -70,9 +69,12 @@ abstract class ServiceUtils { } static Future extractLyrics(Uri url) async { - final response = await http.get(url); + final response = await globalDio.getUri( + url, + options: Options(responseType: ResponseType.plain), + ); - Document document = parser.parse(response.body); + Document document = parser.parse(response.data); String? lyrics = document.querySelector('div.lyrics')?.text.trim(); if (lyrics == null) { lyrics = ""; @@ -111,11 +113,14 @@ abstract class ServiceUtils { String reqUrl = "$searchUrl${Uri.encodeComponent(song)}"; Map headers = {"Authorization": 'Bearer $apiKey'}; - final response = await http.get( + final response = await globalDio.getUri( Uri.parse(authHeader ? reqUrl : "$reqUrl&access_token=$apiKey"), - headers: authHeader ? headers : null, + options: Options( + headers: authHeader ? headers : null, + responseType: ResponseType.json, + ), ); - Map data = jsonDecode(utf8.decode(response.bodyBytes))["response"]; + Map data = response.data["response"]; if (data["hits"]?.length == 0) return null; List results = data["hits"]?.map((val) { return { @@ -195,8 +200,11 @@ abstract class ServiceUtils { queryParameters: {"q": query}, ); - final res = await http.get(searchUri); - final document = parser.parse(res.body); + final res = await globalDio.getUri( + searchUri, + options: Options(responseType: ResponseType.plain), + ); + final document = parser.parse(res.data); final results = document.querySelectorAll("#tablecontainer table tbody tr td a"); @@ -229,7 +237,11 @@ abstract class ServiceUtils { logger.v("[Selected subtitle] ${topResult.text} | $subtitleUri"); - final lrcDocument = parser.parse((await http.get(subtitleUri)).body); + final lrcDocument = parser.parse((await globalDio.getUri( + subtitleUri, + options: Options(responseType: ResponseType.plain), + )) + .data); final lrcList = lrcDocument .querySelector("#ctl00_ContentPlaceHolder1_lbllyrics") ?.innerHtml @@ -384,14 +396,16 @@ abstract class ServiceUtils { final packageInfo = await PackageInfo.fromPlatform(); if (Env.releaseChannel == ReleaseChannel.nightly) { - final value = await http.get( + final value = await globalDio.getUri( Uri.parse( "https://api.github.com/repos/KRTirtho/spotube/actions/workflows/spotube-release-binary.yml/runs?status=success&per_page=1", ), + options: Options( + responseType: ResponseType.json, + ), ); - final buildNum = - jsonDecode(value.body)["workflow_runs"][0]["run_number"] as int; + final buildNum = value.data["workflow_runs"][0]["run_number"] as int; if (buildNum <= int.parse(packageInfo.buildNumber) || !context.mounted) { return; @@ -406,13 +420,12 @@ abstract class ServiceUtils { }, ); } else { - final value = await http.get( + final value = await globalDio.getUri( Uri.parse( "https://api.github.com/repos/KRTirtho/spotube/releases/latest", ), ); - final tagName = - (jsonDecode(value.body)["tag_name"] as String).replaceAll("v", ""); + final tagName = (value.data["tag_name"] as String).replaceAll("v", ""); final currentVersion = packageInfo.version == "Unknown" ? null : Version.parse(packageInfo.version); diff --git a/pubspec.yaml b/pubspec.yaml index 6ec4a2fc..c3ab2a53 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,7 +55,6 @@ dependencies: hive_flutter: ^1.1.0 hooks_riverpod: ^2.5.1 html: ^0.15.1 - http: ^1.2.0 image_picker: ^1.1.0 intl: ^0.18.0 introduction_screen: ^3.1.14 @@ -131,6 +130,7 @@ dependencies: crypto: ^3.0.3 local_notifier: ^0.1.6 tray_manager: ^0.2.2 + http: ^1.2.1 dev_dependencies: build_runner: ^2.4.9