diff --git a/lib/src/endpoints/endpoint_paging.dart b/lib/src/endpoints/endpoint_paging.dart index 9849071..f26e993 100644 --- a/lib/src/endpoints/endpoint_paging.dart +++ b/lib/src/endpoints/endpoint_paging.dart @@ -16,6 +16,10 @@ abstract class EndpointPaging extends EndpointBase { [String? pageKey, ParserFunction? pageContainerParser]) => CursorPages(_api, path, pageItemParser, pageKey, pageContainerParser); + MultiPage _getMultiPage( + String path, Map> itemMappers) => + MultiPage(_api, path, itemMappers); + BundledPages _getBundledPages( String path, Map> pageItemParsers, [String? pageKey, ParserFunction? pageContainerParser]) => @@ -36,10 +40,18 @@ abstract class BasePage { _container = pageContainer; } - // BasePage.fromParserMap(this._paging, Map parserMap, [Object? pageContainer]) { - // _items = _paging.itemsNative!.map((e) => parserMap[e.]) - // _container = pageContainer; - // } + BasePage.fromParserMap( + this._paging, Map> parserMap, + [Object? pageContainer]) { + _items = _paging.itemsNative!.map((item) { + var type = item["track"]["type"] as String; + if (parserMap.containsKey(type)) { + return parserMap[type]!(item); + } + throw TypeError(); + }); + _container = pageContainer; + } /// The offset-based paging object is a container for a set of objects. It /// contains a key called items (whose value is an array of the requested @@ -72,6 +84,11 @@ class Page extends BasePage { [Object? pageContainer]) : super(paging, pageItemParser, pageContainer); + Page.fromParserMap( + Paging paging, Map> parserMap, + [Object? pageContainer]) + : super.fromParserMap(paging, parserMap); + @override bool get isLast { var paging = _paging as Paging; @@ -290,24 +307,24 @@ class CursorPages extends SinglePages> } } -// class MultiPage extends SinglePages> -// with OffsetStrategy> { -// final Map> _pageMappers; +class MultiPage extends SinglePages> + with OffsetStrategy> { + final Map> _pageMappers; -// MultiPage(SpotifyApiBase api, String path, this._pageMappers) -// : super(api, path, null, null); + MultiPage(SpotifyApiBase api, String path, this._pageMappers) + : super(api, path, null, null); -// @override -// Future> getPage(int limit, [int offset = 0]) async { -// var pathDelimiter = _path.contains('?') ? '&' : '?'; -// var newPath = '$_path${pathDelimiter}limit=$limit&offset=$offset'; + @override + Future> getPage(int limit, [int offset = 0]) async { + var pathDelimiter = _path.contains('?') ? '&' : '?'; + var newPath = '$_path${pathDelimiter}limit=$limit&offset=$offset'; -// var jsonString = await _api._get(newPath); -// var map = Paging.fromJson(json.decode(jsonString)); + var jsonString = await _api._get(newPath); + var paging = Paging.fromJson(json.decode(jsonString)); -// for (var key in _pageMappers.keys) {} -// } -// } + return Page.fromParserMap(paging, _pageMappers); + } +} /// Page that allows multiple types (artist, track, episode etc.) together class BundledPages extends _Pages with OffsetStrategy>> { diff --git a/lib/src/endpoints/playlists.dart b/lib/src/endpoints/playlists.dart index f91ae54..9be058f 100644 --- a/lib/src/endpoints/playlists.dart +++ b/lib/src/endpoints/playlists.dart @@ -43,15 +43,15 @@ class Playlists extends EndpointPaging { (json) => Track.fromJson(json['track'])); } - // Pages> getItemsByPlaylistId(String playlistId, - // {Iterable filters = PlaylistFilter.values}) { - // final additionalTypes = filters.map((filter) => filter._key).join(','); - // final query = _buildQuery({'additional_types': additionalTypes}); - // return _getPages('v1/playlists/$playlistId/tracks$query', { - // 'track': (json) => Track.fromJson(json), - // 'episode': (json) => EpisodeFull.fromJson(json) - // }); - // } + MultiPage getItemsByPlaylistId(String playlistId, + {Iterable filters = PlaylistFilter.values}) { + final additionalTypes = filters.map((filter) => filter._key).join(','); + final query = _buildQuery({'additional_types': additionalTypes}); + return _getMultiPage('v1/playlists/$playlistId/tracks?$query', { + 'track': (json) => Track.fromJson(json), + 'episode': (json) => EpisodeFull.fromJson(json) + }); + } /// [userId] - the Spotify user ID /// @@ -335,8 +335,8 @@ class Playlists extends EndpointPaging { } enum PlaylistFilter { - track(key: 'album'), - episode(key: 'artist'); + track(key: 'track'), + episode(key: 'episode'); const PlaylistFilter({required String key}) : _key = key; diff --git a/test/data/v1/episodes/5Xt5DXGzch68nYYamXrNxZ.json b/test/data/v1/episodes/5Xt5DXGzch68nYYamXrNxZ.json index 1f1c5bb..716a7f6 100644 --- a/test/data/v1/episodes/5Xt5DXGzch68nYYamXrNxZ.json +++ b/test/data/v1/episodes/5Xt5DXGzch68nYYamXrNxZ.json @@ -62,7 +62,7 @@ ], "is_externally_hosted": true, "languages": [ - "string" + "en" ], "media_type": "string", "name": "The No-Show", diff --git a/test/data/v1/playlists/1XIAxOGAEK2h4ravpNTmYF/tracks.json b/test/data/v1/playlists/1XIAxOGAEK2h4ravpNTmYF/tracks.json index 88a545f..816c76b 100644 --- a/test/data/v1/playlists/1XIAxOGAEK2h4ravpNTmYF/tracks.json +++ b/test/data/v1/playlists/1XIAxOGAEK2h4ravpNTmYF/tracks.json @@ -1,122 +1,543 @@ { - "href": "https://api.spotify.com/v1/playlists/3szasmcMkQX0tPQuqtl67Y/tracks?offset=0&limit=100&locale=de,en-US;q=0.7,en;q=0.3", + "href": "https://api.spotify.com/v1/playlists/1XIAxOGAEK2h4ravpNTmYF/tracks?offset=0&limit=100&locale=de,en-US;q=0.7,en;q=0.3", "items": [ { - "added_at": "2023-11-29T11:20:25Z", + "added_at": "2023-11-10T16:22:15Z", "added_by": { "external_urls": { - "spotify": "https://open.spotify.com/user/hayribakici" + "spotify": "https://open.spotify.com/user/kriegermusik" }, - "href": "https://api.spotify.com/v1/users/hayribakici", - "id": "hayribakici", + "href": "https://api.spotify.com/v1/users/kriegermusik", + "id": "kriegermusik", "type": "user", - "uri": "spotify:user:hayribakici" + "uri": "spotify:user:kriegermusik" }, "is_local": false, "primary_color": null, "track": { "album": { - "album_type": "album", + "album_type": "single", "artists": [ { "external_urls": { - "spotify": "https://open.spotify.com/artist/24eDfi2MSYo3A87hCcgpIL" + "spotify": "https://open.spotify.com/artist/4iBwchw0U0GZv5RfVYSMxN" }, - "href": "https://api.spotify.com/v1/artists/24eDfi2MSYo3A87hCcgpIL", - "id": "24eDfi2MSYo3A87hCcgpIL", - "name": "Ludwig Göransson", + "href": "https://api.spotify.com/v1/artists/4iBwchw0U0GZv5RfVYSMxN", + "id": "4iBwchw0U0GZv5RfVYSMxN", + "name": "Anyma", "type": "artist", - "uri": "spotify:artist:24eDfi2MSYo3A87hCcgpIL" + "uri": "spotify:artist:4iBwchw0U0GZv5RfVYSMxN" + }, + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/1NaQOKgddaJipUtmptb7GI" + }, + "href": "https://api.spotify.com/v1/artists/1NaQOKgddaJipUtmptb7GI", + "id": "1NaQOKgddaJipUtmptb7GI", + "name": "Argy", + "type": "artist", + "uri": "spotify:artist:1NaQOKgddaJipUtmptb7GI" + }, + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/4pOglqMAavrWFo20ORRx5w" + }, + "href": "https://api.spotify.com/v1/artists/4pOglqMAavrWFo20ORRx5w", + "id": "4pOglqMAavrWFo20ORRx5w", + "name": "MAGNUS", + "type": "artist", + "uri": "spotify:artist:4pOglqMAavrWFo20ORRx5w" } ], "available_markets": [ - "AR" + "AR", + "AU", + "AT", + "BE", + "BO", + "BR", + "BG", + "CA", + "CL", + "CO", + "CR", + "CY", + "CZ", + "DK", + "DO", + "DE", + "EC", + "EE", + "SV", + "FI", + "FR", + "GR", + "GT", + "HN", + "HK", + "HU", + "IS", + "IE", + "IT", + "LV", + "LT", + "LU", + "MY", + "MT", + "MX", + "NL", + "NZ", + "NI", + "NO", + "PA", + "PY", + "PE", + "PH", + "PL", + "PT", + "SG", + "SK", + "ES", + "SE", + "CH", + "TW", + "TR", + "UY", + "US", + "GB", + "AD", + "LI", + "MC", + "ID", + "JP", + "TH", + "VN", + "RO", + "IL", + "ZA", + "SA", + "AE", + "BH", + "QA", + "OM", + "KW", + "EG", + "MA", + "DZ", + "TN", + "LB", + "JO", + "PS", + "IN", + "KZ", + "MD", + "UA", + "AL", + "BA", + "HR", + "ME", + "MK", + "RS", + "SI", + "KR", + "BD", + "PK", + "LK", + "GH", + "KE", + "NG", + "TZ", + "UG", + "AG", + "AM", + "BS", + "BB", + "BZ", + "BT", + "BW", + "BF", + "CV", + "CW", + "DM", + "FJ", + "GM", + "GE", + "GD", + "GW", + "GY", + "HT", + "JM", + "KI", + "LS", + "LR", + "MW", + "MV", + "ML", + "MH", + "FM", + "NA", + "NR", + "NE", + "PW", + "PG", + "WS", + "SM", + "ST", + "SN", + "SC", + "SL", + "SB", + "KN", + "LC", + "VC", + "SR", + "TL", + "TO", + "TT", + "TV", + "VU", + "AZ", + "BN", + "BI", + "KH", + "CM", + "TD", + "KM", + "GQ", + "SZ", + "GA", + "GN", + "KG", + "LA", + "MO", + "MR", + "MN", + "NP", + "RW", + "TG", + "UZ", + "ZW", + "BJ", + "MG", + "MU", + "MZ", + "AO", + "CI", + "DJ", + "ZM", + "CD", + "CG", + "IQ", + "LY", + "TJ", + "VE", + "ET", + "XK" ], "external_urls": { - "spotify": "https://open.spotify.com/album/0rwbMKjNkp4ehQTwf9V2Jk" + "spotify": "https://open.spotify.com/album/5t81OipNDNgEJ8MR5RVwFU" }, - "href": "https://api.spotify.com/v1/albums/0rwbMKjNkp4ehQTwf9V2Jk", - "id": "0rwbMKjNkp4ehQTwf9V2Jk", + "href": "https://api.spotify.com/v1/albums/5t81OipNDNgEJ8MR5RVwFU", + "id": "5t81OipNDNgEJ8MR5RVwFU", "images": [ { "height": 640, - "url": "https://i.scdn.co/image/ab67616d0000b273af634982d9b15de3c77f7dd9", + "url": "https://i.scdn.co/image/ab67616d0000b273bb2261fd25a20d1d72e96913", "width": 640 }, { "height": 300, - "url": "https://i.scdn.co/image/ab67616d00001e02af634982d9b15de3c77f7dd9", + "url": "https://i.scdn.co/image/ab67616d00001e02bb2261fd25a20d1d72e96913", "width": 300 }, { "height": 64, - "url": "https://i.scdn.co/image/ab67616d00004851af634982d9b15de3c77f7dd9", + "url": "https://i.scdn.co/image/ab67616d00004851bb2261fd25a20d1d72e96913", "width": 64 } ], - "name": "Oppenheimer", - "release_date": "2023-07-21", + "name": "Higher Power", + "release_date": "2023-11-10", "release_date_precision": "day", - "total_tracks": 24, + "total_tracks": 1, "type": "album", - "uri": "spotify:album:0rwbMKjNkp4ehQTwf9V2Jk" + "uri": "spotify:album:5t81OipNDNgEJ8MR5RVwFU" }, "artists": [ { "external_urls": { - "spotify": "https://open.spotify.com/artist/24eDfi2MSYo3A87hCcgpIL" + "spotify": "https://open.spotify.com/artist/4iBwchw0U0GZv5RfVYSMxN" }, - "href": "https://api.spotify.com/v1/artists/24eDfi2MSYo3A87hCcgpIL", - "id": "24eDfi2MSYo3A87hCcgpIL", - "name": "Ludwig Göransson", + "href": "https://api.spotify.com/v1/artists/4iBwchw0U0GZv5RfVYSMxN", + "id": "4iBwchw0U0GZv5RfVYSMxN", + "name": "Anyma", "type": "artist", - "uri": "spotify:artist:24eDfi2MSYo3A87hCcgpIL" + "uri": "spotify:artist:4iBwchw0U0GZv5RfVYSMxN" + }, + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/1NaQOKgddaJipUtmptb7GI" + }, + "href": "https://api.spotify.com/v1/artists/1NaQOKgddaJipUtmptb7GI", + "id": "1NaQOKgddaJipUtmptb7GI", + "name": "Argy", + "type": "artist", + "uri": "spotify:artist:1NaQOKgddaJipUtmptb7GI" + }, + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/4pOglqMAavrWFo20ORRx5w" + }, + "href": "https://api.spotify.com/v1/artists/4pOglqMAavrWFo20ORRx5w", + "id": "4pOglqMAavrWFo20ORRx5w", + "name": "MAGNUS", + "type": "artist", + "uri": "spotify:artist:4pOglqMAavrWFo20ORRx5w" } ], "available_markets": [ - "AR" + "AR", + "AU", + "AT", + "BE", + "BO", + "BR", + "BG", + "CA", + "CL", + "CO", + "CR", + "CY", + "CZ", + "DK", + "DO", + "DE", + "EC", + "EE", + "SV", + "FI", + "FR", + "GR", + "GT", + "HN", + "HK", + "HU", + "IS", + "IE", + "IT", + "LV", + "LT", + "LU", + "MY", + "MT", + "MX", + "NL", + "NZ", + "NI", + "NO", + "PA", + "PY", + "PE", + "PH", + "PL", + "PT", + "SG", + "SK", + "ES", + "SE", + "CH", + "TW", + "TR", + "UY", + "US", + "GB", + "AD", + "LI", + "MC", + "ID", + "JP", + "TH", + "VN", + "RO", + "IL", + "ZA", + "SA", + "AE", + "BH", + "QA", + "OM", + "KW", + "EG", + "MA", + "DZ", + "TN", + "LB", + "JO", + "PS", + "IN", + "KZ", + "MD", + "UA", + "AL", + "BA", + "HR", + "ME", + "MK", + "RS", + "SI", + "KR", + "BD", + "PK", + "LK", + "GH", + "KE", + "NG", + "TZ", + "UG", + "AG", + "AM", + "BS", + "BB", + "BZ", + "BT", + "BW", + "BF", + "CV", + "CW", + "DM", + "FJ", + "GM", + "GE", + "GD", + "GW", + "GY", + "HT", + "JM", + "KI", + "LS", + "LR", + "MW", + "MV", + "ML", + "MH", + "FM", + "NA", + "NR", + "NE", + "PW", + "PG", + "WS", + "SM", + "ST", + "SN", + "SC", + "SL", + "SB", + "KN", + "LC", + "VC", + "SR", + "TL", + "TO", + "TT", + "TV", + "VU", + "AZ", + "BN", + "BI", + "KH", + "CM", + "TD", + "KM", + "GQ", + "SZ", + "GA", + "GN", + "KG", + "LA", + "MO", + "MR", + "MN", + "NP", + "RW", + "TG", + "UZ", + "ZW", + "BJ", + "MG", + "MU", + "MZ", + "AO", + "CI", + "DJ", + "ZM", + "CD", + "CG", + "IQ", + "LY", + "TJ", + "VE", + "ET", + "XK" ], "disc_number": 1, - "duration_ms": 110160, + "duration_ms": 208601, "episode": false, "explicit": false, "external_ids": { - "isrc": "USQ4E2204391" + "isrc": "USUG12308415" }, "external_urls": { - "spotify": "https://open.spotify.com/track/4VnDmjYCZkyeqeb0NIKqdA" + "spotify": "https://open.spotify.com/track/7mQAjg2u5udWhnQ07jIHI7" }, - "href": "https://api.spotify.com/v1/tracks/4VnDmjYCZkyeqeb0NIKqdA", - "id": "4VnDmjYCZkyeqeb0NIKqdA", + "href": "https://api.spotify.com/v1/tracks/7mQAjg2u5udWhnQ07jIHI7", + "id": "7mQAjg2u5udWhnQ07jIHI7", "is_local": false, - "name": "Can You Hear The Music", - "popularity": 76, - "preview_url": "https://p.scdn.co/mp3-preview/fb4c33bf9ac3841c29fc7447533e1ae1156295dd?cid=a51c360ccea644af8e2a0b1baad8a878", + "name": "Higher Power", + "popularity": 66, + "preview_url": "https://p.scdn.co/mp3-preview/b3f3253471625288bb921640285b0bd1924765a3?cid=a51c360ccea644af8e2a0b1baad8a878", "track": true, - "track_number": 2, + "track_number": 1, "type": "track", - "uri": "spotify:track:4VnDmjYCZkyeqeb0NIKqdA" + "uri": "spotify:track:7mQAjg2u5udWhnQ07jIHI7" + }, + "video_thumbnail": { + "url": null + } + }, + { + "added_at": "2023-11-10T16:22:34Z", + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/kriegermusik" + }, + "href": "https://api.spotify.com/v1/users/kriegermusik", + "id": "kriegermusik", + "type": "user", + "uri": "spotify:user:kriegermusik" }, - "episode": { + "is_local": false, + "primary_color": null, + "track": { "audio_preview_url": "https://p.scdn.co/mp3-preview/2f37da1d4221f40b9d1a98cd191f4d6f1646ad17", - "description": "A Spotify podcast.", - "html_description": "

A Spotify podcast.

", + "description": "A Spotify podcast sharing fresh insights on important topics of the moment—in a way only Spotify can. You’ll hear from experts in the music, podcast and tech industries as we discover and uncover stories about our work and the world around us.", + "html_description": "

A Spotify podcast sharing fresh insights on important topics of the moment—in a way only Spotify can. You’ll hear from experts in the music, podcast and tech industries as we discover and uncover stories about our work and the world around us.

\n", "duration_ms": 1686230, - "explicit": false, + "explicit": true, "external_urls": { - "spotify": "https://open.spotify.com/track/4VnDmjYCZkyeqeb0NIKqdA" + "spotify": "string" }, "href": "https://api.spotify.com/v1/episodes/5Xt5DXGzch68nYYamXrNxZ", "id": "5Xt5DXGzch68nYYamXrNxZ", "images": [ { - "url": "https://i.scdn.co/image/ab67616d00001e02ff9ca10b55ce82ae553c8228", + "url": "https://i.scdn.co/image/ab67616d00001e02ff9ca10b55ce82ae553c8228\n", "height": 300, "width": 300 } ], - "is_externally_hosted": false, - "is_playable": false, + "is_externally_hosted": true, + "is_playable": true, "language": "en", "languages": [ "fr", @@ -126,41 +547,51 @@ "release_date": "1981-12-15", "release_date_precision": "day", "resume_point": { - "fully_played": false, + "fully_played": true, "resume_position_ms": 0 }, "type": "episode", "uri": "spotify:episode:0zLhl3WsOCQHbe1BPTiHgr", + "restrictions": { + "reason": "string" + }, "show": { "available_markets": [ - "AR" + "US" + ], + "copyrights": [ + { + "text": "No rights", + "type": "C" + } ], - "description": "string", + "description": "hi", "html_description": "string", - "explicit": false, + "explicit": true, "external_urls": { - "spotify": "https://open.spotify.com/track/4VnDmjYCZkyeqeb0NIKqdA" + "spotify": "string" }, "href": "string", "id": "string", "images": [ { - "url": "https://i.scdn.co/image/ab67616d00001e02ff9ca10b55ce82ae553c8228", + "url": "https://i.scdn.co/image/ab67616d00001e02ff9ca10b55ce82ae553c8228\n", "height": 300, "width": 300 } ], - "is_externally_hosted": false, + "is_externally_hosted": true, "languages": [ "en" ], "media_type": "string", - "name": "string", + "name": "The No-Show", "publisher": "string", "type": "show", "uri": "string", - "total_episodes": 0 + "total_episodes": 1 } + }, "video_thumbnail": { "url": null @@ -168,8 +599,8 @@ } ], "limit": 100, - "next": null, + "next": "https://api.spotify.com/v1/playlists/1XIAxOGAEK2h4ravpNTmYF/tracks?offset=100&limit=100&locale=de,en-US;q=0.7,en;q=0.3", "offset": 0, "previous": null, - "total": 1 + "total": 2 } \ No newline at end of file diff --git a/test/spotify_test.dart b/test/spotify_test.dart index ef7b074..f67876b 100644 --- a/test/spotify_test.dart +++ b/test/spotify_test.dart @@ -196,17 +196,18 @@ Future main() async { expect(firstImage.width, 300); }); - // test('getPlaylistItems', () async { - // var items = - // spotify.playlists.getItemsByPlaylistId('1XIAxOGAEK2h4ravpNTmYF'); - // expect(items, isNotNull); - - // var first = await items.first(); - // for (var item in first) { - // print(item); - // } - // // print((await items.first()).); - // }); + test('getPlaylistItems', () async { + var items = + spotify.playlists.getItemsByPlaylistId('1XIAxOGAEK2h4ravpNTmYF'); + expect(items, isNotNull); + + var first = (await items.first()).items; + expect(first, isNotNull); + expect(first, isNotEmpty); + expect(first?.length, 2); + expect(first?.first?.runtimeType, Track); + expect(first?.last?.runtimeType, EpisodeFull); + }); test('followedByUsers', () async { var result = await spotify.playlists