import 'dart:async'; import 'package:dio/adapter.dart'; import 'package:dio/dio.dart'; import 'package:cookie_jar/cookie_jar.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:freezer/api/cache.dart'; import 'dart:io'; import 'dart:convert'; import '../settings.dart'; import 'definitions.dart'; DeezerAPI deezerAPI = DeezerAPI(); class DeezerAPI { String arl; DeezerAPI({this.arl}); String token; String userId; String userName; String favoritesPlaylistId; String privateUrl = 'http://www.deezer.com/ajax/gw-light.php'; Map headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", "Content-Language": '${settings.deezerLanguage??"en"}-${settings.deezerCountry??'US'}', "Cache-Control": "max-age=0", "Accept": "*/*", "Accept-Charset": "utf-8,ISO-8859-1;q=0.7,*;q=0.3", "Accept-Language": "${settings.deezerLanguage??"en"}-${settings.deezerCountry??'US'},${settings.deezerLanguage??"en"};q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "keep-alive" }; Future _authorizing; Dio dio = Dio(); CookieJar _cookieJar = new CookieJar(); //Call private api Future> callApi(String method, {Map params, String gatewayInput}) async { //Add headers dio.interceptors.add(InterceptorsWrapper( onRequest: (RequestOptions options) { options.headers = this.headers; return options; } )); //Proxy if (settings.proxyAddress != null && settings.proxyAddress != '' && settings.proxyAddress.length > 9) { (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { client.findProxy = (uri) => "PROXY ${settings.proxyAddress}"; client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; }; } //Add cookies List cookies = [Cookie('arl', this.arl)]; _cookieJar.saveFromResponse(Uri.parse(this.privateUrl), cookies); dio.interceptors.add(CookieManager(_cookieJar)); //Make request Response response = await dio.post( this.privateUrl, queryParameters: { 'api_version': '1.0', 'api_token': this.token, 'input': '3', 'method': method, //Used for homepage if (gatewayInput != null) 'gateway_input': gatewayInput }, data: jsonEncode(params??{}), options: Options(responseType: ResponseType.json, sendTimeout: 10000, receiveTimeout: 10000) ); return response.data; } Future callPublicApi(String path) async { Dio dio = Dio(); Response response = await dio.get( 'https://api.deezer.com/' + path, options: Options(responseType: ResponseType.json, sendTimeout: 10000, receiveTimeout: 10000) ); return response.data; } //Wrapper so it can be globally awaited Future authorize() async { if (_authorizing == null) { this._authorizing = this.rawAuthorize(); } return _authorizing; } //Authorize, bool = success Future rawAuthorize({Function onError}) async { try { Map data = await callApi('deezer.getUserData'); if (data['results']['USER']['USER_ID'] == 0) { return false; } else { this.token = data['results']['checkForm']; this.userId = data['results']['USER']['USER_ID'].toString(); this.userName = data['results']['USER']['BLOG_NAME']; this.favoritesPlaylistId = data['results']['USER']['LOVEDTRACKS_ID']; return true; } } catch (e) { if (onError != null) onError(e); print('Login Error (D): ' + e.toString()); return false; } } //URL/Link parser Future parseLink(String url) async { Uri uri = Uri.parse(url); //https://www.deezer.com/NOTHING_OR_COUNTRY/TYPE/ID if (uri.host == 'www.deezer.com' || uri.host == 'deezer.com') { if (uri.pathSegments.length < 2) return null; DeezerLinkType type = DeezerLinkResponse.typeFromString(uri.pathSegments[uri.pathSegments.length-2]); return DeezerLinkResponse(type: type, id: uri.pathSegments[uri.pathSegments.length-1]); } //Share URL if (uri.host == 'deezer.page.link' || uri.host == 'www.deezer.page.link') { Dio dio = Dio(); Response res = await dio.head(url, options: RequestOptions( followRedirects: true )); return parseLink('http://deezer.com' + res.realUri.toString()); } } //Search Future search(String query) async { Map data = await callApi('deezer.pageSearch', params: { 'nb': 50, 'query': query, 'start': 0 }); return SearchResults.fromPrivateJson(data['results']); } Future track(String id) async { Map data = await callApi('song.getListData', params: {'sng_ids': [id]}); return Track.fromPrivateJson(data['results']['data'][0]); } //Get album details, tracks Future album(String id) async { Map data = await callApi('deezer.pageAlbum', params: { 'alb_id': id, 'header': true, 'lang': settings.deezerLanguage??'en' }); return Album.fromPrivateJson(data['results']['DATA'], songsJson: data['results']['SONGS']); } //Get artist details Future artist(String id) async { Map data = await callApi('deezer.pageArtist', params: { 'art_id': id, 'lang': settings.deezerLanguage??'en', }); return Artist.fromPrivateJson( data['results']['DATA'], topJson: data['results']['TOP'], albumsJson: data['results']['ALBUMS'] ); } //Get playlist tracks at offset Future> playlistTracksPage(String id, int start, {int nb = 50}) async { Map data = await callApi('deezer.pagePlaylist', params: { 'playlist_id': id, 'lang': settings.deezerLanguage??'en', 'nb': nb, 'tags': true, 'start': start }); return data['results']['SONGS']['data'].map((json) => Track.fromPrivateJson(json)).toList(); } //Get playlist details Future playlist(String id, {int nb = 100}) async { Map data = await callApi('deezer.pagePlaylist', params: { 'playlist_id': id, 'lang': settings.deezerLanguage??'en', 'nb': nb, 'tags': true, 'start': 0 }); return Playlist.fromPrivateJson(data['results']['DATA'], songsJson: data['results']['SONGS']); } //Get playlist with all tracks Future fullPlaylist(String id) async { return await playlist(id, nb: 100000); } //Add track to favorites Future addFavoriteTrack(String id) async { await callApi('favorite_song.add', params: {'SNG_ID': id}); } //Add album to favorites/library Future addFavoriteAlbum(String id) async { await callApi('album.addFavorite', params: {'ALB_ID': id}); } //Add artist to favorites/library Future addFavoriteArtist(String id) async { await callApi('artist.addFavorite', params: {'ART_ID': id}); } //Remove artist from favorites/library Future removeArtist(String id) async { await callApi('artist.deleteFavorite', params: {'ART_ID': id}); } //Add tracks to playlist Future addToPlaylist(String trackId, String playlistId, {int offset = -1}) async { await callApi('playlist.addSongs', params: { 'offset': offset, 'playlist_id': playlistId, 'songs': [[trackId, 0]] }); } //Remove track from playlist Future removeFromPlaylist(String trackId, String playlistId) async { await callApi('playlist.deleteSongs', params: { 'playlist_id': playlistId, 'songs': [[trackId, 0]] }); } //Get users playlists Future> getPlaylists() async { Map data = await callApi('deezer.pageProfile', params: { 'nb': 100, 'tab': 'playlists', 'user_id': this.userId }); return data['results']['TAB']['playlists']['data'].map((json) => Playlist.fromPrivateJson(json, library: true)).toList(); } //Get favorite albums Future> getAlbums() async { Map data = await callApi('deezer.pageProfile', params: { 'nb': 50, 'tab': 'albums', 'user_id': this.userId }); List albumList = data['results']['TAB']['albums']['data']; List albums = albumList.map((json) => Album.fromPrivateJson(json, library: true)).toList(); return albums; } //Remove album from library Future removeAlbum(String id) async { await callApi('album.deleteFavorite', params: { 'ALB_ID': id }); } //Remove track from favorites Future removeFavorite(String id) async { await callApi('favorite_song.remove', params: { 'SNG_ID': id }); } //Get favorite artists Future> getArtists() async { Map data = await callApi('deezer.pageProfile', params: { 'nb': 40, 'tab': 'artists', 'user_id': this.userId }); return data['results']['TAB']['artists']['data'].map((json) => Artist.fromPrivateJson(json, library: true)).toList(); } //Get lyrics by track id Future lyrics(String trackId) async { Map data = await callApi('song.getLyrics', params: { 'sng_id': trackId }); if (data['error'] != null && data['error'].length > 0) return Lyrics.error(); return Lyrics.fromPrivateJson(data['results']); } Future smartTrackList(String id) async { Map data = await callApi('deezer.pageSmartTracklist', params: { 'smarttracklist_id': id }); return SmartTrackList.fromPrivateJson(data['results']['DATA'], songsJson: data['results']['SONGS']); } Future> flow() async { Map data = await callApi('radio.getUserRadio', params: { 'user_id': userId }); return data['results']['data'].map((json) => Track.fromPrivateJson(json)).toList(); } //Get homepage/music library from deezer Future homePage() async { List grid = ['album', 'artist', 'channel', 'flow', 'playlist', 'radio', 'show', 'smarttracklist', 'track', 'user']; Map data = await callApi('page.get', gatewayInput: jsonEncode({ "PAGE": "home", "VERSION": "2.3", "SUPPORT": { /* "deeplink-list": ["deeplink"], "list": ["episode"], "grid-preview-one": grid, "grid-preview-two": grid, "slideshow": grid, "message": ["call_onboarding"], */ "grid": grid, "horizontal-grid": grid, "item-highlight": ["radio"], "large-card": ["album", "playlist", "show", "video-link"], "ads": [] //Nope }, "LANG": settings.deezerLanguage??'en', "OPTIONS": [] })); return HomePage.fromPrivateJson(data['results']); } //Log song listen to deezer Future logListen(String trackId) async { await callApi('log.listen', params: { 'params': { 'timestamp': DateTime.now().millisecondsSinceEpoch, 'ts_listen': DateTime.now().millisecondsSinceEpoch, 'type': 1, 'stat': {'seek': 0, 'pause': 0, 'sync': 1}, 'media': {'id': trackId, 'type': 'song', 'format': 'MP3_128'} } }); } Future getChannel(String target) async { List grid = ['album', 'artist', 'channel', 'flow', 'playlist', 'radio', 'show', 'smarttracklist', 'track', 'user']; Map data = await callApi('page.get', gatewayInput: jsonEncode({ 'PAGE': target, "VERSION": "2.3", "SUPPORT": { /* "deeplink-list": ["deeplink"], "list": ["episode"], "grid-preview-one": grid, "grid-preview-two": grid, "slideshow": grid, "message": ["call_onboarding"], */ "grid": grid, "horizontal-grid": grid, "item-highlight": ["radio"], "large-card": ["album", "playlist", "show", "video-link"], "ads": [] //Nope }, "LANG": settings.deezerLanguage??'en', "OPTIONS": [] })); return HomePage.fromPrivateJson(data['results']); } //Add playlist to library Future addPlaylist(String id) async { await callApi('playlist.addFavorite', params: { 'parent_playlist_id': int.parse(id) }); } //Remove playlist from library Future removePlaylist(String id) async { await callApi('playlist.deleteFavorite', params: { 'playlist_id': int.parse(id) }); } //Delete playlist Future deletePlaylist(String id) async { await callApi('playlist.delete', params: { 'playlist_id': id }); } //Create playlist //Status 1 - private, 2 - collaborative Future createPlaylist(String title, {String description = "", int status = 1, List trackIds = const []}) async { Map data = await callApi('playlist.create', params: { 'title': title, 'description': description, 'songs': trackIds.map((id) => [int.parse(id), trackIds.indexOf(id)]).toList(), 'status': status }); //Return playlistId return data['results'].toString(); } //Get part of discography Future> discographyPage(String artistId, {int start = 0, int nb = 50}) async { Map data = await callApi('album.getDiscography', params: { 'art_id': int.parse(artistId), 'discography_mode': 'all', 'nb': nb, 'start': start, 'nb_songs': 30 }); return data['results']['data'].map((a) => Album.fromPrivateJson(a)).toList(); } Future searchSuggestions(String query) async { Map data = await callApi('search_getSuggestedQueries', params: { 'QUERY': query }); return data['results']['SUGGESTION'].map((s) => s['QUERY']).toList(); } //Get smart radio for artist id Future> smartRadio(String artistId) async { Map data = await callApi('smart.getSmartRadio', params: { 'art_id': int.parse(artistId) }); return data['results']['data'].map((t) => Track.fromPrivateJson(t)).toList(); } //Update playlist metadata, status = see createPlaylist Future updatePlaylist(String id, String title, String description, {int status = 1}) async { await callApi('playlist.update', params: { 'description': description, 'title': title, 'playlist_id': int.parse(id), 'status': status, 'songs': [] }); } }