diff --git a/server/ctrlsubsonic/handlers_by_tags.go b/server/ctrlsubsonic/handlers_by_tags.go index ca06f6b..fd85b86 100644 --- a/server/ctrlsubsonic/handlers_by_tags.go +++ b/server/ctrlsubsonic/handlers_by_tags.go @@ -11,6 +11,7 @@ import ( "senan.xyz/g/gonic/model" "senan.xyz/g/gonic/server/ctrlsubsonic/params" "senan.xyz/g/gonic/server/ctrlsubsonic/spec" + "senan.xyz/g/gonic/server/lastfm" ) func (c *Controller) ServeGetArtists(r *http.Request) *spec.Response { @@ -217,3 +218,44 @@ func (c *Controller) ServeSearchThree(r *http.Request) *spec.Response { sub.SearchResultThree = results return sub } + +func (c *Controller) ServeGetArtistInfoTwo(r *http.Request) *spec.Response { + params := r.Context().Value(CtxParams).(params.Params) + id, err := params.GetInt("id") + if err != nil { + return spec.NewError(10, "please provide an `id` parameter") + } + apiKey := c.DB.GetSetting("lastfm_api_key") + if apiKey == "" { + return spec.NewError(0, "please set ask your admin to set the last.fm api key") + } + artist := &model.Artist{} + err = c.DB. + Where("id = ?", id). + Find(artist). + Error + if gorm.IsRecordNotFoundError(err) { + return spec.NewError(70, "artist with id `%d` not found", id) + } + info, err := lastfm.ArtistGetInfo(apiKey, artist) + if err != nil { + return spec.NewError(0, "fetching artist info: %v", err) + } + sub := spec.NewResponse() + sub.ArtistInfoTwo = &spec.ArtistInfo{ + Biography: info.Bio.Summary, + MusicBrainzID: info.MBID, + LastFMURL: info.URL, + } + for _, image := range info.Image { + switch image.Size { + case "small": + sub.ArtistInfoTwo.SmallImageURL = image.Text + case "medium": + sub.ArtistInfoTwo.MediumImageURL = image.Text + case "large": + sub.ArtistInfoTwo.LargeImageURL = image.Text + } + } + return sub +} diff --git a/server/ctrlsubsonic/handlers_unimplemented.go b/server/ctrlsubsonic/handlers_unimplemented.go index 93c5f80..287b90f 100644 --- a/server/ctrlsubsonic/handlers_unimplemented.go +++ b/server/ctrlsubsonic/handlers_unimplemented.go @@ -9,18 +9,6 @@ import ( // NOTE: when these are implemented, they should be moved to their // respective _by_folder or _by_tag file -func (c *Controller) ServeGetArtistInfo(r *http.Request) *spec.Response { - sub := spec.NewResponse() - sub.ArtistInfo = &spec.ArtistInfo{} - return sub -} - -func (c *Controller) ServeGetArtistInfoTwo(r *http.Request) *spec.Response { - sub := spec.NewResponse() - sub.ArtistInfoTwo = &spec.ArtistInfo{} - return sub -} - func (c *Controller) ServeGetGenres(r *http.Request) *spec.Response { sub := spec.NewResponse() sub.Genres = &spec.Genres{} diff --git a/server/lastfm/lastfm.go b/server/lastfm/lastfm.go index 0849814..dd14f89 100644 --- a/server/lastfm/lastfm.go +++ b/server/lastfm/lastfm.go @@ -23,6 +23,8 @@ var ( } ) +// TODO: remove this package's dependency on models/db + func getParamSignature(params url.Values, secret string) string { // the parameters must be in order before hashing paramKeys := make([]string, 0) @@ -40,22 +42,22 @@ func getParamSignature(params url.Values, secret string) string { return hex.EncodeToString(hash[:]) } -func makeRequest(method string, params url.Values) (*LastFM, error) { +func makeRequest(method string, params url.Values) (LastFM, error) { req, _ := http.NewRequest(method, baseURL, nil) req.URL.RawQuery = params.Encode() resp, err := client.Do(req) if err != nil { - return nil, errors.Wrap(err, "get") + return LastFM{}, errors.Wrap(err, "get") } defer resp.Body.Close() decoder := xml.NewDecoder(resp.Body) - lastfm := &LastFM{} - err = decoder.Decode(lastfm) - if err != nil { - return nil, errors.Wrap(err, "decoding") + lastfm := LastFM{} + if err = decoder.Decode(&lastfm); err != nil { + return LastFM{}, errors.Wrap(err, "decoding") } - if lastfm.Error != nil { - return nil, fmt.Errorf("parsing: %v", lastfm.Error.Value) + //? + if lastfm.Error.Code != 0 { + return LastFM{}, fmt.Errorf("parsing: %v", lastfm.Error.Value) } return lastfm, nil } @@ -100,3 +102,15 @@ func Scrobble(apiKey, secret, session string, opts ScrobbleOpts) error { _, err := makeRequest("POST", params) return err } + +func ArtistGetInfo(apiKey string, artist *model.Artist) (Artist, error) { + params := url.Values{} + params.Add("method", "artist.getInfo") + params.Add("api_key", apiKey) + params.Add("artist", artist.Name) + resp, err := makeRequest("GET", params) + if err != nil { + return Artist{}, errors.Wrap(err, "making artist GET") + } + return resp.Artist, nil +} diff --git a/server/lastfm/models.go b/server/lastfm/models.go index 1890096..5ac3764 100644 --- a/server/lastfm/models.go +++ b/server/lastfm/models.go @@ -5,8 +5,9 @@ import "encoding/xml" type LastFM struct { XMLName xml.Name `xml:"lfm"` Status string `xml:"status,attr"` - Session *Session `xml:"session"` - Error *Error `xml:"error"` + Session Session `xml:"session"` + Error Error `xml:"error"` + Artist Artist `xml:"artist"` } type Session struct { @@ -19,3 +20,37 @@ type Error struct { Code uint `xml:"code,attr"` Value string `xml:",chardata"` } + +type Artist struct { + XMLName xml.Name `xml:"artist"` + Name string `xml:"name"` + MBID string `xml:"mbid"` + URL string `xml:"url"` + Image []struct { + Text string `xml:",chardata"` + Size string `xml:"size,attr"` + } `xml:"image"` + Streamable string `xml:"streamable"` + Stats struct { + Listeners string `xml:"listeners"` + Plays string `xml:"plays"` + } `xml:"stats"` + Similar struct { + Artists []Artist `xml:"artist"` + } `xml:"similar"` + Tags struct { + Tag []ArtistTag `xml:"tag"` + } `xml:"tags"` + Bio ArtistBio `xml:"bio"` +} + +type ArtistTag struct { + Name string `xml:"name"` + URL string `xml:"url"` +} + +type ArtistBio struct { + Published string `xml:"published"` + Summary string `xml:"summary"` + Content string `xml:"content"` +} diff --git a/server/server.go b/server/server.go index ad4ac20..b0e096e 100644 --- a/server/server.go +++ b/server/server.go @@ -148,14 +148,13 @@ func (s *Server) SetupSubsonic() error { rout.Handle("/getArtist{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetArtist)) rout.Handle("/getArtists{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetArtists)) rout.Handle("/search3{_:(?:\\.view)?}", ctrl.H(ctrl.ServeSearchThree)) + rout.Handle("/getArtistInfo2{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetArtistInfoTwo)) // ** begin browse by folder rout.Handle("/getIndexes{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetIndexes)) rout.Handle("/getMusicDirectory{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetMusicDirectory)) rout.Handle("/getAlbumList{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetAlbumList)) rout.Handle("/search2{_:(?:\\.view)?}", ctrl.H(ctrl.ServeSearchTwo)) // ** begin unimplemented - rout.Handle("/getArtistInfo{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetArtistInfo)) - rout.Handle("/getArtistInfo2{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetArtistInfoTwo)) rout.Handle("/getGenres{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetGenres)) // middlewares should be run for not found handler // https://github.com/gorilla/mux/issues/416