1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@ gonic
|
|||||||
gonicscan
|
gonicscan
|
||||||
gonicembed
|
gonicembed
|
||||||
.vscode
|
.vscode
|
||||||
|
*.swp
|
||||||
|
|||||||
@@ -251,10 +251,10 @@ func (c *Controller) ServeGetArtistInfoTwo(r *http.Request) *spec.Response {
|
|||||||
return spec.NewError(10, "please provide an `id` parameter")
|
return spec.NewError(10, "please provide an `id` parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
artist := &db.Artist{}
|
var artist db.Artist
|
||||||
err = c.DB.
|
err = c.DB.
|
||||||
Where("id=?", id.Value).
|
Where("id=?", id.Value).
|
||||||
Find(artist).
|
Find(&artist).
|
||||||
Error
|
Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return spec.NewError(70, "artist with id `%s` not found", id)
|
return spec.NewError(70, "artist with id `%s` not found", id)
|
||||||
@@ -263,16 +263,16 @@ func (c *Controller) ServeGetArtistInfoTwo(r *http.Request) *spec.Response {
|
|||||||
sub := spec.NewResponse()
|
sub := spec.NewResponse()
|
||||||
sub.ArtistInfoTwo = &spec.ArtistInfo{}
|
sub.ArtistInfoTwo = &spec.ArtistInfo{}
|
||||||
if artist.Cover != "" {
|
if artist.Cover != "" {
|
||||||
sub.ArtistInfoTwo.SmallImageURL = c.genArtistCoverURL(r, artist, 64)
|
sub.ArtistInfoTwo.SmallImageURL = c.genArtistCoverURL(r, &artist, 64)
|
||||||
sub.ArtistInfoTwo.MediumImageURL = c.genArtistCoverURL(r, artist, 126)
|
sub.ArtistInfoTwo.MediumImageURL = c.genArtistCoverURL(r, &artist, 126)
|
||||||
sub.ArtistInfoTwo.LargeImageURL = c.genArtistCoverURL(r, artist, 256)
|
sub.ArtistInfoTwo.LargeImageURL = c.genArtistCoverURL(r, &artist, 256)
|
||||||
}
|
}
|
||||||
|
|
||||||
apiKey, _ := c.DB.GetSetting("lastfm_api_key")
|
apiKey, _ := c.DB.GetSetting("lastfm_api_key")
|
||||||
if apiKey == "" {
|
if apiKey == "" {
|
||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
info, err := lastfm.ArtistGetInfo(apiKey, artist)
|
info, err := lastfm.ArtistGetInfo(apiKey, artist.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return spec.NewError(0, "fetching artist info: %v", err)
|
return spec.NewError(0, "fetching artist info: %v", err)
|
||||||
}
|
}
|
||||||
@@ -300,13 +300,13 @@ func (c *Controller) ServeGetArtistInfoTwo(r *http.Request) *spec.Response {
|
|||||||
if i == count {
|
if i == count {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
artist = &db.Artist{}
|
var artist db.Artist
|
||||||
err = c.DB.
|
err = c.DB.
|
||||||
Select("artists.*, count(albums.id) album_count").
|
Select("artists.*, count(albums.id) album_count").
|
||||||
Where("name=?", similarInfo.Name).
|
Where("name=?", similarInfo.Name).
|
||||||
Joins("LEFT JOIN albums ON artists.id=albums.tag_artist_id").
|
Joins("LEFT JOIN albums ON artists.id=albums.tag_artist_id").
|
||||||
Group("artists.id").
|
Group("artists.id").
|
||||||
Find(artist).
|
Find(&artist).
|
||||||
Error
|
Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) && !inclNotPresent {
|
if errors.Is(err, gorm.ErrRecordNotFound) && !inclNotPresent {
|
||||||
continue
|
continue
|
||||||
@@ -396,3 +396,56 @@ func (c *Controller) genArtistCoverURL(r *http.Request, artist *db.Artist, size
|
|||||||
|
|
||||||
return coverURL.String()
|
return coverURL.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServeGetTopSongs(r *http.Request) *spec.Response {
|
||||||
|
params := r.Context().Value(CtxParams).(params.Params)
|
||||||
|
count := params.GetOrInt("count", 10)
|
||||||
|
artistName, err := params.Get("artist")
|
||||||
|
if err != nil {
|
||||||
|
return spec.NewError(10, "please provide an `artist` parameter")
|
||||||
|
}
|
||||||
|
var artist db.Artist
|
||||||
|
if err := c.DB.Where("name=?", artistName).Find(&artist).Error; err != nil {
|
||||||
|
return spec.NewError(0, "finding artist by name: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiKey, _ := c.DB.GetSetting("lastfm_api_key")
|
||||||
|
if apiKey == "" {
|
||||||
|
return spec.NewResponse()
|
||||||
|
}
|
||||||
|
topTracks, err := lastfm.ArtistGetTopTracks(apiKey, artist.Name)
|
||||||
|
if err != nil {
|
||||||
|
return spec.NewError(0, "fetching artist top tracks: %v", err)
|
||||||
|
}
|
||||||
|
if len(topTracks.Tracks) == 0 {
|
||||||
|
return spec.NewError(70, "no top tracks found for artist: %v", artist)
|
||||||
|
}
|
||||||
|
|
||||||
|
topTrackNames := make([]string, len(topTracks.Tracks))
|
||||||
|
for i, t := range topTracks.Tracks {
|
||||||
|
topTrackNames[i] = t.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
var tracks []*db.Track
|
||||||
|
err = c.DB.
|
||||||
|
Preload("Album").
|
||||||
|
Where("artist_id=? AND tracks.tag_title IN (?)", artist.ID, topTrackNames).
|
||||||
|
Limit(count).
|
||||||
|
Find(&tracks).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return spec.NewError(0, "error finding tracks: %v", err)
|
||||||
|
}
|
||||||
|
if len(tracks) == 0 {
|
||||||
|
return spec.NewError(70, "no tracks found matchind last fm top songs for artist: %v", artist)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub := spec.NewResponse()
|
||||||
|
sub.TopSongs = &spec.TopSongs{
|
||||||
|
Tracks: make([]*spec.TrackChild, len(tracks)),
|
||||||
|
}
|
||||||
|
for i, track := range tracks {
|
||||||
|
sub.TopSongs.Tracks[i] = spec.NewTrackByTags(track, track.Album)
|
||||||
|
}
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func (c *Controller) ServeScrobble(r *http.Request) *spec.Response {
|
|||||||
|
|
||||||
id, err := params.GetID("id")
|
id, err := params.GetID("id")
|
||||||
if err != nil || id.Type != specid.Track {
|
if err != nil || id.Type != specid.Track {
|
||||||
return spec.NewError(10, "please provide an valid `id` track parameter")
|
return spec.NewError(10, "please provide a track `id` track parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
track := &db.Track{}
|
track := &db.Track{}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ type Response struct {
|
|||||||
Bookmarks *Bookmarks `xml:"bookmarks" json:"bookmarks,omitempty"`
|
Bookmarks *Bookmarks `xml:"bookmarks" json:"bookmarks,omitempty"`
|
||||||
Starred *Starred `xml:"starred" json:"starred,omitempty"`
|
Starred *Starred `xml:"starred" json:"starred,omitempty"`
|
||||||
StarredTwo *StarredTwo `xml:"starred2" json:"starred2,omitempty"`
|
StarredTwo *StarredTwo `xml:"starred2" json:"starred2,omitempty"`
|
||||||
|
TopSongs *TopSongs `xml:"topSongs" json:"topSongs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResponse() *Response {
|
func NewResponse() *Response {
|
||||||
@@ -352,3 +353,7 @@ type StarredTwo struct {
|
|||||||
Albums []*Album `xml:"album,omitempty" json:"album,omitempty"`
|
Albums []*Album `xml:"album,omitempty" json:"album,omitempty"`
|
||||||
Tracks []*TrackChild `xml:"song,omitempty" json:"song,omitempty"`
|
Tracks []*TrackChild `xml:"song,omitempty" json:"song,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TopSongs struct {
|
||||||
|
Tracks []*TrackChild `xml:"song,omitempty" json:"song,omitempty"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type LastFM struct {
|
type LastFM struct {
|
||||||
XMLName xml.Name `xml:"lfm"`
|
XMLName xml.Name `xml:"lfm"`
|
||||||
Status string `xml:"status,attr"`
|
Status string `xml:"status,attr"`
|
||||||
Session Session `xml:"session"`
|
Session Session `xml:"session"`
|
||||||
Error Error `xml:"error"`
|
Error Error `xml:"error"`
|
||||||
Artist Artist `xml:"artist"`
|
Artist Artist `xml:"artist"`
|
||||||
|
TopTracks TopTracks `xml:"toptracks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
@@ -77,6 +78,26 @@ type ArtistBio struct {
|
|||||||
Content string `xml:"content"`
|
Content string `xml:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TopTracks struct {
|
||||||
|
XMLName xml.Name `xml:"toptracks"`
|
||||||
|
Artist string `xml:"artist,attr"`
|
||||||
|
Tracks []Track `xml:"track"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Track struct {
|
||||||
|
Rank int `xml:"rank,attr"`
|
||||||
|
Tracks []Track `xml:"track"`
|
||||||
|
Name string `xml:"name"`
|
||||||
|
MBID string `xml:"mbid"`
|
||||||
|
PlayCount int `xml:"playcount"`
|
||||||
|
Listeners int `xml:"listeners"`
|
||||||
|
URL string `xml:"url"`
|
||||||
|
Image []struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Size string `xml:"size,attr"`
|
||||||
|
} `xml:"image"`
|
||||||
|
}
|
||||||
|
|
||||||
func getParamSignature(params url.Values, secret string) string {
|
func getParamSignature(params url.Values, secret string) string {
|
||||||
// the parameters must be in order before hashing
|
// the parameters must be in order before hashing
|
||||||
paramKeys := make([]string, 0, len(params))
|
paramKeys := make([]string, 0, len(params))
|
||||||
@@ -113,11 +134,11 @@ func makeRequest(method string, params url.Values) (LastFM, error) {
|
|||||||
return lastfm, nil
|
return lastfm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ArtistGetInfo(apiKey string, artist *db.Artist) (Artist, error) {
|
func ArtistGetInfo(apiKey string, artistName string) (Artist, error) {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Add("method", "artist.getInfo")
|
params.Add("method", "artist.getInfo")
|
||||||
params.Add("api_key", apiKey)
|
params.Add("api_key", apiKey)
|
||||||
params.Add("artist", artist.Name)
|
params.Add("artist", artistName)
|
||||||
resp, err := makeRequest("GET", params)
|
resp, err := makeRequest("GET", params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Artist{}, fmt.Errorf("making artist GET: %w", err)
|
return Artist{}, fmt.Errorf("making artist GET: %w", err)
|
||||||
@@ -125,6 +146,18 @@ func ArtistGetInfo(apiKey string, artist *db.Artist) (Artist, error) {
|
|||||||
return resp.Artist, nil
|
return resp.Artist, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ArtistGetTopTracks(apiKey, artistName string) (TopTracks, error) {
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("method", "artist.getTopTracks")
|
||||||
|
params.Add("api_key", apiKey)
|
||||||
|
params.Add("artist", artistName)
|
||||||
|
resp, err := makeRequest("GET", params)
|
||||||
|
if err != nil {
|
||||||
|
return TopTracks{}, fmt.Errorf("making track GET: %w", err)
|
||||||
|
}
|
||||||
|
return resp.TopTracks, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetSession(apiKey, secret, token string) (string, error) {
|
func GetSession(apiKey, secret, token string) (string, error) {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Add("method", "auth.getSession")
|
params.Add("method", "auth.getSession")
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ func setupSubsonic(r *mux.Router, ctrl *ctrlsubsonic.Controller) {
|
|||||||
r.Handle("/getBookmarks{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetBookmarks))
|
r.Handle("/getBookmarks{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetBookmarks))
|
||||||
r.Handle("/createBookmark{_:(?:\\.view)?}", ctrl.H(ctrl.ServeCreateBookmark))
|
r.Handle("/createBookmark{_:(?:\\.view)?}", ctrl.H(ctrl.ServeCreateBookmark))
|
||||||
r.Handle("/deleteBookmark{_:(?:\\.view)?}", ctrl.H(ctrl.ServeDeleteBookmark))
|
r.Handle("/deleteBookmark{_:(?:\\.view)?}", ctrl.H(ctrl.ServeDeleteBookmark))
|
||||||
|
r.Handle("/getTopSongs{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetTopSongs))
|
||||||
|
|
||||||
// raw
|
// raw
|
||||||
r.Handle("/download{_:(?:\\.view)?}", ctrl.HR(ctrl.ServeDownload))
|
r.Handle("/download{_:(?:\\.view)?}", ctrl.HR(ctrl.ServeDownload))
|
||||||
|
|||||||
Reference in New Issue
Block a user