diff --git a/backends/spotify/client.go b/backends/spotify/client.go index 13a5af2..6e51774 100644 --- a/backends/spotify/client.go +++ b/backends/spotify/client.go @@ -68,13 +68,27 @@ func (c Client) recentlyPlayed(after *time.Time, before *time.Time, limit int) ( request.SetQueryParam("after", strconv.FormatInt(after.Unix(), 10)) } else if before != nil { request.SetQueryParam("before", strconv.FormatInt(before.Unix(), 10)) - } response, err := request.Get(path) if response.StatusCode() != 200 { err = errors.New(response.String()) - return + } + return +} + +func (c Client) UserTracks(offset int, limit int) (result TracksResult, err error) { + const path = "/me/tracks" + response, err := c.HttpClient.R(). + SetQueryParams(map[string]string{ + "offset": strconv.Itoa(offset), + "limit": strconv.Itoa(limit), + }). + SetResult(&result). + Get(path) + + if response.StatusCode() != 200 { + err = errors.New(response.String()) } return } diff --git a/backends/spotify/client_test.go b/backends/spotify/client_test.go index 5e16c0e..780830c 100644 --- a/backends/spotify/client_test.go +++ b/backends/spotify/client_test.go @@ -22,9 +22,13 @@ THE SOFTWARE. package spotify_test import ( + "net/http" "testing" + "time" + "github.com/jarcoal/httpmock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uploadedlobster.com/scotty/backends/spotify" "golang.org/x/oauth2" ) @@ -35,3 +39,53 @@ func TestNewClient(t *testing.T) { client := spotify.NewClient(conf, token) assert.IsType(t, spotify.Client{}, client) } + +func TestRecentlyPlayedAfter(t *testing.T) { + defer httpmock.DeactivateAndReset() + + client := spotify.NewClient(oauth2.Config{}, nil) + setupHttpMock(t, client.HttpClient.GetClient(), + "https://api.spotify.com/v1/me/player/recently-played", + "testdata/recently-played.json") + + result, err := client.RecentlyPlayedAfter(time.Now(), 3) + require.NoError(t, err) + + assert := assert.New(t) + assert.Equal(3, result.Limit) + require.Len(t, result.Items, 3) + listen1 := result.Items[0] + assert.Equal("2023-11-21T15:00:07.229Z", listen1.PlayedAt) + assert.Equal("Evidence", listen1.Track.Name) + assert.Equal("Viva Emptiness", listen1.Track.Album.Name) +} + +func TestGetUserTracks(t *testing.T) { + defer httpmock.DeactivateAndReset() + + client := spotify.NewClient(oauth2.Config{}, nil) + setupHttpMock(t, client.HttpClient.GetClient(), + "https://api.spotify.com/v1/me/tracks", + "testdata/user-tracks.json") + + result, err := client.UserTracks(0, 2) + require.NoError(t, err) + + assert := assert.New(t) + assert.Equal(1243, result.Total) + require.Len(t, result.Items, 2) + track1 := result.Items[0] + assert.Equal("2022-02-13T21:46:08Z", track1.AddedAt) + assert.Equal("Death to the Holy", track1.Track.Name) + assert.Equal("Zeal & Ardor", track1.Track.Album.Name) +} + +func setupHttpMock(t *testing.T, client *http.Client, url string, testDataPath string) { + httpmock.ActivateNonDefault(client) + + responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath)) + if err != nil { + t.Fatal(err) + } + httpmock.RegisterResponder("GET", url, responder) +} diff --git a/backends/spotify/models.go b/backends/spotify/models.go index 2dbd0ef..e80eccc 100644 --- a/backends/spotify/models.go +++ b/backends/spotify/models.go @@ -23,12 +23,18 @@ THE SOFTWARE. package spotify type TracksResult struct { - Href string `json:"href"` - Limit int `json:"limit"` - Next string `json:"next"` - Previous string `json:"previous"` - Offset int `json:"offset"` - Total int `json:"total"` + Href string `json:"href"` + Limit int `json:"limit"` + Next string `json:"next"` + Previous string `json:"previous"` + Offset int `json:"offset"` + Total int `json:"total"` + Items []SavedTrack `json:"items"` +} + +type SavedTrack struct { + AddedAt string `json:"added_at"` + Track Track `json:"track"` } type RecentlyPlayedResult struct { @@ -45,8 +51,8 @@ type Cursors struct { } type Listen struct { - Track Track `json:"track"` PlayedAt string `json:"played_at"` + Track Track `json:"track"` } type Track struct { diff --git a/backends/spotify/testdata/user-tracks.json b/backends/spotify/testdata/user-tracks.json new file mode 100644 index 0000000..6846683 --- /dev/null +++ b/backends/spotify/testdata/user-tracks.json @@ -0,0 +1,540 @@ +{ + "href": "https://api.spotify.com/v1/me/tracks?offset=143&limit=2&locale=de,en-US;q=0.7,en;q=0.3", + "items": [ + { + "added_at": "2022-02-13T21:46:08Z", + "track": { + "album": { + "album_type": "album", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/6yCjbLFZ9qAnWfsy9ujm5Y" + }, + "href": "https://api.spotify.com/v1/artists/6yCjbLFZ9qAnWfsy9ujm5Y", + "id": "6yCjbLFZ9qAnWfsy9ujm5Y", + "name": "Zeal & Ardor", + "type": "artist", + "uri": "spotify:artist:6yCjbLFZ9qAnWfsy9ujm5Y" + } + ], + "available_markets": [], + "external_urls": { + "spotify": "https://open.spotify.com/album/34u4cq27YP6837IcmzTTgX" + }, + "href": "https://api.spotify.com/v1/albums/34u4cq27YP6837IcmzTTgX", + "id": "34u4cq27YP6837IcmzTTgX", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b2731fedf861c139b6d8bebdc840", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e021fedf861c139b6d8bebdc840", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d000048511fedf861c139b6d8bebdc840", + "width": 64 + } + ], + "name": "Zeal & Ardor", + "release_date": "2022-02-11", + "release_date_precision": "day", + "total_tracks": 14, + "type": "album", + "uri": "spotify:album:34u4cq27YP6837IcmzTTgX" + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/6yCjbLFZ9qAnWfsy9ujm5Y" + }, + "href": "https://api.spotify.com/v1/artists/6yCjbLFZ9qAnWfsy9ujm5Y", + "id": "6yCjbLFZ9qAnWfsy9ujm5Y", + "name": "Zeal & Ardor", + "type": "artist", + "uri": "spotify:artist:6yCjbLFZ9qAnWfsy9ujm5Y" + } + ], + "available_markets": [], + "disc_number": 1, + "duration_ms": 187680, + "explicit": false, + "external_ids": { + "isrc": "CH8092101707" + }, + "external_urls": { + "spotify": "https://open.spotify.com/track/07K2e1PXNra3Zd5SGCsLuZ" + }, + "href": "https://api.spotify.com/v1/tracks/07K2e1PXNra3Zd5SGCsLuZ", + "id": "07K2e1PXNra3Zd5SGCsLuZ", + "is_local": false, + "name": "Death to the Holy", + "popularity": 0, + "preview_url": null, + "track_number": 3, + "type": "track", + "uri": "spotify:track:07K2e1PXNra3Zd5SGCsLuZ" + } + }, + { + "added_at": "2022-02-13T21:34:31Z", + "track": { + "album": { + "album_type": "single", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/7FsZ5HKdtDFJ1xmK6NICBO" + }, + "href": "https://api.spotify.com/v1/artists/7FsZ5HKdtDFJ1xmK6NICBO", + "id": "7FsZ5HKdtDFJ1xmK6NICBO", + "name": "Wucan", + "type": "artist", + "uri": "spotify:artist:7FsZ5HKdtDFJ1xmK6NICBO" + } + ], + "available_markets": [ + "AR", + "AU", + "AT", + "BE", + "BO", + "BR", + "BG", + "CA", + "CL", + "CO", + "CR", + "CY", + "CZ", + "DK", + "DO", + "DE", + "EC", + "EE", + "SV", + "FI", + "FR", + "GR", + "GT", + "HN", + "HK", + "HU", + "IS", + "IE", + "IT", + "LV", + "LT", + "LU", + "MY", + "MT", + "MX", + "NL", + "NZ", + "NI", + "NO", + "PA", + "PY", + "PE", + "PH", + "PL", + "PT", + "SG", + "SK", + "ES", + "SE", + "CH", + "TW", + "TR", + "UY", + "US", + "GB", + "AD", + "LI", + "MC", + "ID", + "JP", + "TH", + "VN", + "RO", + "IL", + "ZA", + "SA", + "AE", + "BH", + "QA", + "OM", + "KW", + "EG", + "MA", + "DZ", + "TN", + "LB", + "JO", + "PS", + "IN", + "BY", + "KZ", + "MD", + "UA", + "AL", + "BA", + "HR", + "ME", + "MK", + "RS", + "SI", + "KR", + "BD", + "PK", + "LK", + "GH", + "KE", + "NG", + "TZ", + "UG", + "AG", + "AM", + "BS", + "BB", + "BZ", + "BT", + "BW", + "BF", + "CV", + "CW", + "DM", + "FJ", + "GM", + "GE", + "GD", + "GW", + "GY", + "HT", + "JM", + "KI", + "LS", + "LR", + "MW", + "MV", + "ML", + "MH", + "FM", + "NA", + "NR", + "NE", + "PW", + "PG", + "WS", + "SM", + "ST", + "SN", + "SC", + "SL", + "SB", + "KN", + "LC", + "VC", + "SR", + "TL", + "TO", + "TT", + "TV", + "VU", + "AZ", + "BN", + "BI", + "KH", + "CM", + "TD", + "KM", + "GQ", + "SZ", + "GA", + "GN", + "KG", + "LA", + "MO", + "MR", + "MN", + "NP", + "RW", + "TG", + "UZ", + "ZW", + "BJ", + "MG", + "MU", + "MZ", + "AO", + "CI", + "DJ", + "ZM", + "CD", + "CG", + "IQ", + "LY", + "TJ", + "VE", + "ET", + "XK" + ], + "external_urls": { + "spotify": "https://open.spotify.com/album/11XibdriTL2mncbfoWLwv7" + }, + "href": "https://api.spotify.com/v1/albums/11XibdriTL2mncbfoWLwv7", + "id": "11XibdriTL2mncbfoWLwv7", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b2734f84c9e19c3a309cd123c6e5", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e024f84c9e19c3a309cd123c6e5", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d000048514f84c9e19c3a309cd123c6e5", + "width": 64 + } + ], + "name": "Night to Fall", + "release_date": "2018-10-22", + "release_date_precision": "day", + "total_tracks": 1, + "type": "album", + "uri": "spotify:album:11XibdriTL2mncbfoWLwv7" + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/7FsZ5HKdtDFJ1xmK6NICBO" + }, + "href": "https://api.spotify.com/v1/artists/7FsZ5HKdtDFJ1xmK6NICBO", + "id": "7FsZ5HKdtDFJ1xmK6NICBO", + "name": "Wucan", + "type": "artist", + "uri": "spotify:artist:7FsZ5HKdtDFJ1xmK6NICBO" + } + ], + "available_markets": [ + "AR", + "AU", + "AT", + "BE", + "BO", + "BR", + "BG", + "CA", + "CL", + "CO", + "CR", + "CY", + "CZ", + "DK", + "DO", + "DE", + "EC", + "EE", + "SV", + "FI", + "FR", + "GR", + "GT", + "HN", + "HK", + "HU", + "IS", + "IE", + "IT", + "LV", + "LT", + "LU", + "MY", + "MT", + "MX", + "NL", + "NZ", + "NI", + "NO", + "PA", + "PY", + "PE", + "PH", + "PL", + "PT", + "SG", + "SK", + "ES", + "SE", + "CH", + "TW", + "TR", + "UY", + "US", + "GB", + "AD", + "LI", + "MC", + "ID", + "JP", + "TH", + "VN", + "RO", + "IL", + "ZA", + "SA", + "AE", + "BH", + "QA", + "OM", + "KW", + "EG", + "MA", + "DZ", + "TN", + "LB", + "JO", + "PS", + "IN", + "BY", + "KZ", + "MD", + "UA", + "AL", + "BA", + "HR", + "ME", + "MK", + "RS", + "SI", + "KR", + "BD", + "PK", + "LK", + "GH", + "KE", + "NG", + "TZ", + "UG", + "AG", + "AM", + "BS", + "BB", + "BZ", + "BT", + "BW", + "BF", + "CV", + "CW", + "DM", + "FJ", + "GM", + "GE", + "GD", + "GW", + "GY", + "HT", + "JM", + "KI", + "LS", + "LR", + "MW", + "MV", + "ML", + "MH", + "FM", + "NA", + "NR", + "NE", + "PW", + "PG", + "WS", + "SM", + "ST", + "SN", + "SC", + "SL", + "SB", + "KN", + "LC", + "VC", + "SR", + "TL", + "TO", + "TT", + "TV", + "VU", + "AZ", + "BN", + "BI", + "KH", + "CM", + "TD", + "KM", + "GQ", + "SZ", + "GA", + "GN", + "KG", + "LA", + "MO", + "MR", + "MN", + "NP", + "RW", + "TG", + "UZ", + "ZW", + "BJ", + "MG", + "MU", + "MZ", + "AO", + "CI", + "DJ", + "ZM", + "CD", + "CG", + "IQ", + "LY", + "TJ", + "VE", + "ET", + "XK" + ], + "disc_number": 1, + "duration_ms": 248372, + "explicit": false, + "external_ids": { + "isrc": "DEMV91805509" + }, + "external_urls": { + "spotify": "https://open.spotify.com/track/30YxzMczS77DCXIWiXSzSK" + }, + "href": "https://api.spotify.com/v1/tracks/30YxzMczS77DCXIWiXSzSK", + "id": "30YxzMczS77DCXIWiXSzSK", + "is_local": false, + "name": "Night to Fall", + "popularity": 29, + "preview_url": "https://p.scdn.co/mp3-preview/9d3edae25510e9f78194a04625a0773ee1f0db65?cid=5433a04d90a946f2a0e5175b1383604a", + "track_number": 1, + "type": "track", + "uri": "spotify:track:30YxzMczS77DCXIWiXSzSK" + } + } + ], + "limit": 2, + "next": "https://api.spotify.com/v1/me/tracks?offset=145&limit=2&locale=de,en-US;q=0.7,en;q=0.3", + "offset": 143, + "previous": "https://api.spotify.com/v1/me/tracks?offset=141&limit=2&locale=de,en-US;q=0.7,en;q=0.3", + "total": 1243 +}