diff --git a/player.go b/player.go index 142e2da..82e2ef4 100755 --- a/player.go +++ b/player.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "net/http" "net/url" "strconv" @@ -61,7 +62,46 @@ type CurrentlyPlaying struct { // Playing If something is currently playing. Playing bool `json:"is_playing"` // The currently playing track. Can be null. - Item *FullTrack `json:"item"` + Item *CurrentlyPlayingTrack `json:"item"` +} + +// CurrentlyPlayingTrack is a union type for both a track and and episode. +type CurrentlyPlayingTrack struct { + Track *FullTrack + Episode *EpisodePage +} + +// UnmarshalJSON customises the unmarshalling based on the type flags set. +func (t *CurrentlyPlayingTrack) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte("null")) { + return nil + } + + itemType := struct { + Type string `json:"type"` + }{} + + err := json.Unmarshal(b, &itemType) + if err != nil { + return err + } + + switch itemType.Type { + case "episode": + err := json.Unmarshal(b, &t.Episode) + if err != nil { + return err + } + case "track": + err := json.Unmarshal(b, &t.Track) + if err != nil { + return err + } + default: + return fmt.Errorf("unrecognized item type: %s", itemType.Type) + } + + return nil } type RecentlyPlayedItem struct { @@ -178,10 +218,13 @@ func (c *Client) PlayerState(ctx context.Context, opts ...RequestOption) (*Playe // Requires the ScopeUserReadCurrentlyPlaying scope or the ScopeUserReadPlaybackState // scope in order to read information // -// Supported options: Market +// Supported options: Market, AdditionalTypes func (c *Client) PlayerCurrentlyPlaying(ctx context.Context, opts ...RequestOption) (*CurrentlyPlaying, error) { spotifyURL := c.baseURL + "me/player/currently-playing" + // 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/player_test.go b/player_test.go index 00edb1e..8a66d15 100644 --- a/player_test.go +++ b/player_test.go @@ -128,6 +128,59 @@ func TestPlayerCurrentlyPlaying(t *testing.T) { } } +func TestPlayerCurrentlyPlayingEpisode(t *testing.T) { + client, server := testClientFile(http.StatusOK, "test_data/player_currently_playing_episode.json") + defer server.Close() + + current, err := client.PlayerCurrentlyPlaying(context.Background()) + if err != nil { + t.Error(err) + return + } + + if current.Item == nil { + t.Error("Expected item to be a episode") + } + + expectedName := "300 multiple choices" + actualName := current.Item.Episode.Name + if expectedName != actualName { + t.Errorf("Got '%s', expected '%s'\n", actualName, expectedName) + } + + if current.Playing { + t.Error("Expected not to be playing") + } +} + +func TestPlayerCurrentlyPlayingOverride(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.PlayerCurrentlyPlaying(context.Background(), AdditionalTypes(EpisodeAdditionalType)) + + if types != "episode" { + t.Errorf("Expected additional type episode, got %s\n", types) + } +} + +func TestPlayerCurrentlyPlayingDefault(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.PlayerCurrentlyPlaying(context.Background()) + + if types != "episode,track" { + t.Errorf("Expected additional type episode,track, got %s\n", types) + } +} + func TestPlayerRecentlyPlayed(t *testing.T) { client, server := testClientFile(http.StatusOK, "test_data/player_recently_played.txt") defer server.Close() diff --git a/test_data/player_currently_playing_episode.json b/test_data/player_currently_playing_episode.json new file mode 100644 index 0000000..ec14708 --- /dev/null +++ b/test_data/player_currently_playing_episode.json @@ -0,0 +1,237 @@ +{ + "context": null, + "timestamp": 1706351396175, + "progress_ms": 79799, + "is_playing": false, + "item": { + "audio_preview_url": "https://podz-content.spotifycdn.com/audio/clips/0o5xSNKJenESDGRQYklFdz/clip_3271600_3331750.mp3", + "description": "Over the past 8 years, Go Time has published 300 episodes! In this episode, the panel discusses which ones they loved the most, some current stuff that’s in the works, what struggles the podcast has had & what we’re planning for the future.", + "html_description": "
Over the past 8 years, Go Time has published 300 episodes! In this episode, the panel discusses which ones they loved the most, some current stuff that’s in the works, what struggles the podcast has had & what we’re planning for the future.
Changelog++ members save 2 minutes on this episode because they made the ads disappear. Join today!
Sponsors:
Featuring:
Show Notes:
Something missing or broken? PRs welcome!
", + "duration_ms": 6672328, + "explicit": false, + "external_urls": { + "spotify": "https://open.spotify.com/episode/0N1dkr7thtEBhk059opmp9" + }, + "href": "https://api.spotify.com/v1/episodes/0N1dkr7thtEBhk059opmp9", + "id": "0N1dkr7thtEBhk059opmp9", + "images": [ + { + "url": "https://i.scdn.co/image/18d9cacf366a7173b10204eb983fe5d374b728dd", + "height": 640, + "width": 640 + }, + { + "url": "https://i.scdn.co/image/3f9b1dd55b3353a0d70dfcca6a44c01775cfbfcc", + "height": 300, + "width": 300 + }, + { + "url": "https://i.scdn.co/image/aa9e23de8a5a64bedaf526bb963123413984e53f", + "height": 64, + "width": 64 + } + ], + "is_externally_hosted": false, + "is_playable": true, + "language": "en-US", + "languages": [ + "en-US" + ], + "name": "300 multiple choices", + "release_date": "2024-01-23", + "release_date_precision": "day", + "type": "episode", + "uri": "spotify:episode:0N1dkr7thtEBhk059opmp9", + "show": { + "available_markets": [ + "AD", + "AE", + "AG", + "AL", + "AM", + "AR", + "AT", + "AU", + "BA", + "BB", + "BE", + "BF", + "BG", + "BH", + "BJ", + "BO", + "BR", + "BS", + "BT", + "BW", + "BZ", + "CA", + "CH", + "CL", + "CO", + "CR", + "CV", + "CW", + "CY", + "CZ", + "DE", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "ES", + "FI", + "FJ", + "FM", + "FR", + "GB", + "GD", + "GE", + "GH", + "GM", + "GR", + "GT", + "GW", + "GY", + "HK", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IN", + "IS", + "IT", + "JM", + "JO", + "JP", + "KE", + "KI", + "KN", + "KR", + "KW", + "LB", + "LC", + "LI", + "LR", + "LS", + "LT", + "LU", + "LV", + "MA", + "MC", + "ME", + "MG", + "MH", + "MK", + "ML", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NE", + "NG", + "NI", + "NL", + "NO", + "NR", + "NZ", + "OM", + "PA", + "PE", + "PG", + "PH", + "PL", + "PS", + "PT", + "PW", + "PY", + "QA", + "RO", + "RS", + "SA", + "SB", + "SC", + "SE", + "SG", + "SI", + "SK", + "SL", + "SM", + "SN", + "SR", + "ST", + "SV", + "TH", + "TL", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "UA", + "US", + "UY", + "VC", + "VN", + "VU", + "WS", + "XK", + "ZA" + ], + "copyrights": [], + "description": "Your source for diverse discussions from around the Go community. This show records LIVE every Tuesday at 3pm US Eastern. Join the Golang community and chat with us during the show in the #gotimefm channel of Gophers slack. Panelists include Mat Ryer, Jon Calhoun, Natalie Pistunovich, Johnny Boursiquot, Angelica Hill, Kris Brandow, and Ian Lopshire. We discuss cloud infrastructure, distributed systems, microservices, Kubernetes, Docker… oh and also Go! Some people search for GoTime or GoTimeFM and can’t find the show, so now the strings GoTime and GoTimeFM are in our description too.", + "html_description": "Your source for diverse discussions from around the Go community. This show records LIVE every Tuesday at 3pm US Eastern. Join the Golang community and chat with us during the show in the #gotimefm channel of Gophers slack. Panelists include Mat Ryer, Jon Calhoun, Natalie Pistunovich, Johnny Boursiquot, Angelica Hill, Kris Brandow, and Ian Lopshire. We discuss cloud infrastructure, distributed systems, microservices, Kubernetes, Docker… oh and also Go! Some people search for GoTime or GoTimeFM and can’t find the show, so now the strings GoTime and GoTimeFM are in our description too.", + "explicit": false, + "external_urls": { + "spotify": "https://open.spotify.com/show/2cKdcxETn7jDp7uJCwqmSE" + }, + "href": "https://api.spotify.com/v1/shows/2cKdcxETn7jDp7uJCwqmSE", + "id": "2cKdcxETn7jDp7uJCwqmSE", + "images": [ + { + "url": "https://i.scdn.co/image/18d9cacf366a7173b10204eb983fe5d374b728dd", + "height": 640, + "width": 640 + }, + { + "url": "https://i.scdn.co/image/3f9b1dd55b3353a0d70dfcca6a44c01775cfbfcc", + "height": 300, + "width": 300 + }, + { + "url": "https://i.scdn.co/image/aa9e23de8a5a64bedaf526bb963123413984e53f", + "height": 64, + "width": 64 + } + ], + "is_externally_hosted": false, + "languages": [ + "en-US" + ], + "media_type": "audio", + "name": "Go Time: Golang, Software Engineering", + "publisher": "Changelog Media", + "type": "show", + "uri": "spotify:show:2cKdcxETn7jDp7uJCwqmSE", + "total_episodes": 307 + } + }, + "currently_playing_type": "episode", + "actions": { + "disallows": { + "resuming": true, + "skipping_prev": true + } + } +} \ No newline at end of file