Add "real" TopSongs

This commit is contained in:
Deluan 2020-10-20 22:53:52 -04:00
parent b5e20c1934
commit 049ac70b2b
7 changed files with 119 additions and 3 deletions

View File

@ -25,6 +25,7 @@ type ExternalInfo interface {
ArtistInfo(ctx context.Context, id string) (*model.ArtistInfo, error)
SimilarArtists(ctx context.Context, id string, includeNotPresent bool, count int) (model.Artists, error)
SimilarSongs(ctx context.Context, id string, count int) (model.MediaFiles, error)
TopSongs(ctx context.Context, artist string, count int) (model.MediaFiles, error)
}
func NewExternalInfo(ds model.DataStore, lfm *lastfm.Client, spf *spotify.Client) ExternalInfo {
@ -136,6 +137,32 @@ func (e *externalInfo) similarArtists(ctx context.Context, artist *model.Artist,
return result, nil
}
func (e *externalInfo) TopSongs(ctx context.Context, artist string, count int) (model.MediaFiles, error) {
if e.lfm == nil {
log.Warn(ctx, "Last.FM client not configured")
return nil, model.ErrNotAvailable
}
log.Debug(ctx, "Calling Last.FM ArtistGetTopTracks", "artist", artist)
tracks, err := e.lfm.ArtistGetTopTracks(ctx, artist, count)
if err != nil {
return nil, err
}
var songs model.MediaFiles
for _, t := range tracks {
mfs, err := e.ds.MediaFile(ctx).GetAll(model.QueryOptions{
Filters: squirrel.And{
squirrel.Like{"artist": artist},
squirrel.Like{"title": t.Name},
},
})
if err != nil || len(mfs) == 0 {
continue
}
songs = append(songs, mfs[0])
}
return songs, nil
}
func (e *externalInfo) ArtistInfo(ctx context.Context, id string) (*model.ArtistInfo, error) {
artist, err := e.getArtist(ctx, id)
if err != nil {

View File

@ -80,6 +80,18 @@ func (c *Client) ArtistGetSimilar(ctx context.Context, name string, limit int) (
return response.SimilarArtists.Artists, nil
}
func (c *Client) ArtistGetTopTracks(ctx context.Context, name string, limit int) ([]Track, error) {
params := url.Values{}
params.Add("method", "artist.getTopTracks")
params.Add("artist", name)
params.Add("limit", strconv.Itoa(limit))
response, err := c.makeRequest(params)
if err != nil {
return nil, err
}
return response.TopTracks.Track, nil
}
func (c *Client) parseError(data []byte) error {
var e Error
err := json.Unmarshal(data, &e)

View File

@ -98,7 +98,45 @@ var _ = Describe("Client", func() {
_, err := client.ArtistGetSimilar(context.TODO(), "U2", 2)
Expect(err).To(MatchError("invalid character '<' looking for beginning of value"))
})
})
Describe("ArtistGetTopTracks", func() {
It("returns top tracks for a successful response", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.gettoptracks.json")
httpClient.res = http.Response{Body: f, StatusCode: 200}
tracks, err := client.ArtistGetTopTracks(context.TODO(), "U2", 2)
Expect(err).To(BeNil())
Expect(len(tracks)).To(Equal(2))
Expect(httpClient.savedRequest.URL.String()).To(Equal(apiBaseUrl + "?api_key=API_KEY&artist=U2&format=json&limit=2&method=artist.getTopTracks"))
})
It("fails if Last.FM returns an error", func() {
httpClient.res = http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString(`{"error":3,"message":"Invalid Method - No method with that name in this package"}`)),
StatusCode: 400,
}
_, err := client.ArtistGetTopTracks(context.TODO(), "U2", 2)
Expect(err).To(MatchError("last.fm error(3): Invalid Method - No method with that name in this package"))
})
It("fails if HttpClient.Do() returns error", func() {
httpClient.err = errors.New("generic error")
_, err := client.ArtistGetTopTracks(context.TODO(), "U2", 2)
Expect(err).To(MatchError("generic error"))
})
It("fails if returned body is not a valid JSON", func() {
httpClient.res = http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString(`<xml>NOT_VALID_JSON</xml>`)),
StatusCode: 200,
}
_, err := client.ArtistGetTopTracks(context.TODO(), "U2", 2)
Expect(err).To(MatchError("invalid character '<' looking for beginning of value"))
})
})
})

View File

@ -3,6 +3,7 @@ package lastfm
type Response struct {
Artist Artist `json:"artist"`
SimilarArtists SimilarArtists `json:"similarartists"`
TopTracks TopTracks `json:"toptracks"`
}
type Artist struct {
@ -42,6 +43,15 @@ type ArtistBio struct {
Content string `json:"content"`
}
type Track struct {
Name string `json:"name"`
MBID string `json:"mbid"`
}
type TopTracks struct {
Track []Track `json:"track"`
}
type Error struct {
Code int `json:"error"`
Message string `json:"message"`

View File

@ -41,6 +41,21 @@ var _ = Describe("LastFM responses", func() {
})
})
Describe("TopTracks", func() {
It("parses the response correctly", func() {
var resp Response
body, _ := ioutil.ReadFile("tests/fixtures/lastfm.artist.gettoptracks.json")
err := json.Unmarshal(body, &resp)
Expect(err).To(BeNil())
Expect(resp.TopTracks.Track).To(HaveLen(2))
Expect(resp.TopTracks.Track[0].Name).To(Equal("Beautiful Day"))
Expect(resp.TopTracks.Track[0].MBID).To(Equal("f7f264d0-a89b-4682-9cd7-a4e7c37637af"))
Expect(resp.TopTracks.Track[1].Name).To(Equal("With or Without You"))
Expect(resp.TopTracks.Track[1].MBID).To(Equal("6b9a509f-6907-4a6e-9345-2f12da09ba4b"))
})
})
Describe("Error", func() {
It("parses the error response correctly", func() {
var error Error

View File

@ -298,7 +298,7 @@ func (c *BrowsingController) GetSimilarSongs(w http.ResponseWriter, r *http.Requ
if err != nil {
return nil, err
}
count := utils.ParamInt(r, "count", 20)
count := utils.ParamInt(r, "count", 50)
songs, err := c.ei.SimilarSongs(ctx, id, count)
if err != nil {
@ -325,10 +325,23 @@ func (c *BrowsingController) GetSimilarSongs2(w http.ResponseWriter, r *http.Req
return response, nil
}
// TODO Integrate with Last.FM
func (c *BrowsingController) GetTopSongs(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
ctx := r.Context()
artist, err := requiredParamString(r, "artist", "artist parameter required")
if err != nil {
return nil, err
}
count := utils.ParamInt(r, "count", 50)
songs, err := c.ei.TopSongs(ctx, artist, count)
if err != nil {
return nil, err
}
response := newResponse()
response.TopSongs = &responses.TopSongs{}
response.TopSongs = &responses.TopSongs{
Song: childrenFromMediaFiles(ctx, songs),
}
return response, nil
}

View File

@ -0,0 +1 @@
{"toptracks":{"track":[{"name":"Beautiful Day","playcount":"6309776","listeners":"1037970","mbid":"f7f264d0-a89b-4682-9cd7-a4e7c37637af","url":"https://www.last.fm/music/U2/_/Beautiful+Day","streamable":"0","artist":{"name":"U2","mbid":"a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432","url":"https://www.last.fm/music/U2"},"image":[{"#text":"https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png","size":"small"},{"#text":"https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png","size":"medium"},{"#text":"https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png","size":"large"},{"#text":"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png","size":"extralarge"}],"@attr":{"rank":"1"}},{"name":"With or Without You","playcount":"6779665","listeners":"1022929","mbid":"6b9a509f-6907-4a6e-9345-2f12da09ba4b","url":"https://www.last.fm/music/U2/_/With+or+Without+You","streamable":"0","artist":{"name":"U2","mbid":"a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432","url":"https://www.last.fm/music/U2"},"image":[{"#text":"https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png","size":"small"},{"#text":"https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png","size":"medium"},{"#text":"https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png","size":"large"},{"#text":"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png","size":"extralarge"}],"@attr":{"rank":"2"}}],"@attr":{"artist":"U2","page":"1","perPage":"2","totalPages":"166117","total":"332234"}}}