From 441528636ec26b431cb14d085bd67e1a3e2776a3 Mon Sep 17 00:00:00 2001 From: xonstone <17579331+xonstone@users.noreply.github.com> Date: Thu, 19 May 2022 21:03:01 +0200 Subject: [PATCH 1/9] Add GetPlaylistItems --- playlist.go | 78 ++ playlist_test.go | 58 + request_options.go | 23 + test_data/playlist_items_episodes.json | 989 +++++++++++++++ test_data/playlist_items_tracks.json | 1556 ++++++++++++++++++++++++ 5 files changed, 2704 insertions(+) create mode 100644 test_data/playlist_items_episodes.json create mode 100644 test_data/playlist_items_tracks.json diff --git a/playlist.go b/playlist.go index ea4be90..7039e46 100644 --- a/playlist.go +++ b/playlist.go @@ -190,6 +190,84 @@ func (c *Client) GetPlaylistTracks( return &result, err } +// PlaylistItem contains info about an item in a playlist. +type PlaylistItem struct { + // The date and time the track was added to the playlist. + // You can use the TimestampLayout constant to convert + // this field to a time.Time value. + // Warning: very old playlists may not populate this value. + AddedAt string `json:"added_at"` + // The Spotify user who added the track to the playlist. + // Warning: vary old playlists may not populate this value. + AddedBy User `json:"added_by"` + // Whether this track is a local file or not. + IsLocal bool `json:"is_local"` + // Information about the track. + Track PlaylistItemTrack `json:"track"` +} + +type PlaylistItemTrack struct { + *FullTrack + Episode *EpisodePage +} + +func (t *PlaylistItemTrack) UnmarshalJSON(b []byte) error { + is := struct { + Episode bool `json:"episode"` + Track bool `json:"track"` + }{} + + err := json.Unmarshal(b, &is) + if err != nil { + return err + } + + if is.Episode { + err := json.Unmarshal(b, &t.Episode) + if err != nil { + return err + } + } + + if is.Track { + err := json.Unmarshal(b, &t.FullTrack) + if err != nil { + return err + } + } + + return nil +} + +// PlaylistItemPage contains information about tracks in a playlist. +type PlaylistItemPage struct { + basePage + Items []PlaylistItem `json:"items"` +} + +// GetPlaylistItems gets full details of the items in a playlist, given the +// playlist's Spotify ID. +// +// Supported options: Limit, Offset, Market, Fields +func (c *Client) GetPlaylistItems(ctx context.Context, playlistID ID, opts ...RequestOption) (*PlaylistItemPage, error) { + spotifyURL := fmt.Sprintf("%splaylists/%s/tracks", c.baseURL, playlistID) + + opts = append(opts, additionalTypes(episodeAdditionalType, trackAdditionalType)) + + if params := processOptions(opts...).urlParams.Encode(); params != "" { + spotifyURL += "?" + params + } + + var result PlaylistItemPage + + err := c.get(ctx, spotifyURL, &result) + if err != nil { + return nil, err + } + + return &result, err +} + // CreatePlaylistForUser creates a playlist for a Spotify user. // The playlist will be empty until you add tracks to it. // The playlistName does not need to be unique - a user can have diff --git a/playlist_test.go b/playlist_test.go index 0db2207..23b54f4 100644 --- a/playlist_test.go +++ b/playlist_test.go @@ -141,6 +141,64 @@ func TestGetPlaylistTracks(t *testing.T) { } } +func TestGetPlaylistItemsEpisodes(t *testing.T) { + client, server := testClientFile(http.StatusOK, "test_data/playlist_items_episodes.json") + defer server.Close() + + tracks, err := client.GetPlaylistItems(context.Background(), "playlistID") + if err != nil { + t.Error(err) + } + if tracks.Total != 4 { + t.Errorf("Got %d tracks, expected 47\n", tracks.Total) + } + if len(tracks.Items) == 0 { + t.Fatal("No tracks returned") + } + expected := "112: Dirty Coms" + actual := tracks.Items[0].Track.Episode.Name + if expected != actual { + t.Errorf("Got '%s', expected '%s'\n", actual, expected) + } + added := tracks.Items[0].AddedAt + tm, err := time.Parse(TimestampLayout, added) + if err != nil { + t.Error(err) + } + if f := tm.Format(DateLayout); f != "2022-03-22" { + t.Errorf("Expected added at 2014-11-25, got %s\n", f) + } +} + +func TestGetPlaylistItemsTracks(t *testing.T) { + client, server := testClientFile(http.StatusOK, "test_data/playlist_items_tracks.json") + defer server.Close() + + tracks, err := client.GetPlaylistItems(context.Background(), "playlistID") + if err != nil { + t.Error(err) + } + if tracks.Total != 4 { + t.Errorf("Got %d tracks, expected 47\n", tracks.Total) + } + if len(tracks.Items) == 0 { + t.Fatal("No tracks returned") + } + expected := "112: Dirty Coms" + actual := tracks.Items[0].Track.Name + if expected != actual { + t.Errorf("Got '%s', expected '%s'\n", actual, expected) + } + added := tracks.Items[0].AddedAt + tm, err := time.Parse(TimestampLayout, added) + if err != nil { + t.Error(err) + } + if f := tm.Format(DateLayout); f != "2022-03-22" { + t.Errorf("Expected added at 2014-11-25, got %s\n", f) + } +} + func TestUserFollowsPlaylist(t *testing.T) { client, server := testClientString(http.StatusOK, `[ true, false ]`) defer server.Close() diff --git a/request_options.go b/request_options.go index 8da92fa..3fb7251 100644 --- a/request_options.go +++ b/request_options.go @@ -3,6 +3,7 @@ package spotify import ( "net/url" "strconv" + "strings" ) type RequestOption func(*requestOptions) @@ -110,6 +111,28 @@ func Timerange(timerange Range) RequestOption { } } +type additionalType string + +const ( + episodeAdditionalType = "episode" + trackAdditionalType = "track" +) + +// additionalTypes is a list of item types that your client supports besides the default track type. +// Valid types are: episodeAdditionalType and trackAdditionalType. +func additionalTypes(types ...additionalType) RequestOption { + strTypes := make([]string, len(types)) + for i, t := range types { + strTypes[i] = string(t) + } + + csv := strings.Join(strTypes, ",") + + return func(o *requestOptions) { + o.urlParams.Set("additional_types", csv) + } +} + func processOptions(options ...RequestOption) requestOptions { o := requestOptions{ urlParams: url.Values{}, diff --git a/test_data/playlist_items_episodes.json b/test_data/playlist_items_episodes.json new file mode 100644 index 0000000..dbd81e7 --- /dev/null +++ b/test_data/playlist_items_episodes.json @@ -0,0 +1,989 @@ +{ + "href": "https://api.spotify.com/v1/playlists/23Ay9WfURne0LvtlCvYTkj/tracks?offset=0\u0026limit=100\u0026additional_types=episode", + "items": [ + { + "added_at": "2022-03-22T17:17:01Z", + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/randomUser" + }, + "href": "https://api.spotify.com/v1/users/randomUser", + "id": "randomUser", + "type": "user", + "uri": "spotify:user:randomUser" + }, + "is_local": false, + "primary_color": null, + "track": { + "audio_preview_url": "https://p.scdn.co/mp3-preview/58e56ec5a44a886fc17def91ed22dd8b60f6a22d", + "description": "This episode we talk with a guy named “Drew” who gives us a rare peek into what some of the young hackers are up to today. From listening to Drew, we can see that times are changing for the motive behind hacking. In the ’90s and ’00s it was done for fun and curiosity. In the ’10s Anonymous showed us what Hacktivism is. And now, in the ’20s, the young hackers seem to be profit driven. Sponsors Support for this show comes from Linode. Linode supplies you with virtual servers. Visit linode.com/darknet and get a special offer. Support for this show comes from Juniper Networks. Juniper Networks is dedicated to simplifying network operations and driving superior experiences for end users. Visit juniper.net/darknet to learn more about how Juniper Secure Edge can help you keep your remote workforce seamlessly secure wherever they are.", + "duration_ms": 5303484, + "episode": true, + "explicit": true, + "external_urls": { + "spotify": "https://open.spotify.com/episode/5Snektk5Z2nUzM7DgoEBSx" + }, + "href": "https://api.spotify.com/v1/episodes/5Snektk5Z2nUzM7DgoEBSx", + "html_description": "\u003cp\u003eThis episode we talk with a guy named “Drew” who gives us a rare peek into what some of the young hackers are up to today. From listening to Drew, we can see that times are changing for the motive behind hacking. In the ’90s and ’00s it was done for fun and curiosity. In the ’10s Anonymous showed us what Hacktivism is. And now, in the ’20s, the young hackers seem to be profit driven.\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003eSponsors\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003eSupport for this show comes from Linode. Linode supplies you with virtual servers. Visit linode.com/darknet and get a special offer.\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003eSupport for this show comes from Juniper Networks. Juniper Networks is dedicated to simplifying network operations and driving superior experiences for end users. Visit juniper.net/darknet to learn more about how Juniper Secure Edge can help you keep your remote workforce seamlessly secure wherever they are.\u003c/p\u003e", + "id": "5Snektk5Z2nUzM7DgoEBSx", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab6765630000ba8ab6ef277db35cf2d282bb16bf", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67656300005f1fb6ef277db35cf2d282bb16bf", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab6765630000f68db6ef277db35cf2d282bb16bf", + "width": 64 + } + ], + "is_externally_hosted": false, + "is_playable": true, + "language": "en-US", + "languages": [ + "en-US" + ], + "name": "112: Dirty Coms", + "release_date": "2022-03-08", + "release_date_precision": "day", + "resume_point": { + "fully_played": false, + "resume_position_ms": 225000 + }, + "show": { + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BZ", + "CA", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KH", + "KI", + "KM", + "KN", + "KW", + "LA", + "LB", + "LC", + "LI", + "LR", + "LS", + "LT", + "LU", + "LV", + "MA", + "MC", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "US", + "UY", + "UZ", + "VC", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "copyrights": [], + "description": "Explore true stories of the dark side of the Internet with host Jack Rhysider as he takes you on a journey through the chilling world of hacking, data breaches, and cyber crime.", + "explicit": false, + "external_urls": { + "spotify": "https://open.spotify.com/show/4XPl3uEEL9hvqMkoZrzbx5" + }, + "href": "https://api.spotify.com/v1/shows/4XPl3uEEL9hvqMkoZrzbx5", + "html_description": null, + "id": "4XPl3uEEL9hvqMkoZrzbx5", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab6765630000ba8a11874ad24c1dcac2ace8d4c9", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67656300005f1f11874ad24c1dcac2ace8d4c9", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab6765630000f68d11874ad24c1dcac2ace8d4c9", + "width": 64 + } + ], + "is_externally_hosted": false, + "languages": [ + "en" + ], + "media_type": "audio", + "name": "Darknet Diaries", + "publisher": "Jack Rhysider", + "total_episodes": 117, + "type": "show", + "uri": "spotify:show:4XPl3uEEL9hvqMkoZrzbx5" + }, + "track": false, + "type": "episode", + "uri": "spotify:episode:5Snektk5Z2nUzM7DgoEBSx" + }, + "video_thumbnail": { + "url": null + } + }, + { + "added_at": "2022-03-22T17:17:28Z", + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/randomUser" + }, + "href": "https://api.spotify.com/v1/users/randomUser", + "id": "randomUser", + "type": "user", + "uri": "spotify:user:randomUser" + }, + "is_local": false, + "primary_color": null, + "track": { + "audio_preview_url": null, + "description": "Not all is as it seems with Dr Ruja's revolutionary cryptocurrency, OneCoin. The Missing Cryptoqueen is an eight-part series for BBC Sounds, with new episodes every Thursday. Presenter: Jamie Bartlett Producer: Georgia Catt Story consultant: Chris Berube Editor: Philip Sellars Original music and sound design: Phil Channell Original music and vocals: Dessislava Stefanova and the London Bulgarian Choir", + "duration_ms": 2129000, + "episode": true, + "explicit": true, + "external_urls": { + "spotify": "https://open.spotify.com/episode/1WZevjwFcULC8HWySuaB3z" + }, + "href": "https://api.spotify.com/v1/episodes/1WZevjwFcULC8HWySuaB3z", + "html_description": "Not all is as it seems with Dr Ruja\u0026#39;s revolutionary cryptocurrency, OneCoin.\u003cbr/\u003eThe Missing Cryptoqueen is an eight-part series for BBC Sounds, with new episodes every Thursday.\u003cbr/\u003ePresenter: Jamie Bartlett\u003cbr/\u003eProducer: Georgia Catt\u003cbr/\u003eStory consultant: Chris Berube \u003cbr/\u003eEditor: Philip Sellars \u003cbr/\u003eOriginal music and sound design: Phil Channell\u003cbr/\u003eOriginal music and vocals: Dessislava Stefanova and the London Bulgarian Choir", + "id": "1WZevjwFcULC8HWySuaB3z", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/b9f9e946ea8bd1955c57d5d391ff1cc728aa8963", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/5493f2c236bf26699470e24b01696e5ea00e8a31", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/028f030786a5778a4a52f5966ae2817de2265823", + "width": 64 + } + ], + "is_externally_hosted": true, + "is_playable": true, + "language": "en", + "languages": [ + "en" + ], + "name": "Episode 2: The Bitcoin Killer", + "release_date": "2019-09-19", + "release_date_precision": "day", + "resume_point": { + "fully_played": false, + "resume_position_ms": 168000 + }, + "show": { + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BZ", + "CA", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KH", + "KI", + "KM", + "KN", + "KW", + "LA", + "LB", + "LC", + "LI", + "LR", + "LS", + "LT", + "LU", + "LV", + "MA", + "MC", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "US", + "UY", + "UZ", + "VC", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "copyrights": [], + "description": "Dr Ruja Ignatova persuaded millions to join her financial revolution. Then she disappeared. Why? Jamie Bartlett presents a story of greed, deceit and herd madness.", + "explicit": false, + "external_urls": { + "spotify": "https://open.spotify.com/show/5nk7d9MLCgE3M47mXPW7MP" + }, + "href": "https://api.spotify.com/v1/shows/5nk7d9MLCgE3M47mXPW7MP", + "html_description": null, + "id": "5nk7d9MLCgE3M47mXPW7MP", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/b9f9e946ea8bd1955c57d5d391ff1cc728aa8963", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/5493f2c236bf26699470e24b01696e5ea00e8a31", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/028f030786a5778a4a52f5966ae2817de2265823", + "width": 64 + } + ], + "is_externally_hosted": true, + "languages": [ + "en" + ], + "media_type": "audio", + "name": "The Missing Cryptoqueen", + "publisher": "BBC Radio 5 live", + "total_episodes": 12, + "type": "show", + "uri": "spotify:show:5nk7d9MLCgE3M47mXPW7MP" + }, + "track": false, + "type": "episode", + "uri": "spotify:episode:1WZevjwFcULC8HWySuaB3z" + }, + "video_thumbnail": { + "url": null + } + }, + { + "added_at": "2022-03-22T17:18:21Z", + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/randomUser" + }, + "href": "https://api.spotify.com/v1/users/randomUser", + "id": "randomUser", + "type": "user", + "uri": "spotify:user:randomUser" + }, + "is_local": false, + "primary_color": null, + "track": { + "audio_preview_url": "https://p.scdn.co/mp3-preview/c1c7d757eb6bd6415824720918d267390a54156d", + "description": "Logic bombs rarely have warning sounds. The victims mostly don’t know to expect one. And even when a logic bomb is discovered before it’s triggered, there isn’t always enough time to defuse it. But there are ways to stop them in time. Paul Ducklin recounts the race to defuse the CIH logic bomb—and the horrible realization of how widespread it was. Costin Raiu explains how logic bombs get planted, and all the different kinds of damage they can do. And Manuel Egele shares some strategies for detecting logic bombs before their conditions are met.If you want to read up on some of our research on logic bombs, you can check out all our bonus material over at redhat.com/commandlineheroes. Follow along with the episode transcript.  ", + "duration_ms": 1304111, + "episode": true, + "explicit": false, + "external_urls": { + "spotify": "https://open.spotify.com/episode/30HqApDClX0SFE53KJ7wvr" + }, + "href": "https://api.spotify.com/v1/episodes/30HqApDClX0SFE53KJ7wvr", + "html_description": "\u003cp\u003eLogic bombs rarely have warning sounds. The victims mostly don’t know to expect one. And even when a logic bomb is discovered before it’s triggered, there isn’t always enough time to defuse it. But there are ways to stop them in time. \u003c/p\u003e\u003cp\u003ePaul Ducklin recounts the race to defuse the CIH logic bomb—and the horrible realization of how widespread it was. Costin Raiu explains how logic bombs get planted, and all the different kinds of damage they can do. And Manuel Egele shares some strategies for detecting logic bombs before their conditions are met.\u003c/p\u003e\u003cp\u003eIf you want to read up on some of our research on logic bombs, you can check out all our bonus material over at \u003ca href=\"https://www.redhat.com/en/command-line-heroes/season-9/logic-bombs\" rel=\"nofollow\"\u003eredhat.com/commandlineheroes\u003c/a\u003e. Follow along with the episode transcript. \u003c/p\u003e\u003cp\u003e \u003c/p\u003e", + "id": "30HqApDClX0SFE53KJ7wvr", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab6765630000ba8af7dfa6a2e2caac2387c846d0", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67656300005f1ff7dfa6a2e2caac2387c846d0", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab6765630000f68df7dfa6a2e2caac2387c846d0", + "width": 64 + } + ], + "is_externally_hosted": false, + "is_playable": true, + "language": "en", + "languages": [ + "en" + ], + "name": "Lurking Logic Bombs", + "release_date": "2022-03-22", + "release_date_precision": "day", + "resume_point": { + "fully_played": false, + "resume_position_ms": 0 + }, + "show": { + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BZ", + "CA", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KH", + "KI", + "KM", + "KN", + "KW", + "LA", + "LB", + "LC", + "LI", + "LR", + "LS", + "LT", + "LU", + "LV", + "MA", + "MC", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "US", + "UY", + "UZ", + "VC", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "copyrights": [], + "description": "Stories about the people transforming technology from the command line up.", + "explicit": false, + "external_urls": { + "spotify": "https://open.spotify.com/show/4Jgtgr4mHXNDyLldHkfEMz" + }, + "href": "https://api.spotify.com/v1/shows/4Jgtgr4mHXNDyLldHkfEMz", + "html_description": null, + "id": "4Jgtgr4mHXNDyLldHkfEMz", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab6765630000ba8ad5dff299e9c86cd6126fea8f", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67656300005f1fd5dff299e9c86cd6126fea8f", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab6765630000f68dd5dff299e9c86cd6126fea8f", + "width": 64 + } + ], + "is_externally_hosted": false, + "languages": [ + "en" + ], + "media_type": "audio", + "name": "Command Line Heroes", + "publisher": "Red Hat", + "total_episodes": 76, + "type": "show", + "uri": "spotify:show:4Jgtgr4mHXNDyLldHkfEMz" + }, + "track": false, + "type": "episode", + "uri": "spotify:episode:30HqApDClX0SFE53KJ7wvr" + }, + "video_thumbnail": { + "url": null + } + }, + { + "added_at": "2022-03-22T17:19:02Z", + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/randomUser" + }, + "href": "https://api.spotify.com/v1/users/randomUser", + "id": "randomUser", + "type": "user", + "uri": "spotify:user:randomUser" + }, + "is_local": false, + "primary_color": null, + "track": { + "audio_preview_url": "https://p.scdn.co/mp3-preview/aedf7273252dd8a752b56cfbde51a97104f27bf3", + "description": "Three stories about how the sanctions imposed on Russia are playing out – for regular Russian people, for Russia's super-rich, and for Russia's energy exports. | Subscribe to our weekly newsletter here.", + "duration_ms": 1101531, + "episode": true, + "explicit": false, + "external_urls": { + "spotify": "https://open.spotify.com/episode/6Br4mVPbe9c5jvIddrnkOg" + }, + "href": "https://api.spotify.com/v1/episodes/6Br4mVPbe9c5jvIddrnkOg", + "html_description": "Three stories about how the sanctions imposed on Russia are playing out – for regular Russian people, for Russia\u0026#39;s super-rich, and for Russia\u0026#39;s energy exports. | Subscribe to our weekly newsletter \u003ca href=\"https://www.npr.org/newsletter/money?utm_source\u0026#61;rss_feed_copy\u0026amp;utm_medium\u0026#61;podcast\u0026amp;utm_term\u0026#61;planet_money\" rel=\"nofollow\"\u003ehere\u003c/a\u003e.", + "id": "6Br4mVPbe9c5jvIddrnkOg", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab6765630000ba8af12f52c396e196b106772127", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67656300005f1ff12f52c396e196b106772127", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab6765630000f68df12f52c396e196b106772127", + "width": 64 + } + ], + "is_externally_hosted": false, + "is_playable": true, + "language": "en", + "languages": [ + "en" + ], + "name": "Of oligarchs, oil and rubles", + "release_date": "2022-03-05", + "release_date_precision": "day", + "resume_point": { + "fully_played": false, + "resume_position_ms": 0 + }, + "show": { + "available_markets": [ + "AD", + "AR", + "AT", + "AU", + "BE", + "BG", + "BO", + "BR", + "CA", + "CH", + "CL", + "CO", + "CR", + "CY", + "CZ", + "DE", + "DK", + "DO", + "EC", + "EE", + "ES", + "FI", + "FR", + "GB", + "GR", + "GT", + "HK", + "HN", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JP", + "LI", + "LT", + "LU", + "LV", + "MC", + "MT", + "MX", + "MY", + "NI", + "NL", + "NO", + "NZ", + "PA", + "PE", + "PH", + "PL", + "PT", + "PY", + "RO", + "SE", + "SG", + "SK", + "SV", + "TH", + "TR", + "TW", + "US", + "UY", + "VN", + "ZA" + ], + "copyrights": [], + "description": "The economy explained. Imagine you could call up a friend and say, \"Meet me at the bar and tell me what's going on with the economy.\" Now imagine that's actually a fun evening.Want to really power up your fandom? Try Planet Money Plus. Your subscription supports the show and unlocks a sponsor-free feed. Learn more at plus.npr.org/planetmoney", + "explicit": false, + "external_urls": { + "spotify": "https://open.spotify.com/show/4FYpq3lSeQMAhqNI81O0Cn" + }, + "href": "https://api.spotify.com/v1/shows/4FYpq3lSeQMAhqNI81O0Cn", + "html_description": null, + "id": "4FYpq3lSeQMAhqNI81O0Cn", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/11d7b39883f704cfc6e9b1cf717d1b87ad238676", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/8feb7ba9f991af98307ae1de9c491c43754765dc", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/be7bdde2490c4a8ad5208b69080da2eaf70a79a5", + "width": 64 + } + ], + "is_externally_hosted": false, + "languages": [ + "en" + ], + "media_type": "audio", + "name": "Planet Money", + "publisher": "NPR", + "total_episodes": 355, + "type": "show", + "uri": "spotify:show:4FYpq3lSeQMAhqNI81O0Cn" + }, + "track": false, + "type": "episode", + "uri": "spotify:episode:6Br4mVPbe9c5jvIddrnkOg" + }, + "video_thumbnail": { + "url": null + } + } + ], + "limit": 100, + "next": null, + "offset": 0, + "previous": null, + "total": 4 +} \ No newline at end of file diff --git a/test_data/playlist_items_tracks.json b/test_data/playlist_items_tracks.json new file mode 100644 index 0000000..a0edc0b --- /dev/null +++ b/test_data/playlist_items_tracks.json @@ -0,0 +1,1556 @@ +{ + "href": "https://api.spotify.com/v1/playlists/23Ay9WfURne0LvtlCvYTkj/tracks?offset=0\u0026limit=100", + "items": [ + { + "added_at": "2022-03-22T17:17:01Z", + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/randomUser" + }, + "href": "https://api.spotify.com/v1/users/randomUser", + "id": "randomUser", + "type": "user", + "uri": "spotify:user:randomUser" + }, + "is_local": false, + "primary_color": null, + "track": { + "album": { + "album_type": "compilation", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/show/4XPl3uEEL9hvqMkoZrzbx5" + }, + "href": "https://api.spotify.com/v1/shows/4XPl3uEEL9hvqMkoZrzbx5", + "id": "4XPl3uEEL9hvqMkoZrzbx5", + "name": "Darknet Diaries", + "type": "show", + "uri": "spotify:show:4XPl3uEEL9hvqMkoZrzbx5" + } + ], + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BZ", + "CA", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KH", + "KI", + "KM", + "KN", + "KW", + "LA", + "LB", + "LC", + "LI", + "LR", + "LS", + "LT", + "LU", + "LV", + "MA", + "MC", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "US", + "UY", + "UZ", + "VC", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "external_urls": { + "spotify": "https://open.spotify.com/show/4XPl3uEEL9hvqMkoZrzbx5" + }, + "href": "https://api.spotify.com/v1/shows/4XPl3uEEL9hvqMkoZrzbx5", + "id": "4XPl3uEEL9hvqMkoZrzbx5", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab6765630000ba8a11874ad24c1dcac2ace8d4c9", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67656300005f1f11874ad24c1dcac2ace8d4c9", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab6765630000f68d11874ad24c1dcac2ace8d4c9", + "width": 64 + } + ], + "is_playable": false, + "name": "Darknet Diaries", + "release_date": null, + "release_date_precision": null, + "restrictions": { + "reason": "product" + }, + "total_tracks": 1, + "type": "show", + "uri": "spotify:show:4XPl3uEEL9hvqMkoZrzbx5" + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/show/4XPl3uEEL9hvqMkoZrzbx5" + }, + "href": "https://api.spotify.com/v1/shows/4XPl3uEEL9hvqMkoZrzbx5", + "id": "4XPl3uEEL9hvqMkoZrzbx5", + "name": "Darknet Diaries", + "type": "show", + "uri": "spotify:show:4XPl3uEEL9hvqMkoZrzbx5" + } + ], + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BZ", + "CA", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KH", + "KI", + "KM", + "KN", + "KW", + "LA", + "LB", + "LC", + "LI", + "LR", + "LS", + "LT", + "LU", + "LV", + "MA", + "MC", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "US", + "UY", + "UZ", + "VC", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "disc_number": 0, + "duration_ms": 5303484, + "episode": false, + "explicit": true, + "external_ids": { + "spotify": "https://open.spotify.com/episode/5Snektk5Z2nUzM7DgoEBSx" + }, + "external_urls": { + "spotify": "https://open.spotify.com/episode/5Snektk5Z2nUzM7DgoEBSx" + }, + "href": "https://api.spotify.com/v1/episodes/5Snektk5Z2nUzM7DgoEBSx", + "id": "5Snektk5Z2nUzM7DgoEBSx", + "is_local": false, + "is_playable": false, + "name": "112: Dirty Coms", + "popularity": 0, + "preview_url": "https://p.scdn.co/mp3-preview/58e56ec5a44a886fc17def91ed22dd8b60f6a22d", + "track": true, + "track_number": 0, + "type": "episode", + "uri": "spotify:episode:5Snektk5Z2nUzM7DgoEBSx" + }, + "video_thumbnail": { + "url": null + } + }, + { + "added_at": "2022-03-22T17:17:28Z", + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/randomUser" + }, + "href": "https://api.spotify.com/v1/users/randomUser", + "id": "randomUser", + "type": "user", + "uri": "spotify:user:randomUser" + }, + "is_local": false, + "primary_color": null, + "track": { + "album": { + "album_type": "compilation", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/show/5nk7d9MLCgE3M47mXPW7MP" + }, + "href": "https://api.spotify.com/v1/shows/5nk7d9MLCgE3M47mXPW7MP", + "id": "5nk7d9MLCgE3M47mXPW7MP", + "name": "The Missing Cryptoqueen", + "type": "show", + "uri": "spotify:show:5nk7d9MLCgE3M47mXPW7MP" + } + ], + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BZ", + "CA", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KH", + "KI", + "KM", + "KN", + "KW", + "LA", + "LB", + "LC", + "LI", + "LR", + "LS", + "LT", + "LU", + "LV", + "MA", + "MC", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "US", + "UY", + "UZ", + "VC", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "external_urls": { + "spotify": "https://open.spotify.com/show/5nk7d9MLCgE3M47mXPW7MP" + }, + "href": "https://api.spotify.com/v1/shows/5nk7d9MLCgE3M47mXPW7MP", + "id": "5nk7d9MLCgE3M47mXPW7MP", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/b9f9e946ea8bd1955c57d5d391ff1cc728aa8963", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/5493f2c236bf26699470e24b01696e5ea00e8a31", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/028f030786a5778a4a52f5966ae2817de2265823", + "width": 64 + } + ], + "is_playable": false, + "name": "The Missing Cryptoqueen", + "release_date": null, + "release_date_precision": null, + "restrictions": { + "reason": "product" + }, + "total_tracks": 1, + "type": "show", + "uri": "spotify:show:5nk7d9MLCgE3M47mXPW7MP" + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/show/5nk7d9MLCgE3M47mXPW7MP" + }, + "href": "https://api.spotify.com/v1/shows/5nk7d9MLCgE3M47mXPW7MP", + "id": "5nk7d9MLCgE3M47mXPW7MP", + "name": "The Missing Cryptoqueen", + "type": "show", + "uri": "spotify:show:5nk7d9MLCgE3M47mXPW7MP" + } + ], + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BZ", + "CA", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KH", + "KI", + "KM", + "KN", + "KW", + "LA", + "LB", + "LC", + "LI", + "LR", + "LS", + "LT", + "LU", + "LV", + "MA", + "MC", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "US", + "UY", + "UZ", + "VC", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "disc_number": 0, + "duration_ms": 2129000, + "episode": false, + "explicit": true, + "external_ids": { + "spotify": "https://open.spotify.com/episode/1WZevjwFcULC8HWySuaB3z" + }, + "external_urls": { + "spotify": "https://open.spotify.com/episode/1WZevjwFcULC8HWySuaB3z" + }, + "href": "https://api.spotify.com/v1/episodes/1WZevjwFcULC8HWySuaB3z", + "id": "1WZevjwFcULC8HWySuaB3z", + "is_local": false, + "is_playable": false, + "name": "Episode 2: The Bitcoin Killer", + "popularity": 0, + "preview_url": null, + "track": true, + "track_number": 0, + "type": "episode", + "uri": "spotify:episode:1WZevjwFcULC8HWySuaB3z" + }, + "video_thumbnail": { + "url": null + } + }, + { + "added_at": "2022-03-22T17:18:21Z", + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/randomUser" + }, + "href": "https://api.spotify.com/v1/users/randomUser", + "id": "randomUser", + "type": "user", + "uri": "spotify:user:randomUser" + }, + "is_local": false, + "primary_color": null, + "track": { + "album": { + "album_type": "compilation", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/show/4Jgtgr4mHXNDyLldHkfEMz" + }, + "href": "https://api.spotify.com/v1/shows/4Jgtgr4mHXNDyLldHkfEMz", + "id": "4Jgtgr4mHXNDyLldHkfEMz", + "name": "Command Line Heroes", + "type": "show", + "uri": "spotify:show:4Jgtgr4mHXNDyLldHkfEMz" + } + ], + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BZ", + "CA", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KH", + "KI", + "KM", + "KN", + "KW", + "LA", + "LB", + "LC", + "LI", + "LR", + "LS", + "LT", + "LU", + "LV", + "MA", + "MC", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "US", + "UY", + "UZ", + "VC", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "external_urls": { + "spotify": "https://open.spotify.com/show/4Jgtgr4mHXNDyLldHkfEMz" + }, + "href": "https://api.spotify.com/v1/shows/4Jgtgr4mHXNDyLldHkfEMz", + "id": "4Jgtgr4mHXNDyLldHkfEMz", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab6765630000ba8ad5dff299e9c86cd6126fea8f", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67656300005f1fd5dff299e9c86cd6126fea8f", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab6765630000f68dd5dff299e9c86cd6126fea8f", + "width": 64 + } + ], + "is_playable": false, + "name": "Command Line Heroes", + "release_date": null, + "release_date_precision": null, + "restrictions": { + "reason": "product" + }, + "total_tracks": 1, + "type": "show", + "uri": "spotify:show:4Jgtgr4mHXNDyLldHkfEMz" + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/show/4Jgtgr4mHXNDyLldHkfEMz" + }, + "href": "https://api.spotify.com/v1/shows/4Jgtgr4mHXNDyLldHkfEMz", + "id": "4Jgtgr4mHXNDyLldHkfEMz", + "name": "Command Line Heroes", + "type": "show", + "uri": "spotify:show:4Jgtgr4mHXNDyLldHkfEMz" + } + ], + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BZ", + "CA", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KH", + "KI", + "KM", + "KN", + "KW", + "LA", + "LB", + "LC", + "LI", + "LR", + "LS", + "LT", + "LU", + "LV", + "MA", + "MC", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "US", + "UY", + "UZ", + "VC", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "disc_number": 0, + "duration_ms": 1304111, + "episode": false, + "explicit": false, + "external_ids": { + "spotify": "https://open.spotify.com/episode/30HqApDClX0SFE53KJ7wvr" + }, + "external_urls": { + "spotify": "https://open.spotify.com/episode/30HqApDClX0SFE53KJ7wvr" + }, + "href": "https://api.spotify.com/v1/episodes/30HqApDClX0SFE53KJ7wvr", + "id": "30HqApDClX0SFE53KJ7wvr", + "is_local": false, + "is_playable": false, + "name": "Lurking Logic Bombs", + "popularity": 0, + "preview_url": "https://p.scdn.co/mp3-preview/c1c7d757eb6bd6415824720918d267390a54156d", + "track": true, + "track_number": 0, + "type": "episode", + "uri": "spotify:episode:30HqApDClX0SFE53KJ7wvr" + }, + "video_thumbnail": { + "url": null + } + }, + { + "added_at": "2022-03-22T17:19:02Z", + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/randomUser" + }, + "href": "https://api.spotify.com/v1/users/randomUser", + "id": "randomUser", + "type": "user", + "uri": "spotify:user:randomUser" + }, + "is_local": false, + "primary_color": null, + "track": { + "album": { + "album_type": "compilation", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/show/4FYpq3lSeQMAhqNI81O0Cn" + }, + "href": "https://api.spotify.com/v1/shows/4FYpq3lSeQMAhqNI81O0Cn", + "id": "4FYpq3lSeQMAhqNI81O0Cn", + "name": "Planet Money", + "type": "show", + "uri": "spotify:show:4FYpq3lSeQMAhqNI81O0Cn" + } + ], + "available_markets": [ + "AD", + "AR", + "AT", + "AU", + "BE", + "BG", + "BO", + "BR", + "CA", + "CH", + "CL", + "CO", + "CR", + "CY", + "CZ", + "DE", + "DK", + "DO", + "EC", + "EE", + "ES", + "FI", + "FR", + "GB", + "GR", + "GT", + "HK", + "HN", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JP", + "LI", + "LT", + "LU", + "LV", + "MC", + "MT", + "MX", + "MY", + "NI", + "NL", + "NO", + "NZ", + "PA", + "PE", + "PH", + "PL", + "PT", + "PY", + "RO", + "SE", + "SG", + "SK", + "SV", + "TH", + "TR", + "TW", + "US", + "UY", + "VN", + "ZA" + ], + "external_urls": { + "spotify": "https://open.spotify.com/show/4FYpq3lSeQMAhqNI81O0Cn" + }, + "href": "https://api.spotify.com/v1/shows/4FYpq3lSeQMAhqNI81O0Cn", + "id": "4FYpq3lSeQMAhqNI81O0Cn", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/11d7b39883f704cfc6e9b1cf717d1b87ad238676", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/8feb7ba9f991af98307ae1de9c491c43754765dc", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/be7bdde2490c4a8ad5208b69080da2eaf70a79a5", + "width": 64 + } + ], + "is_playable": false, + "name": "Planet Money", + "release_date": null, + "release_date_precision": null, + "restrictions": { + "reason": "product" + }, + "total_tracks": 1, + "type": "show", + "uri": "spotify:show:4FYpq3lSeQMAhqNI81O0Cn" + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/show/4FYpq3lSeQMAhqNI81O0Cn" + }, + "href": "https://api.spotify.com/v1/shows/4FYpq3lSeQMAhqNI81O0Cn", + "id": "4FYpq3lSeQMAhqNI81O0Cn", + "name": "Planet Money", + "type": "show", + "uri": "spotify:show:4FYpq3lSeQMAhqNI81O0Cn" + } + ], + "available_markets": [ + "AD", + "AR", + "AT", + "AU", + "BE", + "BG", + "BO", + "BR", + "CA", + "CH", + "CL", + "CO", + "CR", + "CY", + "CZ", + "DE", + "DK", + "DO", + "EC", + "EE", + "ES", + "FI", + "FR", + "GB", + "GR", + "GT", + "HK", + "HN", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JP", + "LI", + "LT", + "LU", + "LV", + "MC", + "MT", + "MX", + "MY", + "NI", + "NL", + "NO", + "NZ", + "PA", + "PE", + "PH", + "PL", + "PT", + "PY", + "RO", + "SE", + "SG", + "SK", + "SV", + "TH", + "TR", + "TW", + "US", + "UY", + "VN", + "ZA" + ], + "disc_number": 0, + "duration_ms": 1101531, + "episode": false, + "explicit": false, + "external_ids": { + "spotify": "https://open.spotify.com/episode/6Br4mVPbe9c5jvIddrnkOg" + }, + "external_urls": { + "spotify": "https://open.spotify.com/episode/6Br4mVPbe9c5jvIddrnkOg" + }, + "href": "https://api.spotify.com/v1/episodes/6Br4mVPbe9c5jvIddrnkOg", + "id": "6Br4mVPbe9c5jvIddrnkOg", + "is_local": false, + "is_playable": false, + "name": "Of oligarchs, oil and rubles", + "popularity": 0, + "preview_url": "https://p.scdn.co/mp3-preview/aedf7273252dd8a752b56cfbde51a97104f27bf3", + "track": true, + "track_number": 0, + "type": "episode", + "uri": "spotify:episode:6Br4mVPbe9c5jvIddrnkOg" + }, + "video_thumbnail": { + "url": null + } + } + ], + "limit": 100, + "next": null, + "offset": 0, + "previous": null, + "total": 4 +} \ No newline at end of file From e7f5426453dd5439ee7c7fa9ffd74cfb49aafd57 Mon Sep 17 00:00:00 2001 From: xonstone <17579331+xonstone@users.noreply.github.com> Date: Fri, 20 May 2022 14:21:40 +0200 Subject: [PATCH 2/9] Add comments --- playlist.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/playlist.go b/playlist.go index 7039e46..ad7a4ea 100644 --- a/playlist.go +++ b/playlist.go @@ -206,11 +206,13 @@ type PlaylistItem struct { Track PlaylistItemTrack `json:"track"` } +// PlaylistItemTrack is a union type for both tracks and episodes. It defaults to track type. type PlaylistItemTrack struct { *FullTrack Episode *EpisodePage } +// UnmarshalJSON customises the unmarshalling based on the type flags set. func (t *PlaylistItemTrack) UnmarshalJSON(b []byte) error { is := struct { Episode bool `json:"episode"` From 56b111d42c4df174de3b7e38e0ff9e3ff1b8c4e0 Mon Sep 17 00:00:00 2001 From: Davy Van Den Steen <17579331+xonstone@users.noreply.github.com> Date: Fri, 20 May 2022 15:55:52 +0200 Subject: [PATCH 3/9] Typo Co-authored-by: Noah Stride --- playlist.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playlist.go b/playlist.go index ad7a4ea..d1f53b4 100644 --- a/playlist.go +++ b/playlist.go @@ -198,7 +198,7 @@ type PlaylistItem struct { // Warning: very old playlists may not populate this value. AddedAt string `json:"added_at"` // The Spotify user who added the track to the playlist. - // Warning: vary old playlists may not populate this value. + // Warning: very old playlists may not populate this value. AddedBy User `json:"added_by"` // Whether this track is a local file or not. IsLocal bool `json:"is_local"` From f12fffe74729ab17cec239ddd6b630b4bbda00a8 Mon Sep 17 00:00:00 2001 From: xonstone <17579331+xonstone@users.noreply.github.com> Date: Fri, 20 May 2022 16:24:55 +0200 Subject: [PATCH 4/9] Add tests for mixed scenarions and field for track --- playlist.go | 6 +- playlist_test.go | 54 +- .../playlist_items_episodes_and_tracks.json | 1478 +++++++++++++++++ test_data/playlist_items_tracks.json | 902 ++-------- 4 files changed, 1672 insertions(+), 768 deletions(-) create mode 100644 test_data/playlist_items_episodes_and_tracks.json diff --git a/playlist.go b/playlist.go index ad7a4ea..a4efa56 100644 --- a/playlist.go +++ b/playlist.go @@ -206,9 +206,9 @@ type PlaylistItem struct { Track PlaylistItemTrack `json:"track"` } -// PlaylistItemTrack is a union type for both tracks and episodes. It defaults to track type. +// PlaylistItemTrack is a union type for both tracks and episodes. type PlaylistItemTrack struct { - *FullTrack + Track *FullTrack Episode *EpisodePage } @@ -232,7 +232,7 @@ func (t *PlaylistItemTrack) UnmarshalJSON(b []byte) error { } if is.Track { - err := json.Unmarshal(b, &t.FullTrack) + err := json.Unmarshal(b, &t.Track) if err != nil { return err } diff --git a/playlist_test.go b/playlist_test.go index 23b54f4..0435f61 100644 --- a/playlist_test.go +++ b/playlist_test.go @@ -174,6 +174,35 @@ func TestGetPlaylistItemsTracks(t *testing.T) { client, server := testClientFile(http.StatusOK, "test_data/playlist_items_tracks.json") defer server.Close() + tracks, err := client.GetPlaylistItems(context.Background(), "playlistID") + if err != nil { + t.Error(err) + } + if tracks.Total != 2 { + t.Errorf("Got %d tracks, expected 47\n", tracks.Total) + } + if len(tracks.Items) == 0 { + t.Fatal("No tracks returned") + } + expected := "Typhoons" + actual := tracks.Items[0].Track.Track.Name + if expected != actual { + t.Errorf("Got '%s', expected '%s'\n", actual, expected) + } + added := tracks.Items[0].AddedAt + tm, err := time.Parse(TimestampLayout, added) + if err != nil { + t.Error(err) + } + if f := tm.Format(DateLayout); f != "2022-05-20" { + t.Errorf("Expected added at 2014-11-25, got %s\n", f) + } +} + +func TestGetPlaylistItemsTracksAndEpisodes(t *testing.T) { + client, server := testClientFile(http.StatusOK, "test_data/playlist_items_episodes_and_tracks.json") + defer server.Close() + tracks, err := client.GetPlaylistItems(context.Background(), "playlistID") if err != nil { t.Error(err) @@ -184,8 +213,9 @@ func TestGetPlaylistItemsTracks(t *testing.T) { if len(tracks.Items) == 0 { t.Fatal("No tracks returned") } - expected := "112: Dirty Coms" - actual := tracks.Items[0].Track.Name + + expected := "491- The Missing Middle" + actual := tracks.Items[0].Track.Episode.Name if expected != actual { t.Errorf("Got '%s', expected '%s'\n", actual, expected) } @@ -194,7 +224,21 @@ func TestGetPlaylistItemsTracks(t *testing.T) { if err != nil { t.Error(err) } - if f := tm.Format(DateLayout); f != "2022-03-22" { + if f := tm.Format(DateLayout); f != "2022-05-20" { + t.Errorf("Expected added at 2014-11-25, got %s\n", f) + } + + expected = "Typhoons" + actual = tracks.Items[2].Track.Track.Name + if expected != actual { + t.Errorf("Got '%s', expected '%s'\n", actual, expected) + } + added = tracks.Items[0].AddedAt + tm, err = time.Parse(TimestampLayout, added) + if err != nil { + t.Error(err) + } + if f := tm.Format(DateLayout); f != "2022-05-20" { t.Errorf("Expected added at 2014-11-25, got %s\n", f) } } @@ -218,7 +262,7 @@ var newPlaylist = ` "collaborative": %t, "description": "Test Description", "external_urls": { - "spotify": "http://open.spotify.com/user/thelinmichael/playlist/7d2D2S200NyUE5KYs80PwO" + "spotify": "api.http://open.spotify.com/user/thelinmichael/playlist/7d2D2S200NyUE5KYs80PwO" }, "followers": { "href": null, @@ -230,7 +274,7 @@ var newPlaylist = ` "name": "A New Playlist", "owner": { "external_urls": { - "spotify": "http://open.spotify.com/user/thelinmichael" + "spotify": "api.http://open.spotify.com/user/thelinmichael" }, "href": "https://api.spotify.com/v1/users/thelinmichael", "id": "thelinmichael", diff --git a/test_data/playlist_items_episodes_and_tracks.json b/test_data/playlist_items_episodes_and_tracks.json new file mode 100644 index 0000000..4eee8a9 --- /dev/null +++ b/test_data/playlist_items_episodes_and_tracks.json @@ -0,0 +1,1478 @@ +{ + "href": "https://api.spotify.com/v1/playlists/23Ay9WfURne0LvtlCvYTkj/tracks?offset=0&limit=100&additional_types=track%2Cepisode", + "items": [ + { + "added_at": "2022-05-20T12:35:56Z", + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/randomUser" + }, + "href": "https://api.spotify.com/v1/users/randomUser", + "id": "randomUser", + "type": "user", + "uri": "spotify:user:randomUser" + }, + "is_local": false, + "primary_color": null, + "track": { + "audio_preview_url": "https://p.scdn.co/mp3-preview/95c6a3ea1a2144cfdc55dab435901eb443d327ca", + "description": "Downtown Toronto has a dense core of tall, glassy buildings along the waterfront of Lake Ontario. Outside of that, lots short single family homes sprawl out in every direction. Residents looking for something in between an expensive house and a condo in a tall, generic tower struggle to find places to live. There just aren’t a lot of these mid-sized rental buildings in the city.And it's not just Toronto -- a similar architectural void can be found in many other North American cities, like Los Angeles, Seattle, Boston and Vancouver. And this is a big concern for urban planners -- so big, there's a term for it. The \"missing middle.\" That moniker can be confusing, because it's not directly about middle class housing -- rather, it's about a specific range of building sizes and typologies, including: duplexes, triplexes, courtyard buildings, multi-story apartment complexes, the list goes on. Buildings like these have an outsized effect on cities, and cities without enough of these kinds of buildings often suffer from their absence.The Missing Middle", + "duration_ms": 2249560, + "episode": true, + "explicit": false, + "external_urls": { + "spotify": "https://open.spotify.com/episode/6Oo1FMgWEQpQKh2mYtsnp5" + }, + "href": "https://api.spotify.com/v1/episodes/6Oo1FMgWEQpQKh2mYtsnp5", + "html_description": "

Downtown Toronto has a dense core of tall, glassy buildings along the waterfront of Lake Ontario. Outside of that, lots short single family homes sprawl out in every direction. Residents looking for something in between an expensive house and a condo in a tall, generic tower struggle to find places to live. There just aren’t a lot of these mid-sized rental buildings in the city.

And it's not just Toronto -- a similar architectural void can be found in many other North American cities, like Los Angeles, Seattle, Boston and Vancouver. And this is a big concern for urban planners -- so big, there's a term for it. The "missing middle." That moniker can be confusing, because it's not directly about middle class housing -- rather, it's about a specific range of building sizes and typologies, including: duplexes, triplexes, courtyard buildings, multi-story apartment complexes, the list goes on. Buildings like these have an outsized effect on cities, and cities without enough of these kinds of buildings often suffer from their absence.

The Missing Middle

", + "id": "6Oo1FMgWEQpQKh2mYtsnp5", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab6765630000ba8ac2ce8ec0c4fe39ce48a02f73", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67656300005f1fc2ce8ec0c4fe39ce48a02f73", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab6765630000f68dc2ce8ec0c4fe39ce48a02f73", + "width": 64 + } + ], + "is_externally_hosted": false, + "is_playable": true, + "language": "en-US", + "languages": [ + "en-US" + ], + "name": "491- The Missing Middle", + "release_date": "2022-05-18", + "release_date_precision": "day", + "resume_point": { + "fully_played": false, + "resume_position_ms": 0 + }, + "show": { + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BZ", + "CA", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KH", + "KI", + "KM", + "KN", + "KW", + "LA", + "LB", + "LC", + "LI", + "LR", + "LS", + "LT", + "LU", + "LV", + "MA", + "MC", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "US", + "UY", + "UZ", + "VC", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "copyrights": [], + "description": "Design is everywhere in our lives, perhaps most importantly in the places where we've just stopped noticing. 99% Invisible is a weekly exploration of the process and power of design and architecture. From award winning producer Roman Mars. Learn more at 99percentinvisible.org.", + "explicit": true, + "external_urls": { + "spotify": "https://open.spotify.com/show/2VRS1IJCTn2Nlkg33ZVfkM" + }, + "href": "https://api.spotify.com/v1/shows/2VRS1IJCTn2Nlkg33ZVfkM", + "html_description": null, + "id": "2VRS1IJCTn2Nlkg33ZVfkM", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab6765630000ba8ac2ce8ec0c4fe39ce48a02f73", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67656300005f1fc2ce8ec0c4fe39ce48a02f73", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab6765630000f68dc2ce8ec0c4fe39ce48a02f73", + "width": 64 + } + ], + "is_externally_hosted": false, + "languages": [ + "en" + ], + "media_type": "audio", + "name": "99% Invisible", + "publisher": "Roman Mars", + "total_episodes": 545, + "type": "show", + "uri": "spotify:show:2VRS1IJCTn2Nlkg33ZVfkM" + }, + "track": false, + "type": "episode", + "uri": "spotify:episode:6Oo1FMgWEQpQKh2mYtsnp5" + }, + "video_thumbnail": { + "url": null + } + }, + { + "added_at": "2022-05-20T12:35:56Z", + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/randomUser" + }, + "href": "https://api.spotify.com/v1/users/randomUser", + "id": "randomUser", + "type": "user", + "uri": "spotify:user:randomUser" + }, + "is_local": false, + "primary_color": null, + "track": { + "audio_preview_url": "https://p.scdn.co/mp3-preview/43438e848c27656afb45e21b1b418d18548444b3", + "description": "What began as a supposed accounting error landed Cliff Stoll in the midst of database intrusions, government organizations, and the beginnings of a newer threat—cyber-espionage. This led the eclectic astronomer-cum-systems administrator to create what we know today as intrusion detection. And it all began at a time when people didn’t understand the importance of cybersecurity. This is a story that many in the infosec community have already heard, but the lessons from Stoll’s journey are still relevant. Katie Hafner gives us the background on this unbelievable story. Richard Bejtlich outlines the “honey pot” that finally cracked open the international case. And Don Cavender discusses the impact of Stoll’s work, and how it has inspired generations of security professionals.If you want to read up on some of our research on ransomware, you can check out all our bonus material over at redhat.com/commandlineheroes. Follow along with the episode transcript.  ", + "duration_ms": 1338827, + "episode": true, + "explicit": false, + "external_urls": { + "spotify": "https://open.spotify.com/episode/3cjmOXTGkAFYaXYQ8XR5WY" + }, + "href": "https://api.spotify.com/v1/episodes/3cjmOXTGkAFYaXYQ8XR5WY", + "html_description": "

What began as a supposed accounting error landed Cliff Stoll in the midst of database intrusions, government organizations, and the beginnings of a newer threat—cyber-espionage. This led the eclectic astronomer-cum-systems administrator to create what we know today as intrusion detection. And it all began at a time when people didn’t understand the importance of cybersecurity. 

This is a story that many in the infosec community have already heard, but the lessons from Stoll’s journey are still relevant. Katie Hafner gives us the background on this unbelievable story. Richard Bejtlich outlines the “honey pot” that finally cracked open the international case. And Don Cavender discusses the impact of Stoll’s work, and how it has inspired generations of security professionals.

If you want to read up on some of our research on ransomware, you can check out all our bonus material over at redhat.com/commandlineheroes. Follow along with the episode transcript.

 

 

", + "id": "3cjmOXTGkAFYaXYQ8XR5WY", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab6765630000ba8a3e31531d0e73a142e236ae97", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67656300005f1f3e31531d0e73a142e236ae97", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab6765630000f68d3e31531d0e73a142e236ae97", + "width": 64 + } + ], + "is_externally_hosted": false, + "is_playable": true, + "language": "en", + "languages": [ + "en" + ], + "name": "Invisible Intruders", + "release_date": "2022-05-17", + "release_date_precision": "day", + "resume_point": { + "fully_played": false, + "resume_position_ms": 0 + }, + "show": { + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BZ", + "CA", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KH", + "KI", + "KM", + "KN", + "KW", + "LA", + "LB", + "LC", + "LI", + "LR", + "LS", + "LT", + "LU", + "LV", + "MA", + "MC", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "US", + "UY", + "UZ", + "VC", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "copyrights": [], + "description": "Stories about the people transforming technology from the command line up.", + "explicit": false, + "external_urls": { + "spotify": "https://open.spotify.com/show/4Jgtgr4mHXNDyLldHkfEMz" + }, + "href": "https://api.spotify.com/v1/shows/4Jgtgr4mHXNDyLldHkfEMz", + "html_description": null, + "id": "4Jgtgr4mHXNDyLldHkfEMz", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab6765630000ba8ad5dff299e9c86cd6126fea8f", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67656300005f1fd5dff299e9c86cd6126fea8f", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab6765630000f68dd5dff299e9c86cd6126fea8f", + "width": 64 + } + ], + "is_externally_hosted": false, + "languages": [ + "en" + ], + "media_type": "audio", + "name": "Command Line Heroes", + "publisher": "Red Hat", + "total_episodes": 76, + "type": "show", + "uri": "spotify:show:4Jgtgr4mHXNDyLldHkfEMz" + }, + "track": false, + "type": "episode", + "uri": "spotify:episode:3cjmOXTGkAFYaXYQ8XR5WY" + }, + "video_thumbnail": { + "url": null + } + }, + { + "added_at": "2022-05-20T14:10:29Z", + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/randomUser" + }, + "href": "https://api.spotify.com/v1/users/randomUser", + "id": "randomUser", + "type": "user", + "uri": "spotify:user:randomUser" + }, + "is_local": false, + "primary_color": null, + "track": { + "album": { + "album_type": "album", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/2S5hlvw4CMtMGswFtfdK15" + }, + "href": "https://api.spotify.com/v1/artists/2S5hlvw4CMtMGswFtfdK15", + "id": "2S5hlvw4CMtMGswFtfdK15", + "name": "Royal Blood", + "type": "artist", + "uri": "spotify:artist:2S5hlvw4CMtMGswFtfdK15" + } + ], + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BD", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BY", + "BZ", + "CA", + "CD", + "CG", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IQ", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KG", + "KH", + "KI", + "KM", + "KN", + "KR", + "KW", + "KZ", + "LA", + "LB", + "LC", + "LI", + "LK", + "LR", + "LS", + "LT", + "LU", + "LV", + "LY", + "MA", + "MC", + "MD", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PK", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TJ", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "UA", + "UG", + "US", + "UY", + "UZ", + "VC", + "VE", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "external_urls": { + "spotify": "https://open.spotify.com/album/05aqnnpYVOvsX0SIzmIuxi" + }, + "href": "https://api.spotify.com/v1/albums/05aqnnpYVOvsX0SIzmIuxi", + "id": "05aqnnpYVOvsX0SIzmIuxi", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b273712b9c0f9a8d380e26a95c1c", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e02712b9c0f9a8d380e26a95c1c", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d00004851712b9c0f9a8d380e26a95c1c", + "width": 64 + } + ], + "name": "Typhoons", + "release_date": "2021-04-30", + "release_date_precision": "day", + "total_tracks": 11, + "type": "album", + "uri": "spotify:album:05aqnnpYVOvsX0SIzmIuxi" + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/2S5hlvw4CMtMGswFtfdK15" + }, + "href": "https://api.spotify.com/v1/artists/2S5hlvw4CMtMGswFtfdK15", + "id": "2S5hlvw4CMtMGswFtfdK15", + "name": "Royal Blood", + "type": "artist", + "uri": "spotify:artist:2S5hlvw4CMtMGswFtfdK15" + } + ], + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BD", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BY", + "BZ", + "CA", + "CD", + "CG", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IQ", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KG", + "KH", + "KI", + "KM", + "KN", + "KR", + "KW", + "KZ", + "LA", + "LB", + "LC", + "LI", + "LK", + "LR", + "LS", + "LT", + "LU", + "LV", + "LY", + "MA", + "MC", + "MD", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PK", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TJ", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "UA", + "UG", + "US", + "UY", + "UZ", + "VC", + "VE", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "disc_number": 1, + "duration_ms": 236933, + "episode": false, + "explicit": false, + "external_ids": { + "isrc": "GBAHT2001121" + }, + "external_urls": { + "spotify": "https://open.spotify.com/track/5aFGo8wHEntVxFI8IF7Wuj" + }, + "href": "https://api.spotify.com/v1/tracks/5aFGo8wHEntVxFI8IF7Wuj", + "id": "5aFGo8wHEntVxFI8IF7Wuj", + "is_local": false, + "name": "Typhoons", + "popularity": 58, + "preview_url": "https://p.scdn.co/mp3-preview/63fcc72c60ea00649d4eb475ca268228cb9814f3?cid=5edddae5c9964de3bda68780f222f7a6", + "track": true, + "track_number": 3, + "type": "track", + "uri": "spotify:track:5aFGo8wHEntVxFI8IF7Wuj" + }, + "video_thumbnail": { + "url": null + } + }, + { + "added_at": "2022-05-20T14:10:39Z", + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/randomUser" + }, + "href": "https://api.spotify.com/v1/users/randomUser", + "id": "randomUser", + "type": "user", + "uri": "spotify:user:randomUser" + }, + "is_local": false, + "primary_color": null, + "track": { + "album": { + "album_type": "single", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/6ekYAO2D1JkI58CF4uRRqw" + }, + "href": "https://api.spotify.com/v1/artists/6ekYAO2D1JkI58CF4uRRqw", + "id": "6ekYAO2D1JkI58CF4uRRqw", + "name": "Tigercub", + "type": "artist", + "uri": "spotify:artist:6ekYAO2D1JkI58CF4uRRqw" + } + ], + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BD", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BY", + "BZ", + "CA", + "CD", + "CG", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IQ", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KG", + "KH", + "KI", + "KM", + "KN", + "KR", + "KW", + "KZ", + "LA", + "LB", + "LC", + "LI", + "LK", + "LR", + "LS", + "LT", + "LU", + "LV", + "LY", + "MA", + "MC", + "MD", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PK", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TJ", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "UA", + "UG", + "US", + "UY", + "UZ", + "VC", + "VE", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "external_urls": { + "spotify": "https://open.spotify.com/album/2xe0gTFgZok8BDgUlkpRQ6" + }, + "href": "https://api.spotify.com/v1/albums/2xe0gTFgZok8BDgUlkpRQ6", + "id": "2xe0gTFgZok8BDgUlkpRQ6", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b27303f861e63060e7d1ce63ca2d", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e0203f861e63060e7d1ce63ca2d", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d0000485103f861e63060e7d1ce63ca2d", + "width": 64 + } + ], + "name": "Beauty", + "release_date": "2021-01-29", + "release_date_precision": "day", + "total_tracks": 1, + "type": "album", + "uri": "spotify:album:2xe0gTFgZok8BDgUlkpRQ6" + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/6ekYAO2D1JkI58CF4uRRqw" + }, + "href": "https://api.spotify.com/v1/artists/6ekYAO2D1JkI58CF4uRRqw", + "id": "6ekYAO2D1JkI58CF4uRRqw", + "name": "Tigercub", + "type": "artist", + "uri": "spotify:artist:6ekYAO2D1JkI58CF4uRRqw" + } + ], + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AO", + "AR", + "AT", + "AU", + "AZ", + "BA", + "BB", + "BD", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BN", + "BO", + "BR", + "BS", + "BT", + "BW", + "BY", + "BZ", + "CA", + "CD", + "CG", + "CH", + "CI", + "CL", + "CM", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GA", + "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IQ", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KG", + "KH", + "KI", + "KM", + "KN", + "KR", + "KW", + "KZ", + "LA", + "LB", + "LC", + "LI", + "LK", + "LR", + "LS", + "LT", + "LU", + "LV", + "LY", + "MA", + "MC", + "MD", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PK", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "RW", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "SZ", + "TD", + "TG", + "TH", + "TJ", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "UA", + "UG", + "US", + "UY", + "UZ", + "VC", + "VE", + "VN", + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" + ], + "disc_number": 1, + "duration_ms": 220324, + "episode": false, + "explicit": false, + "external_ids": { + "isrc": "QMFMF2010623" + }, + "external_urls": { + "spotify": "https://open.spotify.com/track/0j4FFgyRleA5IbWP4BmlIC" + }, + "href": "https://api.spotify.com/v1/tracks/0j4FFgyRleA5IbWP4BmlIC", + "id": "0j4FFgyRleA5IbWP4BmlIC", + "is_local": false, + "name": "Beauty", + "popularity": 38, + "preview_url": "https://p.scdn.co/mp3-preview/b77ae05a5c30d8249504f5c2307fdd8cd44b4212?cid=5edddae5c9964de3bda68780f222f7a6", + "track": true, + "track_number": 1, + "type": "track", + "uri": "spotify:track:0j4FFgyRleA5IbWP4BmlIC" + }, + "video_thumbnail": { + "url": null + } + } + ], + "limit": 100, + "next": null, + "offset": 0, + "previous": null, + "total": 4 +} \ No newline at end of file diff --git a/test_data/playlist_items_tracks.json b/test_data/playlist_items_tracks.json index a0edc0b..6000d3f 100644 --- a/test_data/playlist_items_tracks.json +++ b/test_data/playlist_items_tracks.json @@ -1,8 +1,8 @@ { - "href": "https://api.spotify.com/v1/playlists/23Ay9WfURne0LvtlCvYTkj/tracks?offset=0\u0026limit=100", + "href": "https://api.spotify.com/v1/playlists/23Ay9WfURne0LvtlCvYTkj/tracks?offset=0&limit=100&additional_types=track%2Cepisode", "items": [ { - "added_at": "2022-03-22T17:17:01Z", + "added_at": "2022-05-20T14:10:29Z", "added_by": { "external_urls": { "spotify": "https://open.spotify.com/user/randomUser" @@ -16,17 +16,17 @@ "primary_color": null, "track": { "album": { - "album_type": "compilation", + "album_type": "album", "artists": [ { "external_urls": { - "spotify": "https://open.spotify.com/show/4XPl3uEEL9hvqMkoZrzbx5" + "spotify": "https://open.spotify.com/artist/2S5hlvw4CMtMGswFtfdK15" }, - "href": "https://api.spotify.com/v1/shows/4XPl3uEEL9hvqMkoZrzbx5", - "id": "4XPl3uEEL9hvqMkoZrzbx5", - "name": "Darknet Diaries", - "type": "show", - "uri": "spotify:show:4XPl3uEEL9hvqMkoZrzbx5" + "href": "https://api.spotify.com/v1/artists/2S5hlvw4CMtMGswFtfdK15", + "id": "2S5hlvw4CMtMGswFtfdK15", + "name": "Royal Blood", + "type": "artist", + "uri": "spotify:artist:2S5hlvw4CMtMGswFtfdK15" } ], "available_markets": [ @@ -42,6 +42,7 @@ "AZ", "BA", "BB", + "BD", "BE", "BF", "BG", @@ -54,8 +55,11 @@ "BS", "BT", "BW", + "BY", "BZ", "CA", + "CD", + "CG", "CH", "CI", "CL", @@ -101,28 +105,35 @@ "IE", "IL", "IN", + "IQ", "IS", "IT", "JM", "JO", "JP", "KE", + "KG", "KH", "KI", "KM", "KN", + "KR", "KW", + "KZ", "LA", "LB", "LC", "LI", + "LK", "LR", "LS", "LT", "LU", "LV", + "LY", "MA", "MC", + "MD", "ME", "MG", "MH", @@ -152,6 +163,7 @@ "PE", "PG", "PH", + "PK", "PL", "PS", "PT", @@ -178,6 +190,7 @@ "TD", "TG", "TH", + "TJ", "TL", "TN", "TO", @@ -186,10 +199,13 @@ "TV", "TW", "TZ", + "UA", + "UG", "US", "UY", "UZ", "VC", + "VE", "VN", "VU", "WS", @@ -199,485 +215,44 @@ "ZW" ], "external_urls": { - "spotify": "https://open.spotify.com/show/4XPl3uEEL9hvqMkoZrzbx5" + "spotify": "https://open.spotify.com/album/05aqnnpYVOvsX0SIzmIuxi" }, - "href": "https://api.spotify.com/v1/shows/4XPl3uEEL9hvqMkoZrzbx5", - "id": "4XPl3uEEL9hvqMkoZrzbx5", + "href": "https://api.spotify.com/v1/albums/05aqnnpYVOvsX0SIzmIuxi", + "id": "05aqnnpYVOvsX0SIzmIuxi", "images": [ { "height": 640, - "url": "https://i.scdn.co/image/ab6765630000ba8a11874ad24c1dcac2ace8d4c9", + "url": "https://i.scdn.co/image/ab67616d0000b273712b9c0f9a8d380e26a95c1c", "width": 640 }, { "height": 300, - "url": "https://i.scdn.co/image/ab67656300005f1f11874ad24c1dcac2ace8d4c9", + "url": "https://i.scdn.co/image/ab67616d00001e02712b9c0f9a8d380e26a95c1c", "width": 300 }, { "height": 64, - "url": "https://i.scdn.co/image/ab6765630000f68d11874ad24c1dcac2ace8d4c9", + "url": "https://i.scdn.co/image/ab67616d00004851712b9c0f9a8d380e26a95c1c", "width": 64 } ], - "is_playable": false, - "name": "Darknet Diaries", - "release_date": null, - "release_date_precision": null, - "restrictions": { - "reason": "product" - }, - "total_tracks": 1, - "type": "show", - "uri": "spotify:show:4XPl3uEEL9hvqMkoZrzbx5" - }, - "artists": [ - { - "external_urls": { - "spotify": "https://open.spotify.com/show/4XPl3uEEL9hvqMkoZrzbx5" - }, - "href": "https://api.spotify.com/v1/shows/4XPl3uEEL9hvqMkoZrzbx5", - "id": "4XPl3uEEL9hvqMkoZrzbx5", - "name": "Darknet Diaries", - "type": "show", - "uri": "spotify:show:4XPl3uEEL9hvqMkoZrzbx5" - } - ], - "available_markets": [ - "AD", - "AE", - "AG", - "AL", - "AM", - "AO", - "AR", - "AT", - "AU", - "AZ", - "BA", - "BB", - "BE", - "BF", - "BG", - "BH", - "BI", - "BJ", - "BN", - "BO", - "BR", - "BS", - "BT", - "BW", - "BZ", - "CA", - "CH", - "CI", - "CL", - "CM", - "CO", - "CR", - "CV", - "CW", - "CY", - "CZ", - "DE", - "DJ", - "DK", - "DM", - "DO", - "DZ", - "EC", - "EE", - "EG", - "ES", - "FI", - "FJ", - "FM", - "FR", - "GA", - "GB", - "GD", - "GE", - "GH", - "GM", - "GN", - "GQ", - "GR", - "GT", - "GW", - "GY", - "HK", - "HN", - "HR", - "HT", - "HU", - "ID", - "IE", - "IL", - "IN", - "IS", - "IT", - "JM", - "JO", - "JP", - "KE", - "KH", - "KI", - "KM", - "KN", - "KW", - "LA", - "LB", - "LC", - "LI", - "LR", - "LS", - "LT", - "LU", - "LV", - "MA", - "MC", - "ME", - "MG", - "MH", - "MK", - "ML", - "MN", - "MO", - "MR", - "MT", - "MU", - "MV", - "MW", - "MX", - "MY", - "MZ", - "NA", - "NE", - "NG", - "NI", - "NL", - "NO", - "NP", - "NR", - "NZ", - "OM", - "PA", - "PE", - "PG", - "PH", - "PL", - "PS", - "PT", - "PW", - "PY", - "QA", - "RO", - "RS", - "RW", - "SA", - "SB", - "SC", - "SE", - "SG", - "SI", - "SK", - "SL", - "SM", - "SN", - "SR", - "ST", - "SV", - "SZ", - "TD", - "TG", - "TH", - "TL", - "TN", - "TO", - "TR", - "TT", - "TV", - "TW", - "TZ", - "US", - "UY", - "UZ", - "VC", - "VN", - "VU", - "WS", - "XK", - "ZA", - "ZM", - "ZW" - ], - "disc_number": 0, - "duration_ms": 5303484, - "episode": false, - "explicit": true, - "external_ids": { - "spotify": "https://open.spotify.com/episode/5Snektk5Z2nUzM7DgoEBSx" - }, - "external_urls": { - "spotify": "https://open.spotify.com/episode/5Snektk5Z2nUzM7DgoEBSx" - }, - "href": "https://api.spotify.com/v1/episodes/5Snektk5Z2nUzM7DgoEBSx", - "id": "5Snektk5Z2nUzM7DgoEBSx", - "is_local": false, - "is_playable": false, - "name": "112: Dirty Coms", - "popularity": 0, - "preview_url": "https://p.scdn.co/mp3-preview/58e56ec5a44a886fc17def91ed22dd8b60f6a22d", - "track": true, - "track_number": 0, - "type": "episode", - "uri": "spotify:episode:5Snektk5Z2nUzM7DgoEBSx" - }, - "video_thumbnail": { - "url": null - } - }, - { - "added_at": "2022-03-22T17:17:28Z", - "added_by": { - "external_urls": { - "spotify": "https://open.spotify.com/user/randomUser" - }, - "href": "https://api.spotify.com/v1/users/randomUser", - "id": "randomUser", - "type": "user", - "uri": "spotify:user:randomUser" - }, - "is_local": false, - "primary_color": null, - "track": { - "album": { - "album_type": "compilation", - "artists": [ - { - "external_urls": { - "spotify": "https://open.spotify.com/show/5nk7d9MLCgE3M47mXPW7MP" - }, - "href": "https://api.spotify.com/v1/shows/5nk7d9MLCgE3M47mXPW7MP", - "id": "5nk7d9MLCgE3M47mXPW7MP", - "name": "The Missing Cryptoqueen", - "type": "show", - "uri": "spotify:show:5nk7d9MLCgE3M47mXPW7MP" - } - ], - "available_markets": [ - "AD", - "AE", - "AG", - "AL", - "AM", - "AO", - "AR", - "AT", - "AU", - "AZ", - "BA", - "BB", - "BE", - "BF", - "BG", - "BH", - "BI", - "BJ", - "BN", - "BO", - "BR", - "BS", - "BT", - "BW", - "BZ", - "CA", - "CH", - "CI", - "CL", - "CM", - "CO", - "CR", - "CV", - "CW", - "CY", - "CZ", - "DE", - "DJ", - "DK", - "DM", - "DO", - "DZ", - "EC", - "EE", - "EG", - "ES", - "FI", - "FJ", - "FM", - "FR", - "GA", - "GB", - "GD", - "GE", - "GH", - "GM", - "GN", - "GQ", - "GR", - "GT", - "GW", - "GY", - "HK", - "HN", - "HR", - "HT", - "HU", - "ID", - "IE", - "IL", - "IN", - "IS", - "IT", - "JM", - "JO", - "JP", - "KE", - "KH", - "KI", - "KM", - "KN", - "KW", - "LA", - "LB", - "LC", - "LI", - "LR", - "LS", - "LT", - "LU", - "LV", - "MA", - "MC", - "ME", - "MG", - "MH", - "MK", - "ML", - "MN", - "MO", - "MR", - "MT", - "MU", - "MV", - "MW", - "MX", - "MY", - "MZ", - "NA", - "NE", - "NG", - "NI", - "NL", - "NO", - "NP", - "NR", - "NZ", - "OM", - "PA", - "PE", - "PG", - "PH", - "PL", - "PS", - "PT", - "PW", - "PY", - "QA", - "RO", - "RS", - "RW", - "SA", - "SB", - "SC", - "SE", - "SG", - "SI", - "SK", - "SL", - "SM", - "SN", - "SR", - "ST", - "SV", - "SZ", - "TD", - "TG", - "TH", - "TL", - "TN", - "TO", - "TR", - "TT", - "TV", - "TW", - "TZ", - "US", - "UY", - "UZ", - "VC", - "VN", - "VU", - "WS", - "XK", - "ZA", - "ZM", - "ZW" - ], - "external_urls": { - "spotify": "https://open.spotify.com/show/5nk7d9MLCgE3M47mXPW7MP" - }, - "href": "https://api.spotify.com/v1/shows/5nk7d9MLCgE3M47mXPW7MP", - "id": "5nk7d9MLCgE3M47mXPW7MP", - "images": [ - { - "height": 640, - "url": "https://i.scdn.co/image/b9f9e946ea8bd1955c57d5d391ff1cc728aa8963", - "width": 640 - }, - { - "height": 300, - "url": "https://i.scdn.co/image/5493f2c236bf26699470e24b01696e5ea00e8a31", - "width": 300 - }, - { - "height": 64, - "url": "https://i.scdn.co/image/028f030786a5778a4a52f5966ae2817de2265823", - "width": 64 - } - ], - "is_playable": false, - "name": "The Missing Cryptoqueen", - "release_date": null, - "release_date_precision": null, - "restrictions": { - "reason": "product" - }, - "total_tracks": 1, - "type": "show", - "uri": "spotify:show:5nk7d9MLCgE3M47mXPW7MP" + "name": "Typhoons", + "release_date": "2021-04-30", + "release_date_precision": "day", + "total_tracks": 11, + "type": "album", + "uri": "spotify:album:05aqnnpYVOvsX0SIzmIuxi" }, "artists": [ { "external_urls": { - "spotify": "https://open.spotify.com/show/5nk7d9MLCgE3M47mXPW7MP" + "spotify": "https://open.spotify.com/artist/2S5hlvw4CMtMGswFtfdK15" }, - "href": "https://api.spotify.com/v1/shows/5nk7d9MLCgE3M47mXPW7MP", - "id": "5nk7d9MLCgE3M47mXPW7MP", - "name": "The Missing Cryptoqueen", - "type": "show", - "uri": "spotify:show:5nk7d9MLCgE3M47mXPW7MP" + "href": "https://api.spotify.com/v1/artists/2S5hlvw4CMtMGswFtfdK15", + "id": "2S5hlvw4CMtMGswFtfdK15", + "name": "Royal Blood", + "type": "artist", + "uri": "spotify:artist:2S5hlvw4CMtMGswFtfdK15" } ], "available_markets": [ @@ -693,6 +268,7 @@ "AZ", "BA", "BB", + "BD", "BE", "BF", "BG", @@ -705,8 +281,11 @@ "BS", "BT", "BW", + "BY", "BZ", "CA", + "CD", + "CG", "CH", "CI", "CL", @@ -752,28 +331,35 @@ "IE", "IL", "IN", + "IQ", "IS", "IT", "JM", "JO", "JP", "KE", + "KG", "KH", "KI", "KM", "KN", + "KR", "KW", + "KZ", "LA", "LB", "LC", "LI", + "LK", "LR", "LS", "LT", "LU", "LV", + "LY", "MA", "MC", + "MD", "ME", "MG", "MH", @@ -803,6 +389,7 @@ "PE", "PG", "PH", + "PK", "PL", "PS", "PT", @@ -829,6 +416,7 @@ "TD", "TG", "TH", + "TJ", "TL", "TN", "TO", @@ -837,10 +425,13 @@ "TV", "TW", "TZ", + "UA", + "UG", "US", "UY", "UZ", "VC", + "VE", "VN", "VU", "WS", @@ -849,34 +440,33 @@ "ZM", "ZW" ], - "disc_number": 0, - "duration_ms": 2129000, + "disc_number": 1, + "duration_ms": 236933, "episode": false, - "explicit": true, + "explicit": false, "external_ids": { - "spotify": "https://open.spotify.com/episode/1WZevjwFcULC8HWySuaB3z" + "isrc": "GBAHT2001121" }, "external_urls": { - "spotify": "https://open.spotify.com/episode/1WZevjwFcULC8HWySuaB3z" + "spotify": "https://open.spotify.com/track/5aFGo8wHEntVxFI8IF7Wuj" }, - "href": "https://api.spotify.com/v1/episodes/1WZevjwFcULC8HWySuaB3z", - "id": "1WZevjwFcULC8HWySuaB3z", + "href": "https://api.spotify.com/v1/tracks/5aFGo8wHEntVxFI8IF7Wuj", + "id": "5aFGo8wHEntVxFI8IF7Wuj", "is_local": false, - "is_playable": false, - "name": "Episode 2: The Bitcoin Killer", - "popularity": 0, - "preview_url": null, + "name": "Typhoons", + "popularity": 58, + "preview_url": "https://p.scdn.co/mp3-preview/63fcc72c60ea00649d4eb475ca268228cb9814f3?cid=5edddae5c9964de3bda68780f222f7a6", "track": true, - "track_number": 0, - "type": "episode", - "uri": "spotify:episode:1WZevjwFcULC8HWySuaB3z" + "track_number": 3, + "type": "track", + "uri": "spotify:track:5aFGo8wHEntVxFI8IF7Wuj" }, "video_thumbnail": { "url": null } }, { - "added_at": "2022-03-22T17:18:21Z", + "added_at": "2022-05-20T14:10:39Z", "added_by": { "external_urls": { "spotify": "https://open.spotify.com/user/randomUser" @@ -890,17 +480,17 @@ "primary_color": null, "track": { "album": { - "album_type": "compilation", + "album_type": "single", "artists": [ { "external_urls": { - "spotify": "https://open.spotify.com/show/4Jgtgr4mHXNDyLldHkfEMz" + "spotify": "https://open.spotify.com/artist/6ekYAO2D1JkI58CF4uRRqw" }, - "href": "https://api.spotify.com/v1/shows/4Jgtgr4mHXNDyLldHkfEMz", - "id": "4Jgtgr4mHXNDyLldHkfEMz", - "name": "Command Line Heroes", - "type": "show", - "uri": "spotify:show:4Jgtgr4mHXNDyLldHkfEMz" + "href": "https://api.spotify.com/v1/artists/6ekYAO2D1JkI58CF4uRRqw", + "id": "6ekYAO2D1JkI58CF4uRRqw", + "name": "Tigercub", + "type": "artist", + "uri": "spotify:artist:6ekYAO2D1JkI58CF4uRRqw" } ], "available_markets": [ @@ -916,6 +506,7 @@ "AZ", "BA", "BB", + "BD", "BE", "BF", "BG", @@ -928,8 +519,11 @@ "BS", "BT", "BW", + "BY", "BZ", "CA", + "CD", + "CG", "CH", "CI", "CL", @@ -975,28 +569,35 @@ "IE", "IL", "IN", + "IQ", "IS", "IT", "JM", "JO", "JP", "KE", + "KG", "KH", "KI", "KM", "KN", + "KR", "KW", + "KZ", "LA", "LB", "LC", "LI", + "LK", "LR", "LS", "LT", "LU", "LV", + "LY", "MA", "MC", + "MD", "ME", "MG", "MH", @@ -1026,6 +627,7 @@ "PE", "PG", "PH", + "PK", "PL", "PS", "PT", @@ -1052,6 +654,7 @@ "TD", "TG", "TH", + "TJ", "TL", "TN", "TO", @@ -1060,10 +663,13 @@ "TV", "TW", "TZ", + "UA", + "UG", "US", "UY", "UZ", "VC", + "VE", "VN", "VU", "WS", @@ -1073,48 +679,44 @@ "ZW" ], "external_urls": { - "spotify": "https://open.spotify.com/show/4Jgtgr4mHXNDyLldHkfEMz" + "spotify": "https://open.spotify.com/album/2xe0gTFgZok8BDgUlkpRQ6" }, - "href": "https://api.spotify.com/v1/shows/4Jgtgr4mHXNDyLldHkfEMz", - "id": "4Jgtgr4mHXNDyLldHkfEMz", + "href": "https://api.spotify.com/v1/albums/2xe0gTFgZok8BDgUlkpRQ6", + "id": "2xe0gTFgZok8BDgUlkpRQ6", "images": [ { "height": 640, - "url": "https://i.scdn.co/image/ab6765630000ba8ad5dff299e9c86cd6126fea8f", + "url": "https://i.scdn.co/image/ab67616d0000b27303f861e63060e7d1ce63ca2d", "width": 640 }, { "height": 300, - "url": "https://i.scdn.co/image/ab67656300005f1fd5dff299e9c86cd6126fea8f", + "url": "https://i.scdn.co/image/ab67616d00001e0203f861e63060e7d1ce63ca2d", "width": 300 }, { "height": 64, - "url": "https://i.scdn.co/image/ab6765630000f68dd5dff299e9c86cd6126fea8f", + "url": "https://i.scdn.co/image/ab67616d0000485103f861e63060e7d1ce63ca2d", "width": 64 } ], - "is_playable": false, - "name": "Command Line Heroes", - "release_date": null, - "release_date_precision": null, - "restrictions": { - "reason": "product" - }, + "name": "Beauty", + "release_date": "2021-01-29", + "release_date_precision": "day", "total_tracks": 1, - "type": "show", - "uri": "spotify:show:4Jgtgr4mHXNDyLldHkfEMz" + "type": "album", + "uri": "spotify:album:2xe0gTFgZok8BDgUlkpRQ6" }, "artists": [ { "external_urls": { - "spotify": "https://open.spotify.com/show/4Jgtgr4mHXNDyLldHkfEMz" + "spotify": "https://open.spotify.com/artist/6ekYAO2D1JkI58CF4uRRqw" }, - "href": "https://api.spotify.com/v1/shows/4Jgtgr4mHXNDyLldHkfEMz", - "id": "4Jgtgr4mHXNDyLldHkfEMz", - "name": "Command Line Heroes", - "type": "show", - "uri": "spotify:show:4Jgtgr4mHXNDyLldHkfEMz" + "href": "https://api.spotify.com/v1/artists/6ekYAO2D1JkI58CF4uRRqw", + "id": "6ekYAO2D1JkI58CF4uRRqw", + "name": "Tigercub", + "type": "artist", + "uri": "spotify:artist:6ekYAO2D1JkI58CF4uRRqw" } ], "available_markets": [ @@ -1130,6 +732,7 @@ "AZ", "BA", "BB", + "BD", "BE", "BF", "BG", @@ -1142,8 +745,11 @@ "BS", "BT", "BW", + "BY", "BZ", "CA", + "CD", + "CG", "CH", "CI", "CL", @@ -1189,28 +795,35 @@ "IE", "IL", "IN", + "IQ", "IS", "IT", "JM", "JO", "JP", "KE", + "KG", "KH", "KI", "KM", "KN", + "KR", "KW", + "KZ", "LA", "LB", "LC", "LI", + "LK", "LR", "LS", "LT", "LU", "LV", + "LY", "MA", "MC", + "MD", "ME", "MG", "MH", @@ -1240,6 +853,7 @@ "PE", "PG", "PH", + "PK", "PL", "PS", "PT", @@ -1266,6 +880,7 @@ "TD", "TG", "TH", + "TJ", "TL", "TN", "TO", @@ -1274,10 +889,13 @@ "TV", "TW", "TZ", + "UA", + "UG", "US", "UY", "UZ", "VC", + "VE", "VN", "VU", "WS", @@ -1286,262 +904,26 @@ "ZM", "ZW" ], - "disc_number": 0, - "duration_ms": 1304111, - "episode": false, - "explicit": false, - "external_ids": { - "spotify": "https://open.spotify.com/episode/30HqApDClX0SFE53KJ7wvr" - }, - "external_urls": { - "spotify": "https://open.spotify.com/episode/30HqApDClX0SFE53KJ7wvr" - }, - "href": "https://api.spotify.com/v1/episodes/30HqApDClX0SFE53KJ7wvr", - "id": "30HqApDClX0SFE53KJ7wvr", - "is_local": false, - "is_playable": false, - "name": "Lurking Logic Bombs", - "popularity": 0, - "preview_url": "https://p.scdn.co/mp3-preview/c1c7d757eb6bd6415824720918d267390a54156d", - "track": true, - "track_number": 0, - "type": "episode", - "uri": "spotify:episode:30HqApDClX0SFE53KJ7wvr" - }, - "video_thumbnail": { - "url": null - } - }, - { - "added_at": "2022-03-22T17:19:02Z", - "added_by": { - "external_urls": { - "spotify": "https://open.spotify.com/user/randomUser" - }, - "href": "https://api.spotify.com/v1/users/randomUser", - "id": "randomUser", - "type": "user", - "uri": "spotify:user:randomUser" - }, - "is_local": false, - "primary_color": null, - "track": { - "album": { - "album_type": "compilation", - "artists": [ - { - "external_urls": { - "spotify": "https://open.spotify.com/show/4FYpq3lSeQMAhqNI81O0Cn" - }, - "href": "https://api.spotify.com/v1/shows/4FYpq3lSeQMAhqNI81O0Cn", - "id": "4FYpq3lSeQMAhqNI81O0Cn", - "name": "Planet Money", - "type": "show", - "uri": "spotify:show:4FYpq3lSeQMAhqNI81O0Cn" - } - ], - "available_markets": [ - "AD", - "AR", - "AT", - "AU", - "BE", - "BG", - "BO", - "BR", - "CA", - "CH", - "CL", - "CO", - "CR", - "CY", - "CZ", - "DE", - "DK", - "DO", - "EC", - "EE", - "ES", - "FI", - "FR", - "GB", - "GR", - "GT", - "HK", - "HN", - "HU", - "ID", - "IE", - "IL", - "IN", - "IS", - "IT", - "JP", - "LI", - "LT", - "LU", - "LV", - "MC", - "MT", - "MX", - "MY", - "NI", - "NL", - "NO", - "NZ", - "PA", - "PE", - "PH", - "PL", - "PT", - "PY", - "RO", - "SE", - "SG", - "SK", - "SV", - "TH", - "TR", - "TW", - "US", - "UY", - "VN", - "ZA" - ], - "external_urls": { - "spotify": "https://open.spotify.com/show/4FYpq3lSeQMAhqNI81O0Cn" - }, - "href": "https://api.spotify.com/v1/shows/4FYpq3lSeQMAhqNI81O0Cn", - "id": "4FYpq3lSeQMAhqNI81O0Cn", - "images": [ - { - "height": 640, - "url": "https://i.scdn.co/image/11d7b39883f704cfc6e9b1cf717d1b87ad238676", - "width": 640 - }, - { - "height": 300, - "url": "https://i.scdn.co/image/8feb7ba9f991af98307ae1de9c491c43754765dc", - "width": 300 - }, - { - "height": 64, - "url": "https://i.scdn.co/image/be7bdde2490c4a8ad5208b69080da2eaf70a79a5", - "width": 64 - } - ], - "is_playable": false, - "name": "Planet Money", - "release_date": null, - "release_date_precision": null, - "restrictions": { - "reason": "product" - }, - "total_tracks": 1, - "type": "show", - "uri": "spotify:show:4FYpq3lSeQMAhqNI81O0Cn" - }, - "artists": [ - { - "external_urls": { - "spotify": "https://open.spotify.com/show/4FYpq3lSeQMAhqNI81O0Cn" - }, - "href": "https://api.spotify.com/v1/shows/4FYpq3lSeQMAhqNI81O0Cn", - "id": "4FYpq3lSeQMAhqNI81O0Cn", - "name": "Planet Money", - "type": "show", - "uri": "spotify:show:4FYpq3lSeQMAhqNI81O0Cn" - } - ], - "available_markets": [ - "AD", - "AR", - "AT", - "AU", - "BE", - "BG", - "BO", - "BR", - "CA", - "CH", - "CL", - "CO", - "CR", - "CY", - "CZ", - "DE", - "DK", - "DO", - "EC", - "EE", - "ES", - "FI", - "FR", - "GB", - "GR", - "GT", - "HK", - "HN", - "HU", - "ID", - "IE", - "IL", - "IN", - "IS", - "IT", - "JP", - "LI", - "LT", - "LU", - "LV", - "MC", - "MT", - "MX", - "MY", - "NI", - "NL", - "NO", - "NZ", - "PA", - "PE", - "PH", - "PL", - "PT", - "PY", - "RO", - "SE", - "SG", - "SK", - "SV", - "TH", - "TR", - "TW", - "US", - "UY", - "VN", - "ZA" - ], - "disc_number": 0, - "duration_ms": 1101531, + "disc_number": 1, + "duration_ms": 220324, "episode": false, "explicit": false, "external_ids": { - "spotify": "https://open.spotify.com/episode/6Br4mVPbe9c5jvIddrnkOg" + "isrc": "QMFMF2010623" }, "external_urls": { - "spotify": "https://open.spotify.com/episode/6Br4mVPbe9c5jvIddrnkOg" + "spotify": "https://open.spotify.com/track/0j4FFgyRleA5IbWP4BmlIC" }, - "href": "https://api.spotify.com/v1/episodes/6Br4mVPbe9c5jvIddrnkOg", - "id": "6Br4mVPbe9c5jvIddrnkOg", + "href": "https://api.spotify.com/v1/tracks/0j4FFgyRleA5IbWP4BmlIC", + "id": "0j4FFgyRleA5IbWP4BmlIC", "is_local": false, - "is_playable": false, - "name": "Of oligarchs, oil and rubles", - "popularity": 0, - "preview_url": "https://p.scdn.co/mp3-preview/aedf7273252dd8a752b56cfbde51a97104f27bf3", + "name": "Beauty", + "popularity": 38, + "preview_url": "https://p.scdn.co/mp3-preview/b77ae05a5c30d8249504f5c2307fdd8cd44b4212?cid=5edddae5c9964de3bda68780f222f7a6", "track": true, - "track_number": 0, - "type": "episode", - "uri": "spotify:episode:6Br4mVPbe9c5jvIddrnkOg" + "track_number": 1, + "type": "track", + "uri": "spotify:track:0j4FFgyRleA5IbWP4BmlIC" }, "video_thumbnail": { "url": null @@ -1552,5 +934,5 @@ "next": null, "offset": 0, "previous": null, - "total": 4 + "total": 2 } \ No newline at end of file From 48a5d81170e5361f8b847ab1bca51b38fa9ea5ab Mon Sep 17 00:00:00 2001 From: xonstone <17579331+xonstone@users.noreply.github.com> Date: Fri, 20 May 2022 16:48:49 +0200 Subject: [PATCH 5/9] Expose additiona types, add default and test for override --- playlist.go | 3 ++- playlist_test.go | 14 ++++++++++++++ request_options.go | 12 ++++++------ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/playlist.go b/playlist.go index 49f7834..d33fbfb 100644 --- a/playlist.go +++ b/playlist.go @@ -254,7 +254,8 @@ type PlaylistItemPage struct { func (c *Client) GetPlaylistItems(ctx context.Context, playlistID ID, opts ...RequestOption) (*PlaylistItemPage, error) { spotifyURL := fmt.Sprintf("%splaylists/%s/tracks", c.baseURL, playlistID) - opts = append(opts, additionalTypes(episodeAdditionalType, trackAdditionalType)) + // Add default as the first option so it gets override by url.Values#Set + opts = append([]RequestOption{AdditionalTypes(EpisodeAdditionalType, TrackAdditionalType)}, opts...) if params := processOptions(opts...).urlParams.Encode(); params != "" { spotifyURL += "?" + params diff --git a/playlist_test.go b/playlist_test.go index 0435f61..2065376 100644 --- a/playlist_test.go +++ b/playlist_test.go @@ -243,6 +243,20 @@ func TestGetPlaylistItemsTracksAndEpisodes(t *testing.T) { } } +func TestGetPlaylistItemsOverride(t *testing.T) { + var types string + client, server := testClientString(http.StatusForbidden, "", func(r *http.Request) { + types = r.URL.Query().Get("additional_types") + }) + defer server.Close() + + _, _ = client.GetPlaylistItems(context.Background(), "playlistID", AdditionalTypes(EpisodeAdditionalType)) + + if types != "episode" { + t.Errorf("Expected additional type episode, got %s\n", types) + } +} + func TestUserFollowsPlaylist(t *testing.T) { client, server := testClientString(http.StatusOK, `[ true, false ]`) defer server.Close() diff --git a/request_options.go b/request_options.go index 3fb7251..d973f15 100644 --- a/request_options.go +++ b/request_options.go @@ -111,16 +111,16 @@ func Timerange(timerange Range) RequestOption { } } -type additionalType string +type AdditionalType string const ( - episodeAdditionalType = "episode" - trackAdditionalType = "track" + EpisodeAdditionalType = "episode" + TrackAdditionalType = "track" ) -// additionalTypes is a list of item types that your client supports besides the default track type. -// Valid types are: episodeAdditionalType and trackAdditionalType. -func additionalTypes(types ...additionalType) RequestOption { +// AdditionalTypes is a list of item types that your client supports besides the default track type. +// Valid types are: EpisodeAdditionalType and TrackAdditionalType. +func AdditionalTypes(types ...AdditionalType) RequestOption { strTypes := make([]string, len(types)) for i, t := range types { strTypes[i] = string(t) From a3b18a0cdfb89e2c9ea7c479b714e90c957f61cf Mon Sep 17 00:00:00 2001 From: xonstone <17579331+xonstone@users.noreply.github.com> Date: Fri, 20 May 2022 16:51:31 +0200 Subject: [PATCH 6/9] Add deprecation notice --- playlist.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/playlist.go b/playlist.go index d33fbfb..8a57bbd 100644 --- a/playlist.go +++ b/playlist.go @@ -170,6 +170,9 @@ func (c *Client) GetPlaylist(ctx context.Context, playlistID ID, opts ...Request // playlist's Spotify ID. // // Supported options: Limit, Offset, Market, Fields +// +// Deprecated: the Spotify api is moving towards supporting both tracks and episodes. Use GetPlaylistItems which +// supports these. func (c *Client) GetPlaylistTracks( ctx context.Context, playlistID ID, From 24ea47a512e8a859aa4fde3eb2c4d8fb9ce589a3 Mon Sep 17 00:00:00 2001 From: xonstone <17579331+xonstone@users.noreply.github.com> Date: Fri, 20 May 2022 17:00:01 +0200 Subject: [PATCH 7/9] Fix episode test --- playlist_test.go | 2 +- test_data/playlist_items_episodes.json | 281 +++++++++++++++++-------- 2 files changed, 192 insertions(+), 91 deletions(-) diff --git a/playlist_test.go b/playlist_test.go index 2065376..01c1510 100644 --- a/playlist_test.go +++ b/playlist_test.go @@ -165,7 +165,7 @@ func TestGetPlaylistItemsEpisodes(t *testing.T) { if err != nil { t.Error(err) } - if f := tm.Format(DateLayout); f != "2022-03-22" { + if f := tm.Format(DateLayout); f != "2022-05-20" { t.Errorf("Expected added at 2014-11-25, got %s\n", f) } } diff --git a/test_data/playlist_items_episodes.json b/test_data/playlist_items_episodes.json index dbd81e7..3d027c8 100644 --- a/test_data/playlist_items_episodes.json +++ b/test_data/playlist_items_episodes.json @@ -1,8 +1,8 @@ { - "href": "https://api.spotify.com/v1/playlists/23Ay9WfURne0LvtlCvYTkj/tracks?offset=0\u0026limit=100\u0026additional_types=episode", + "href": "https://api.spotify.com/v1/playlists/23Ay9WfURne0LvtlCvYTkj/tracks?offset=0&limit=100&additional_types=track%2Cepisode", "items": [ { - "added_at": "2022-03-22T17:17:01Z", + "added_at": "2022-05-20T14:58:16Z", "added_by": { "external_urls": { "spotify": "https://open.spotify.com/user/randomUser" @@ -24,7 +24,7 @@ "spotify": "https://open.spotify.com/episode/5Snektk5Z2nUzM7DgoEBSx" }, "href": "https://api.spotify.com/v1/episodes/5Snektk5Z2nUzM7DgoEBSx", - "html_description": "\u003cp\u003eThis episode we talk with a guy named “Drew” who gives us a rare peek into what some of the young hackers are up to today. From listening to Drew, we can see that times are changing for the motive behind hacking. In the ’90s and ’00s it was done for fun and curiosity. In the ’10s Anonymous showed us what Hacktivism is. And now, in the ’20s, the young hackers seem to be profit driven.\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003eSponsors\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003eSupport for this show comes from Linode. Linode supplies you with virtual servers. Visit linode.com/darknet and get a special offer.\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003eSupport for this show comes from Juniper Networks. Juniper Networks is dedicated to simplifying network operations and driving superior experiences for end users. Visit juniper.net/darknet to learn more about how Juniper Secure Edge can help you keep your remote workforce seamlessly secure wherever they are.\u003c/p\u003e", + "html_description": "

This episode we talk with a guy named “Drew” who gives us a rare peek into what some of the young hackers are up to today. From listening to Drew, we can see that times are changing for the motive behind hacking. In the ’90s and ’00s it was done for fun and curiosity. In the ’10s Anonymous showed us what Hacktivism is. And now, in the ’20s, the young hackers seem to be profit driven.




Sponsors


Support for this show comes from Linode. Linode supplies you with virtual servers. Visit linode.com/darknet and get a special offer.




Support for this show comes from Juniper Networks. Juniper Networks is dedicated to simplifying network operations and driving superior experiences for end users. Visit juniper.net/darknet to learn more about how Juniper Secure Edge can help you keep your remote workforce seamlessly secure wherever they are.

", "id": "5Snektk5Z2nUzM7DgoEBSx", "images": [ { @@ -272,7 +272,7 @@ } }, { - "added_at": "2022-03-22T17:17:28Z", + "added_at": "2022-05-20T14:58:20Z", "added_by": { "external_urls": { "spotify": "https://open.spotify.com/user/randomUser" @@ -285,46 +285,46 @@ "is_local": false, "primary_color": null, "track": { - "audio_preview_url": null, - "description": "Not all is as it seems with Dr Ruja's revolutionary cryptocurrency, OneCoin. The Missing Cryptoqueen is an eight-part series for BBC Sounds, with new episodes every Thursday. Presenter: Jamie Bartlett Producer: Georgia Catt Story consultant: Chris Berube Editor: Philip Sellars Original music and sound design: Phil Channell Original music and vocals: Dessislava Stefanova and the London Bulgarian Choir", - "duration_ms": 2129000, + "audio_preview_url": "https://p.scdn.co/mp3-preview/51a69faa68c2aa450f62f2a987f7c7b1ba2211c4", + "description": "Adam got a job doing IT work at a learning academy. He liked it and was happy there and feeling part of the team. But a strange series of events took him in another direction, that definitely didn’t make him happy. Sponsors Support for this show comes from Axonius. Securing assets — whether managed, unmanaged, ephemeral, or in the cloud — is a tricky task. The Axonius Cybersecurity Asset Management Platform correlates asset data from existing solutions to provide an always up-to-date inventory, uncover gaps, and automate action. Axonius gives IT and security teams the confidence to control complexity by mitigating threats, navigating risk, decreasing incidents, and informing business-level strategy — all while eliminating manual, repetitive tasks. Visit axonius.com/darknet to learn more and try it free. Support for this podcast comes from Cybereason. Cybereason reverses the attacker’s advantage and puts the power back in the defender’s hands. End cyber attacks. From endpoints to everywhere. Learn more at Cybereason.com/darknet. Support for this show comes from Varonis. Do you wonder what your company’s ransomware blast radius is? Varonis does a free cyber resilience assessment that tells you how many important files a compromised user could steal, whether anything would beep if they did, and a whole lot more. They actually do all the work – show you where your data is too open, if anyone is using it, and what you can lock down before attackers get inside. They also can detect behavior that looks like ransomware and stop it automatically. To learn more visit www.varonis.com/darknet.", + "duration_ms": 3253838, "episode": true, - "explicit": true, + "explicit": false, "external_urls": { - "spotify": "https://open.spotify.com/episode/1WZevjwFcULC8HWySuaB3z" + "spotify": "https://open.spotify.com/episode/4K9v2oOwGrM7oSiX1eivsc" }, - "href": "https://api.spotify.com/v1/episodes/1WZevjwFcULC8HWySuaB3z", - "html_description": "Not all is as it seems with Dr Ruja\u0026#39;s revolutionary cryptocurrency, OneCoin.\u003cbr/\u003eThe Missing Cryptoqueen is an eight-part series for BBC Sounds, with new episodes every Thursday.\u003cbr/\u003ePresenter: Jamie Bartlett\u003cbr/\u003eProducer: Georgia Catt\u003cbr/\u003eStory consultant: Chris Berube \u003cbr/\u003eEditor: Philip Sellars \u003cbr/\u003eOriginal music and sound design: Phil Channell\u003cbr/\u003eOriginal music and vocals: Dessislava Stefanova and the London Bulgarian Choir", - "id": "1WZevjwFcULC8HWySuaB3z", + "href": "https://api.spotify.com/v1/episodes/4K9v2oOwGrM7oSiX1eivsc", + "html_description": "

Adam got a job doing IT work at a learning academy. He liked it and was happy there and feeling part of the team. But a strange series of events took him in another direction, that definitely didn’t make him happy.


Sponsors

Support for this show comes from Axonius. Securing assets — whether managed, unmanaged, ephemeral, or in the cloud — is a tricky task. The Axonius Cybersecurity Asset Management Platform correlates asset data from existing solutions to provide an always up-to-date inventory, uncover gaps, and automate action. Axonius gives IT and security teams the confidence to control complexity by mitigating threats, navigating risk, decreasing incidents, and informing business-level strategy — all while eliminating manual, repetitive tasks. Visit axonius.com/darknet to learn more and try it free.


Support for this podcast comes from Cybereason. Cybereason reverses the attacker’s advantage and puts the power back in the defender’s hands. End cyber attacks. From endpoints to everywhere. Learn more at Cybereason.com/darknet.


Support for this show comes from Varonis. Do you wonder what your company’s ransomware blast radius is? Varonis does a free cyber resilience assessment that tells you how many important files a compromised user could steal, whether anything would beep if they did, and a whole lot more. They actually do all the work – show you where your data is too open, if anyone is using it, and what you can lock down before attackers get inside. They also can detect behavior that looks like ransomware and stop it automatically. To learn more visit www.varonis.com/darknet.

", + "id": "4K9v2oOwGrM7oSiX1eivsc", "images": [ { "height": 640, - "url": "https://i.scdn.co/image/b9f9e946ea8bd1955c57d5d391ff1cc728aa8963", + "url": "https://i.scdn.co/image/ab6765630000ba8a34f5a00172853ded018aa9ff", "width": 640 }, { "height": 300, - "url": "https://i.scdn.co/image/5493f2c236bf26699470e24b01696e5ea00e8a31", + "url": "https://i.scdn.co/image/ab67656300005f1f34f5a00172853ded018aa9ff", "width": 300 }, { "height": 64, - "url": "https://i.scdn.co/image/028f030786a5778a4a52f5966ae2817de2265823", + "url": "https://i.scdn.co/image/ab6765630000f68d34f5a00172853ded018aa9ff", "width": 64 } ], - "is_externally_hosted": true, + "is_externally_hosted": false, "is_playable": true, - "language": "en", + "language": "en-US", "languages": [ - "en" + "en-US" ], - "name": "Episode 2: The Bitcoin Killer", - "release_date": "2019-09-19", + "name": "113: Adam", + "release_date": "2022-03-22", "release_date_precision": "day", "resume_point": { "fully_played": false, - "resume_position_ms": 168000 + "resume_position_ms": 0 }, "show": { "available_markets": [ @@ -497,52 +497,52 @@ "ZW" ], "copyrights": [], - "description": "Dr Ruja Ignatova persuaded millions to join her financial revolution. Then she disappeared. Why? Jamie Bartlett presents a story of greed, deceit and herd madness.", + "description": "Explore true stories of the dark side of the Internet with host Jack Rhysider as he takes you on a journey through the chilling world of hacking, data breaches, and cyber crime.", "explicit": false, "external_urls": { - "spotify": "https://open.spotify.com/show/5nk7d9MLCgE3M47mXPW7MP" + "spotify": "https://open.spotify.com/show/4XPl3uEEL9hvqMkoZrzbx5" }, - "href": "https://api.spotify.com/v1/shows/5nk7d9MLCgE3M47mXPW7MP", + "href": "https://api.spotify.com/v1/shows/4XPl3uEEL9hvqMkoZrzbx5", "html_description": null, - "id": "5nk7d9MLCgE3M47mXPW7MP", + "id": "4XPl3uEEL9hvqMkoZrzbx5", "images": [ { "height": 640, - "url": "https://i.scdn.co/image/b9f9e946ea8bd1955c57d5d391ff1cc728aa8963", + "url": "https://i.scdn.co/image/ab6765630000ba8a11874ad24c1dcac2ace8d4c9", "width": 640 }, { "height": 300, - "url": "https://i.scdn.co/image/5493f2c236bf26699470e24b01696e5ea00e8a31", + "url": "https://i.scdn.co/image/ab67656300005f1f11874ad24c1dcac2ace8d4c9", "width": 300 }, { "height": 64, - "url": "https://i.scdn.co/image/028f030786a5778a4a52f5966ae2817de2265823", + "url": "https://i.scdn.co/image/ab6765630000f68d11874ad24c1dcac2ace8d4c9", "width": 64 } ], - "is_externally_hosted": true, + "is_externally_hosted": false, "languages": [ "en" ], "media_type": "audio", - "name": "The Missing Cryptoqueen", - "publisher": "BBC Radio 5 live", - "total_episodes": 12, + "name": "Darknet Diaries", + "publisher": "Jack Rhysider", + "total_episodes": 117, "type": "show", - "uri": "spotify:show:5nk7d9MLCgE3M47mXPW7MP" + "uri": "spotify:show:4XPl3uEEL9hvqMkoZrzbx5" }, "track": false, "type": "episode", - "uri": "spotify:episode:1WZevjwFcULC8HWySuaB3z" + "uri": "spotify:episode:4K9v2oOwGrM7oSiX1eivsc" }, "video_thumbnail": { "url": null } }, { - "added_at": "2022-03-22T17:18:21Z", + "added_at": "2022-05-20T14:58:25Z", "added_by": { "external_urls": { "spotify": "https://open.spotify.com/user/randomUser" @@ -555,42 +555,42 @@ "is_local": false, "primary_color": null, "track": { - "audio_preview_url": "https://p.scdn.co/mp3-preview/c1c7d757eb6bd6415824720918d267390a54156d", - "description": "Logic bombs rarely have warning sounds. The victims mostly don’t know to expect one. And even when a logic bomb is discovered before it’s triggered, there isn’t always enough time to defuse it. But there are ways to stop them in time. Paul Ducklin recounts the race to defuse the CIH logic bomb—and the horrible realization of how widespread it was. Costin Raiu explains how logic bombs get planted, and all the different kinds of damage they can do. And Manuel Egele shares some strategies for detecting logic bombs before their conditions are met.If you want to read up on some of our research on logic bombs, you can check out all our bonus material over at redhat.com/commandlineheroes. Follow along with the episode transcript.  ", - "duration_ms": 1304111, + "audio_preview_url": "https://p.scdn.co/mp3-preview/7103b353fb65f620a30270f41ceb8a0d57549b53", + "description": "HD Moore (https://twitter.com/hdmoore) invented a hacking tool called Metasploit. He crammed it with tons of exploits and payloads that can be used to hack into computers. What could possibly go wrong? Learn more about what HD does today by visiting rumble.run/. Sponsors Support for this show comes from Quorum Cyber. They exist to defend organisations against cyber security breaches and attacks. That’s it. No noise. No hard sell. If you’re looking for a partner to help you reduce risk and defend against the threats that are targeting your business — and specially if you are interested in Microsoft Security - reach out to www.quorumcyber.com. Support for this show comes from Snyk. Snyk is a developer security platform that helps you secure your applications from the start. It automatically scans your code, dependencies, containers, and cloud infrastructure configs — finding and fixing vulnerabilities in real time. And Snyk does it all right from the existing tools and workflows you already use. IDEs, CLI, repos, pipelines, Docker Hub, and more — so your work isn’t interrupted. Create your free account at snyk.co/darknet.", + "duration_ms": 4553665, "episode": true, "explicit": false, "external_urls": { - "spotify": "https://open.spotify.com/episode/30HqApDClX0SFE53KJ7wvr" + "spotify": "https://open.spotify.com/episode/6cEoKKdjMH2NlDVtzjE18I" }, - "href": "https://api.spotify.com/v1/episodes/30HqApDClX0SFE53KJ7wvr", - "html_description": "\u003cp\u003eLogic bombs rarely have warning sounds. The victims mostly don’t know to expect one. And even when a logic bomb is discovered before it’s triggered, there isn’t always enough time to defuse it. But there are ways to stop them in time. \u003c/p\u003e\u003cp\u003ePaul Ducklin recounts the race to defuse the CIH logic bomb—and the horrible realization of how widespread it was. Costin Raiu explains how logic bombs get planted, and all the different kinds of damage they can do. And Manuel Egele shares some strategies for detecting logic bombs before their conditions are met.\u003c/p\u003e\u003cp\u003eIf you want to read up on some of our research on logic bombs, you can check out all our bonus material over at \u003ca href=\"https://www.redhat.com/en/command-line-heroes/season-9/logic-bombs\" rel=\"nofollow\"\u003eredhat.com/commandlineheroes\u003c/a\u003e. Follow along with the episode transcript. \u003c/p\u003e\u003cp\u003e \u003c/p\u003e", - "id": "30HqApDClX0SFE53KJ7wvr", + "href": "https://api.spotify.com/v1/episodes/6cEoKKdjMH2NlDVtzjE18I", + "html_description": "

HD Moore (https://twitter.com/hdmoore) invented a hacking tool called Metasploit. He crammed it with tons of exploits and payloads that can be used to hack into computers. What could possibly go wrong? Learn more about what HD does today by visiting rumble.run/.


Sponsors

Support for this show comes from Quorum Cyber. They exist to defend organisations against cyber security breaches and attacks. That’s it. No noise. No hard sell. If you’re looking for a partner to help you reduce risk and defend against the threats that are targeting your business — and specially if you are interested in Microsoft Security - reach out to www.quorumcyber.com.


Support for this show comes from Snyk. Snyk is a developer security platform that helps you secure your applications from the start. It automatically scans your code, dependencies, containers, and cloud infrastructure configs — finding and fixing vulnerabilities in real time. And Snyk does it all right from the existing tools and workflows you already use. IDEs, CLI, repos, pipelines, Docker Hub, and more — so your work isn’t interrupted. Create your free account at snyk.co/darknet.

", + "id": "6cEoKKdjMH2NlDVtzjE18I", "images": [ { "height": 640, - "url": "https://i.scdn.co/image/ab6765630000ba8af7dfa6a2e2caac2387c846d0", + "url": "https://i.scdn.co/image/ab6765630000ba8ae93e0b41480d0560726ec2d1", "width": 640 }, { "height": 300, - "url": "https://i.scdn.co/image/ab67656300005f1ff7dfa6a2e2caac2387c846d0", + "url": "https://i.scdn.co/image/ab67656300005f1fe93e0b41480d0560726ec2d1", "width": 300 }, { "height": 64, - "url": "https://i.scdn.co/image/ab6765630000f68df7dfa6a2e2caac2387c846d0", + "url": "https://i.scdn.co/image/ab6765630000f68de93e0b41480d0560726ec2d1", "width": 64 } ], "is_externally_hosted": false, "is_playable": true, - "language": "en", + "language": "en-US", "languages": [ - "en" + "en-US" ], - "name": "Lurking Logic Bombs", - "release_date": "2022-03-22", + "name": "114: HD", + "release_date": "2022-04-05", "release_date_precision": "day", "resume_point": { "fully_played": false, @@ -767,28 +767,28 @@ "ZW" ], "copyrights": [], - "description": "Stories about the people transforming technology from the command line up.", + "description": "Explore true stories of the dark side of the Internet with host Jack Rhysider as he takes you on a journey through the chilling world of hacking, data breaches, and cyber crime.", "explicit": false, "external_urls": { - "spotify": "https://open.spotify.com/show/4Jgtgr4mHXNDyLldHkfEMz" + "spotify": "https://open.spotify.com/show/4XPl3uEEL9hvqMkoZrzbx5" }, - "href": "https://api.spotify.com/v1/shows/4Jgtgr4mHXNDyLldHkfEMz", + "href": "https://api.spotify.com/v1/shows/4XPl3uEEL9hvqMkoZrzbx5", "html_description": null, - "id": "4Jgtgr4mHXNDyLldHkfEMz", + "id": "4XPl3uEEL9hvqMkoZrzbx5", "images": [ { "height": 640, - "url": "https://i.scdn.co/image/ab6765630000ba8ad5dff299e9c86cd6126fea8f", + "url": "https://i.scdn.co/image/ab6765630000ba8a11874ad24c1dcac2ace8d4c9", "width": 640 }, { "height": 300, - "url": "https://i.scdn.co/image/ab67656300005f1fd5dff299e9c86cd6126fea8f", + "url": "https://i.scdn.co/image/ab67656300005f1f11874ad24c1dcac2ace8d4c9", "width": 300 }, { "height": 64, - "url": "https://i.scdn.co/image/ab6765630000f68dd5dff299e9c86cd6126fea8f", + "url": "https://i.scdn.co/image/ab6765630000f68d11874ad24c1dcac2ace8d4c9", "width": 64 } ], @@ -797,22 +797,22 @@ "en" ], "media_type": "audio", - "name": "Command Line Heroes", - "publisher": "Red Hat", - "total_episodes": 76, + "name": "Darknet Diaries", + "publisher": "Jack Rhysider", + "total_episodes": 117, "type": "show", - "uri": "spotify:show:4Jgtgr4mHXNDyLldHkfEMz" + "uri": "spotify:show:4XPl3uEEL9hvqMkoZrzbx5" }, "track": false, "type": "episode", - "uri": "spotify:episode:30HqApDClX0SFE53KJ7wvr" + "uri": "spotify:episode:6cEoKKdjMH2NlDVtzjE18I" }, "video_thumbnail": { "url": null } }, { - "added_at": "2022-03-22T17:19:02Z", + "added_at": "2022-05-20T14:58:30Z", "added_by": { "external_urls": { "spotify": "https://open.spotify.com/user/randomUser" @@ -825,42 +825,42 @@ "is_local": false, "primary_color": null, "track": { - "audio_preview_url": "https://p.scdn.co/mp3-preview/aedf7273252dd8a752b56cfbde51a97104f27bf3", - "description": "Three stories about how the sanctions imposed on Russia are playing out – for regular Russian people, for Russia's super-rich, and for Russia's energy exports. | Subscribe to our weekly newsletter here.", - "duration_ms": 1101531, + "audio_preview_url": "https://p.scdn.co/mp3-preview/a02a9a07c09bc940e684c42c35238a786975e914", + "description": "Some video game players buy cheats to win. Let’s take a look at this game cheating industry to see who the players are. Sponsors Support for this show comes from Axonius. Securing assets — whether managed, unmanaged, ephemeral, or in the cloud — is a tricky task. The Axonius Cybersecurity Asset Management Platform correlates asset data from existing solutions to provide an always up-to-date inventory, uncover gaps, and automate action. Axonius gives IT and security teams the confidence to control complexity by mitigating threats, navigating risk, decreasing incidents, and informing business-level strategy — all while eliminating manual, repetitive tasks. Visit axonius.com/darknet to learn more and try it free. Support for this podcast comes from Cybereason. Cybereason reverses the attacker’s advantage and puts the power back in the defender’s hands. End cyber attacks. From endpoints to everywhere. Learn more at Cybereason.com/darknet.", + "duration_ms": 2443102, "episode": true, - "explicit": false, + "explicit": true, "external_urls": { - "spotify": "https://open.spotify.com/episode/6Br4mVPbe9c5jvIddrnkOg" + "spotify": "https://open.spotify.com/episode/2DLn0x8I0w6xR0YhIXO2h7" }, - "href": "https://api.spotify.com/v1/episodes/6Br4mVPbe9c5jvIddrnkOg", - "html_description": "Three stories about how the sanctions imposed on Russia are playing out – for regular Russian people, for Russia\u0026#39;s super-rich, and for Russia\u0026#39;s energy exports. | Subscribe to our weekly newsletter \u003ca href=\"https://www.npr.org/newsletter/money?utm_source\u0026#61;rss_feed_copy\u0026amp;utm_medium\u0026#61;podcast\u0026amp;utm_term\u0026#61;planet_money\" rel=\"nofollow\"\u003ehere\u003c/a\u003e.", - "id": "6Br4mVPbe9c5jvIddrnkOg", + "href": "https://api.spotify.com/v1/episodes/2DLn0x8I0w6xR0YhIXO2h7", + "html_description": "

Some video game players buy cheats to win. Let’s take a look at this game cheating industry to see who the players are.


Sponsors

Support for this show comes from Axonius. Securing assets — whether managed, unmanaged, ephemeral, or in the cloud — is a tricky task. The Axonius Cybersecurity Asset Management Platform correlates asset data from existing solutions to provide an always up-to-date inventory, uncover gaps, and automate action. Axonius gives IT and security teams the confidence to control complexity by mitigating threats, navigating risk, decreasing incidents, and informing business-level strategy — all while eliminating manual, repetitive tasks. Visit axonius.com/darknet to learn more and try it free.


Support for this podcast comes from Cybereason. Cybereason reverses the attacker’s advantage and puts the power back in the defender’s hands. End cyber attacks. From endpoints to everywhere. Learn more at Cybereason.com/darknet.

", + "id": "2DLn0x8I0w6xR0YhIXO2h7", "images": [ { "height": 640, - "url": "https://i.scdn.co/image/ab6765630000ba8af12f52c396e196b106772127", + "url": "https://i.scdn.co/image/ab6765630000ba8af93599018aca48d816e95657", "width": 640 }, { "height": 300, - "url": "https://i.scdn.co/image/ab67656300005f1ff12f52c396e196b106772127", + "url": "https://i.scdn.co/image/ab67656300005f1ff93599018aca48d816e95657", "width": 300 }, { "height": 64, - "url": "https://i.scdn.co/image/ab6765630000f68df12f52c396e196b106772127", + "url": "https://i.scdn.co/image/ab6765630000f68df93599018aca48d816e95657", "width": 64 } ], "is_externally_hosted": false, "is_playable": true, - "language": "en", + "language": "en-US", "languages": [ - "en" + "en-US" ], - "name": "Of oligarchs, oil and rubles", - "release_date": "2022-03-05", + "name": "115: Player Cheater Developer Spy", + "release_date": "2022-04-19", "release_date_precision": "day", "resume_point": { "fully_played": false, @@ -869,33 +869,71 @@ "show": { "available_markets": [ "AD", + "AE", + "AG", + "AL", + "AM", + "AO", "AR", "AT", "AU", + "AZ", + "BA", + "BB", "BE", + "BF", "BG", + "BH", + "BI", + "BJ", + "BN", "BO", "BR", + "BS", + "BT", + "BW", + "BZ", "CA", "CH", + "CI", "CL", + "CM", "CO", "CR", + "CV", + "CW", "CY", "CZ", "DE", + "DJ", "DK", + "DM", "DO", + "DZ", "EC", "EE", + "EG", "ES", "FI", + "FJ", + "FM", "FR", + "GA", "GB", + "GD", + "GE", + "GH", + "GM", + "GN", + "GQ", "GR", "GT", + "GW", + "GY", "HK", "HN", + "HR", + "HT", "HU", "ID", "IE", @@ -903,61 +941,124 @@ "IN", "IS", "IT", + "JM", + "JO", "JP", + "KE", + "KH", + "KI", + "KM", + "KN", + "KW", + "LA", + "LB", + "LC", "LI", + "LR", + "LS", "LT", "LU", "LV", + "MA", "MC", + "ME", + "MG", + "MH", + "MK", + "ML", + "MN", + "MO", + "MR", "MT", + "MU", + "MV", + "MW", "MX", "MY", + "MZ", + "NA", + "NE", + "NG", "NI", "NL", "NO", + "NP", + "NR", "NZ", + "OM", "PA", "PE", + "PG", "PH", "PL", + "PS", "PT", + "PW", "PY", + "QA", "RO", + "RS", + "RW", + "SA", + "SB", + "SC", "SE", "SG", + "SI", "SK", + "SL", + "SM", + "SN", + "SR", + "ST", "SV", + "SZ", + "TD", + "TG", "TH", + "TL", + "TN", + "TO", "TR", + "TT", + "TV", "TW", + "TZ", "US", "UY", + "UZ", + "VC", "VN", - "ZA" + "VU", + "WS", + "XK", + "ZA", + "ZM", + "ZW" ], "copyrights": [], - "description": "The economy explained. Imagine you could call up a friend and say, \"Meet me at the bar and tell me what's going on with the economy.\" Now imagine that's actually a fun evening.Want to really power up your fandom? Try Planet Money Plus. Your subscription supports the show and unlocks a sponsor-free feed. Learn more at plus.npr.org/planetmoney", + "description": "Explore true stories of the dark side of the Internet with host Jack Rhysider as he takes you on a journey through the chilling world of hacking, data breaches, and cyber crime.", "explicit": false, "external_urls": { - "spotify": "https://open.spotify.com/show/4FYpq3lSeQMAhqNI81O0Cn" + "spotify": "https://open.spotify.com/show/4XPl3uEEL9hvqMkoZrzbx5" }, - "href": "https://api.spotify.com/v1/shows/4FYpq3lSeQMAhqNI81O0Cn", + "href": "https://api.spotify.com/v1/shows/4XPl3uEEL9hvqMkoZrzbx5", "html_description": null, - "id": "4FYpq3lSeQMAhqNI81O0Cn", + "id": "4XPl3uEEL9hvqMkoZrzbx5", "images": [ { "height": 640, - "url": "https://i.scdn.co/image/11d7b39883f704cfc6e9b1cf717d1b87ad238676", + "url": "https://i.scdn.co/image/ab6765630000ba8a11874ad24c1dcac2ace8d4c9", "width": 640 }, { "height": 300, - "url": "https://i.scdn.co/image/8feb7ba9f991af98307ae1de9c491c43754765dc", + "url": "https://i.scdn.co/image/ab67656300005f1f11874ad24c1dcac2ace8d4c9", "width": 300 }, { "height": 64, - "url": "https://i.scdn.co/image/be7bdde2490c4a8ad5208b69080da2eaf70a79a5", + "url": "https://i.scdn.co/image/ab6765630000f68d11874ad24c1dcac2ace8d4c9", "width": 64 } ], @@ -966,15 +1067,15 @@ "en" ], "media_type": "audio", - "name": "Planet Money", - "publisher": "NPR", - "total_episodes": 355, + "name": "Darknet Diaries", + "publisher": "Jack Rhysider", + "total_episodes": 117, "type": "show", - "uri": "spotify:show:4FYpq3lSeQMAhqNI81O0Cn" + "uri": "spotify:show:4XPl3uEEL9hvqMkoZrzbx5" }, "track": false, "type": "episode", - "uri": "spotify:episode:6Br4mVPbe9c5jvIddrnkOg" + "uri": "spotify:episode:2DLn0x8I0w6xR0YhIXO2h7" }, "video_thumbnail": { "url": null From de65b0210b3c7a154a83b6d42187df8c278763cb Mon Sep 17 00:00:00 2001 From: xonstone <17579331+xonstone@users.noreply.github.com> Date: Fri, 20 May 2022 17:11:47 +0200 Subject: [PATCH 8/9] Typo --- playlist.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playlist.go b/playlist.go index 8a57bbd..b5d7ca7 100644 --- a/playlist.go +++ b/playlist.go @@ -244,7 +244,7 @@ func (t *PlaylistItemTrack) UnmarshalJSON(b []byte) error { return nil } -// PlaylistItemPage contains information about tracks in a playlist. +// PlaylistItemPage contains information about items in a playlist. type PlaylistItemPage struct { basePage Items []PlaylistItem `json:"items"` From 40b2aa34464d869d5120d729ed71dd21755183b4 Mon Sep 17 00:00:00 2001 From: xonstone <17579331+xonstone@users.noreply.github.com> Date: Fri, 20 May 2022 18:01:44 +0200 Subject: [PATCH 9/9] Added test for default case --- playlist_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/playlist_test.go b/playlist_test.go index 01c1510..1716d8f 100644 --- a/playlist_test.go +++ b/playlist_test.go @@ -257,6 +257,20 @@ func TestGetPlaylistItemsOverride(t *testing.T) { } } +func TestGetPlaylistItemsDefault(t *testing.T) { + var types string + client, server := testClientString(http.StatusForbidden, "", func(r *http.Request) { + types = r.URL.Query().Get("additional_types") + }) + defer server.Close() + + _, _ = client.GetPlaylistItems(context.Background(), "playlistID") + + if types != "episode,track" { + t.Errorf("Expected additional type episode, got %s\n", types) + } +} + func TestUserFollowsPlaylist(t *testing.T) { client, server := testClientString(http.StatusOK, `[ true, false ]`) defer server.Close()