Some checks failed
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v5) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v6) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v7) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to GHCR (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Has been cancelled
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
POEditor import / update-translations (push) Has been cancelled
149 lines
3.6 KiB
Go
149 lines
3.6 KiB
Go
package deezer
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/navidrome/navidrome/conf"
|
|
"github.com/navidrome/navidrome/consts"
|
|
"github.com/navidrome/navidrome/core/agents"
|
|
"github.com/navidrome/navidrome/log"
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/navidrome/navidrome/utils/cache"
|
|
"github.com/navidrome/navidrome/utils/slice"
|
|
)
|
|
|
|
const deezerAgentName = "deezer"
|
|
const deezerApiPictureXlSize = 1000
|
|
const deezerApiPictureBigSize = 500
|
|
const deezerApiPictureMediumSize = 250
|
|
const deezerApiPictureSmallSize = 56
|
|
const deezerArtistSearchLimit = 50
|
|
|
|
type deezerAgent struct {
|
|
dataStore model.DataStore
|
|
client *client
|
|
}
|
|
|
|
func deezerConstructor(dataStore model.DataStore) agents.Interface {
|
|
agent := &deezerAgent{dataStore: dataStore}
|
|
httpClient := &http.Client{
|
|
Timeout: consts.DefaultHttpClientTimeOut,
|
|
}
|
|
cachedHttpClient := cache.NewHTTPClient(httpClient, consts.DefaultHttpClientTimeOut)
|
|
agent.client = newClient(cachedHttpClient, conf.Server.Deezer.Language)
|
|
return agent
|
|
}
|
|
|
|
func (s *deezerAgent) AgentName() string {
|
|
return deezerAgentName
|
|
}
|
|
|
|
func (s *deezerAgent) GetArtistImages(ctx context.Context, _, name, _ string) ([]agents.ExternalImage, error) {
|
|
artist, err := s.searchArtist(ctx, name)
|
|
if err != nil {
|
|
if errors.Is(err, agents.ErrNotFound) {
|
|
log.Warn(ctx, "Artist not found in deezer", "artist", name)
|
|
} else {
|
|
log.Error(ctx, "Error calling deezer", "artist", name, err)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var res []agents.ExternalImage
|
|
possibleImages := []struct {
|
|
URL string
|
|
Size int
|
|
}{
|
|
{artist.PictureXl, deezerApiPictureXlSize},
|
|
{artist.PictureBig, deezerApiPictureBigSize},
|
|
{artist.PictureMedium, deezerApiPictureMediumSize},
|
|
{artist.PictureSmall, deezerApiPictureSmallSize},
|
|
}
|
|
for _, imgData := range possibleImages {
|
|
if imgData.URL != "" {
|
|
res = append(res, agents.ExternalImage{
|
|
URL: imgData.URL,
|
|
Size: imgData.Size,
|
|
})
|
|
}
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (s *deezerAgent) searchArtist(ctx context.Context, name string) (*Artist, error) {
|
|
artists, err := s.client.searchArtists(ctx, name, deezerArtistSearchLimit)
|
|
if errors.Is(err, ErrNotFound) || len(artists) == 0 {
|
|
return nil, agents.ErrNotFound
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If the first one has the same name, that's the one
|
|
if !strings.EqualFold(artists[0].Name, name) {
|
|
return nil, agents.ErrNotFound
|
|
}
|
|
return &artists[0], err
|
|
}
|
|
|
|
func (s *deezerAgent) GetSimilarArtists(ctx context.Context, _, name, _ string, limit int) ([]agents.Artist, error) {
|
|
artist, err := s.searchArtist(ctx, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
related, err := s.client.getRelatedArtists(ctx, artist.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res := slice.Map(related, func(r Artist) agents.Artist {
|
|
return agents.Artist{
|
|
Name: r.Name,
|
|
}
|
|
})
|
|
if len(res) > limit {
|
|
res = res[:limit]
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (s *deezerAgent) GetArtistTopSongs(ctx context.Context, _, artistName, _ string, count int) ([]agents.Song, error) {
|
|
artist, err := s.searchArtist(ctx, artistName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tracks, err := s.client.getTopTracks(ctx, artist.ID, count)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res := slice.Map(tracks, func(r Track) agents.Song {
|
|
return agents.Song{
|
|
Name: r.Title,
|
|
}
|
|
})
|
|
return res, nil
|
|
}
|
|
|
|
func (s *deezerAgent) GetArtistBiography(ctx context.Context, _, name, _ string) (string, error) {
|
|
artist, err := s.searchArtist(ctx, name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return s.client.getArtistBio(ctx, artist.ID)
|
|
}
|
|
|
|
func init() {
|
|
conf.AddHook(func() {
|
|
if conf.Server.Deezer.Enabled {
|
|
agents.Register(deezerAgentName, deezerConstructor)
|
|
}
|
|
})
|
|
}
|