From 8fb09e71b6c982a9650e352fdc2a665a687b368a Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 1 Dec 2024 17:31:18 -0500 Subject: [PATCH] feat(server): get artist images from Last.fm Signed-off-by: Deluan --- core/agents/lastfm/agent.go | 41 +++++++++++++++++++++++++++++++++++++ go.mod | 3 ++- go.sum | 5 +++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/core/agents/lastfm/agent.go b/core/agents/lastfm/agent.go index 55deab32b..828cd0cf5 100644 --- a/core/agents/lastfm/agent.go +++ b/core/agents/lastfm/agent.go @@ -3,11 +3,13 @@ package lastfm import ( "context" "errors" + "fmt" "net/http" "regexp" "strconv" "strings" + "github.com/andybalholm/cascadia" "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/core/agents" @@ -15,6 +17,7 @@ import ( "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/utils/cache" + "golang.org/x/net/html" ) const ( @@ -178,6 +181,44 @@ func (l *lastfmAgent) GetArtistTopSongs(ctx context.Context, id, artistName, mbi return res, nil } +var artistOpenGraphQuery = cascadia.MustCompile(`html > head > meta[property="og:image"]`) + +func (l *lastfmAgent) GetArtistImages(ctx context.Context, _, name, mbid string) ([]agents.ExternalImage, error) { + a, err := l.callArtistGetInfo(ctx, name, mbid) + if err != nil { + return nil, fmt.Errorf("get artist info: %w", err) + } + req, err := http.NewRequest(http.MethodGet, a.URL, nil) + if err != nil { + return nil, fmt.Errorf("create artist image request: %w", err) + } + resp, err := l.client.hc.Do(req) + if err != nil { + return nil, fmt.Errorf("get artist url: %w", err) + } + defer resp.Body.Close() + + node, err := html.Parse(resp.Body) + if err != nil { + return nil, fmt.Errorf("parse html: %w", err) + } + + var res []agents.ExternalImage + n := cascadia.Query(node, artistOpenGraphQuery) + if n == nil { + return res, nil + } + for _, attr := range n.Attr { + if attr.Key == "content" { + res = []agents.ExternalImage{ + {URL: attr.Val}, + } + break + } + } + return res, nil +} + func (l *lastfmAgent) callAlbumGetInfo(ctx context.Context, name, artist, mbid string) (*Album, error) { a, err := l.client.albumGetInfo(ctx, name, artist, mbid) var lfErr *lastFMError diff --git a/go.mod b/go.mod index b87bee143..4b4bf6515 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ replace github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 => github.com/ require ( github.com/Masterminds/squirrel v1.5.4 github.com/RaveNoX/go-jsoncommentstrip v1.0.0 + github.com/andybalholm/cascadia v1.3.2 github.com/bradleyjkemp/cupaloy/v2 v2.8.0 github.com/deluan/rest v0.0.0-20211102003136-6260bc399cbf github.com/deluan/sanitize v0.0.0-20241120162836-fdfd8fdfaa55 @@ -51,6 +52,7 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f golang.org/x/image v0.22.0 + golang.org/x/net v0.31.0 golang.org/x/sync v0.9.0 golang.org/x/text v0.20.0 golang.org/x/time v0.8.0 @@ -103,7 +105,6 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.29.0 // indirect - golang.org/x/net v0.31.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/tools v0.27.0 // indirect google.golang.org/protobuf v1.35.1 // indirect diff --git a/go.sum b/go.sum index a6462885e..5bebc1608 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8 github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/RaveNoX/go-jsoncommentstrip v1.0.0 h1:t527LHHE3HmiHrq74QMpNPZpGCIJzTx+apLkMKt4HC0= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -243,6 +245,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= @@ -265,6 +268,7 @@ golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -273,6 +277,7 @@ golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=