Use Cover Art Archive for albums with MusicBrainz IDs #12
@@ -88,6 +88,15 @@ For album artwork to display in Discord, Discord needs to be able to access the
|
|||||||
|
|
||||||
**How it works**: Album art is automatically uploaded to uguu.se (temporary, anonymous hosting service) so Discord can access it. Files are deleted after 3 hours.
|
**How it works**: Album art is automatically uploaded to uguu.se (temporary, anonymous hosting service) so Discord can access it. Files are deleted after 3 hours.
|
||||||
|
|
||||||
|
### Backup: Cover Art Archive
|
||||||
|
**Use this if**: You have your music tagged with MusicBrainz
|
||||||
|
|
||||||
|
**Setup**:
|
||||||
|
1. In plugin settings: **Enable** "Use artwork from Cover Art Archive"
|
||||||
|
2. No other configuration needed
|
||||||
|
|
||||||
|
**How it works**: Cover art is linked directly from MusicBrainz via Release ID using the [Cover Art Archive API](https://musicbrainz.org/doc/Cover_Art_Archive/API). Will fall back to other methods if no artwork is found.
|
||||||
|
|
||||||
### Troubleshooting Album Art
|
### Troubleshooting Album Art
|
||||||
- **No album art showing**: Check Navidrome logs for errors
|
- **No album art showing**: Check Navidrome logs for errors
|
||||||
- **Using public instance**: Verify ND_BASEURL is correct and Navidrome was restarted
|
- **Using public instance**: Verify ND_BASEURL is correct and Navidrome was restarted
|
||||||
@@ -115,6 +124,10 @@ Access the plugin configuration in Navidrome: **Settings > Plugins > Discord Ric
|
|||||||
- **Album**: Shows the currently playing track's album name
|
- **Album**: Shows the currently playing track's album name
|
||||||
- **Artist**: Shows the currently playing track's artist name
|
- **Artist**: Shows the currently playing track's artist name
|
||||||
|
|
||||||
|
#### Use artwork from Cover Art Archive
|
||||||
|
- **When to enable**: Your Navidrome instance is NOT publicly accessible from the internet, or you don't feel comfortable with directly linking
|
||||||
|
- **What it does**: Attempts to find and link album artwork with MusicBrainz before using other methods
|
||||||
|
|
||||||
#### Upload to uguu.se
|
#### Upload to uguu.se
|
||||||
- **When to enable**: Your Navidrome instance is NOT publicly accessible from the internet
|
- **When to enable**: Your Navidrome instance is NOT publicly accessible from the internet
|
||||||
- **What it does**: Automatically uploads album artwork to uguu.se (temporary hosting) so Discord can display it
|
- **What it does**: Automatically uploads album artwork to uguu.se (temporary hosting) so Discord can display it
|
||||||
|
|||||||
+114
-20
@@ -4,30 +4,16 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/plugins/pdk/go/host"
|
"github.com/navidrome/navidrome/plugins/pdk/go/host"
|
||||||
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
|
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
|
||||||
|
"github.com/navidrome/navidrome/plugins/pdk/go/scrobbler"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configuration key for uguu.se image hosting
|
// ============================================================================
|
||||||
const uguuEnabledKey = "uguuenabled"
|
// Direct
|
||||||
|
// ============================================================================
|
||||||
// uguu.se API response
|
|
||||||
type uguuResponse struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Files []struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
} `json:"files"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// getImageURL retrieves the track artwork URL, optionally uploading to uguu.se.
|
|
||||||
func getImageURL(username, trackID string) string {
|
|
||||||
uguuEnabled, _ := pdk.GetConfig(uguuEnabledKey)
|
|
||||||
if uguuEnabled == "true" {
|
|
||||||
return getImageViaUguu(username, trackID)
|
|
||||||
}
|
|
||||||
return getImageDirect(trackID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getImageDirect returns the artwork URL directly from Navidrome (current behavior).
|
// getImageDirect returns the artwork URL directly from Navidrome (current behavior).
|
||||||
func getImageDirect(trackID string) string {
|
func getImageDirect(trackID string) string {
|
||||||
@@ -44,13 +30,25 @@ func getImageDirect(trackID string) string {
|
|||||||
return artworkURL
|
return artworkURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// uguu.se
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// uguu.se API response
|
||||||
|
type uguuResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Files []struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
} `json:"files"`
|
||||||
|
}
|
||||||
|
|
||||||
// getImageViaUguu fetches artwork and uploads it to uguu.se.
|
// getImageViaUguu fetches artwork and uploads it to uguu.se.
|
||||||
func getImageViaUguu(username, trackID string) string {
|
func getImageViaUguu(username, trackID string) string {
|
||||||
// Check cache first
|
// Check cache first
|
||||||
cacheKey := fmt.Sprintf("uguu.artwork.%s", trackID)
|
cacheKey := fmt.Sprintf("uguu.artwork.%s", trackID)
|
||||||
cachedURL, exists, err := host.CacheGetString(cacheKey)
|
cachedURL, exists, err := host.CacheGetString(cacheKey)
|
||||||
if err == nil && exists {
|
if err == nil && exists {
|
||||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("Cache hit for uguu artwork: %s", trackID))
|
pdk.Log(pdk.LogDebug, fmt.Sprintf("Cache hit for uguu.se artwork: %s", trackID))
|
||||||
return cachedURL
|
return cachedURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|||||||
@@ -108,3 +106,99 @@ func uploadToUguu(imageData []byte, contentType string) (string, error) {
|
|||||||
|
|
||||||
return result.Files[0].URL, nil
|
return result.Files[0].URL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Cover Art Archive
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
const CAA_TIMEOUT = 5 * time.Second
|
||||||
|
|
||||||
|
// caaResponse only includes relevant parameters; see API for full response
|
||||||
|
// https://musicbrainz.org/doc/Cover_Art_Archive/API
|
||||||
|
type caaResponse struct {
|
||||||
|
Images []struct {
|
||||||
|
Front bool `json:"front"`
|
||||||
|
Back bool `json:"back"`
|
||||||
|
ImageURL string `json:"image"`
|
||||||
|
ThumbnailImageURLs struct {
|
||||||
|
Size250 string `json:"250"`
|
||||||
|
Size500 string `json:"500"`
|
||||||
|
Size1200 string `json:"1200"`
|
||||||
|
} `json:"thumbnails"`
|
||||||
|
} `json:"images"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getThumbnailForMBZAlbumID(mbzAlbumID string) (string, error) {
|
||||||
|
req := pdk.NewHTTPRequest(pdk.MethodGet, fmt.Sprintf("https://coverartarchive.org/release/%s", mbzAlbumID))
|
||||||
|
|
||||||
|
respChan := make(chan pdk.HTTPResponse, 1)
|
||||||
|
go func() { respChan <- req.Send() }()
|
||||||
|
|
||||||
|
var result caaResponse
|
||||||
|
|
||||||
|
select {
|
||||||
|
case resp := <-respChan:
|
||||||
|
if status := resp.Status(); status == 404 {
|
||||||
|
pdk.Log(pdk.LogDebug, fmt.Sprintf("No cover art for MusicBrainz Album ID: %s", mbzAlbumID))
|
||||||
|
return "", nil
|
||||||
|
} else if status >= 400 {
|
||||||
|
return "", fmt.Errorf("HTTP %d", resp.Status())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse: %w", err)
|
||||||
|
}
|
||||||
|
case <-time.After(CAA_TIMEOUT):
|
||||||
|
return "", fmt.Errorf("Timed out")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, image := range result.Images {
|
||||||
|
if image.Front {
|
||||||
|
return image.ThumbnailImageURLs.Size250, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pdk.Log(pdk.LogDebug, fmt.Sprintf("No front cover art for MusicBrainz Album ID: %s (%d images)", mbzAlbumID, len(result.Images)))
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getImageViaCAA(mbzAlbumID string) string {
|
||||||
|
cacheKey := fmt.Sprintf("caa.artwork.%s", mbzAlbumID)
|
||||||
|
cachedURL, exists, err := host.CacheGetString(cacheKey)
|
||||||
|
if err == nil && exists {
|
||||||
|
pdk.Log(pdk.LogDebug, fmt.Sprintf("Cache hit for Cover Art Archive artwork: %s", mbzAlbumID))
|
||||||
|
return cachedURL
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := getThumbnailForMBZAlbumID(mbzAlbumID)
|
||||||
|
if err != nil {
|
||||||
|
pdk.Log(pdk.LogWarn, fmt.Sprintf("Cover Art Archive request failed for %s: %v", mbzAlbumID, err))
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = host.CacheSetString(cacheKey, url, 86400)
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Image URL Resolution
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
const uguuEnabledKey = "uguuenabled"
|
||||||
|
const caaEnabledKey = "caaenabled"
|
||||||
|
|
||||||
|
func getImageURL(username string, track scrobbler.TrackInfo) string {
|
||||||
|
caaEnabled, _ := pdk.GetConfig(caaEnabledKey)
|
||||||
|
if caaEnabled == "true" && track.MBZAlbumID != "" {
|
||||||
|
if url := getImageViaCAA(track.MBZAlbumID); url != "" {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uguuEnabled, _ := pdk.GetConfig(uguuEnabledKey)
|
||||||
|
if uguuEnabled == "true" {
|
||||||
|
return getImageViaUguu(username, track.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getImageDirect(track.ID)
|
||||||
|
}
|
||||||
|
|||||||
+83
-8
@@ -2,9 +2,11 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/plugins/pdk/go/host"
|
"github.com/navidrome/navidrome/plugins/pdk/go/host"
|
||||||
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
|
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
|
||||||
|
"github.com/navidrome/navidrome/plugins/pdk/go/scrobbler"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
@@ -23,29 +25,30 @@ var _ = Describe("getImageURL", func() {
|
|||||||
pdk.PDKMock.On("Log", mock.Anything, mock.Anything).Maybe()
|
pdk.PDKMock.On("Log", mock.Anything, mock.Anything).Maybe()
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("uguu disabled (default)", func() {
|
Describe("direct", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
pdk.PDKMock.On("GetConfig", uguuEnabledKey).Return("", false)
|
pdk.PDKMock.On("GetConfig", uguuEnabledKey).Return("", false)
|
||||||
|
pdk.PDKMock.On("GetConfig", caaEnabledKey).Return("", false)
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns artwork URL directly", func() {
|
It("returns artwork URL directly", func() {
|
||||||
host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("https://example.com/art.jpg", nil)
|
host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("https://example.com/art.jpg", nil)
|
||||||
|
|
||||||
url := getImageURL("testuser", "track1")
|
url := getImageURL("testuser", scrobbler.TrackInfo{ID: "track1"})
|
||||||
Expect(url).To(Equal("https://example.com/art.jpg"))
|
Expect(url).To(Equal("https://example.com/art.jpg"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns empty for localhost URL", func() {
|
It("returns empty for localhost URL", func() {
|
||||||
host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("http://localhost:4533/art.jpg", nil)
|
host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("http://localhost:4533/art.jpg", nil)
|
||||||
|
|
||||||
url := getImageURL("testuser", "track1")
|
url := getImageURL("testuser", scrobbler.TrackInfo{ID: "track1"})
|
||||||
Expect(url).To(BeEmpty())
|
Expect(url).To(BeEmpty())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns empty when artwork fetch fails", func() {
|
It("returns empty when artwork fetch fails", func() {
|
||||||
host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("", errors.New("not found"))
|
host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("", errors.New("not found"))
|
||||||
|
|
||||||
url := getImageURL("testuser", "track1")
|
url := getImageURL("testuser", scrobbler.TrackInfo{ID: "track1"})
|
||||||
Expect(url).To(BeEmpty())
|
Expect(url).To(BeEmpty())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -53,12 +56,13 @@ var _ = Describe("getImageURL", func() {
|
|||||||
Describe("uguu enabled", func() {
|
Describe("uguu enabled", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
pdk.PDKMock.On("GetConfig", uguuEnabledKey).Return("true", true)
|
pdk.PDKMock.On("GetConfig", uguuEnabledKey).Return("true", true)
|
||||||
|
pdk.PDKMock.On("GetConfig", caaEnabledKey).Return("", false)
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns cached URL when available", func() {
|
It("returns cached URL when available", func() {
|
||||||
host.CacheMock.On("GetString", "uguu.artwork.track1").Return("https://a.uguu.se/cached.jpg", true, nil)
|
host.CacheMock.On("GetString", "uguu.artwork.track1").Return("https://a.uguu.se/cached.jpg", true, nil)
|
||||||
|
|
||||||
url := getImageURL("testuser", "track1")
|
url := getImageURL("testuser", scrobbler.TrackInfo{ID: "track1"})
|
||||||
Expect(url).To(Equal("https://a.uguu.se/cached.jpg"))
|
Expect(url).To(Equal("https://a.uguu.se/cached.jpg"))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -79,7 +83,7 @@ var _ = Describe("getImageURL", func() {
|
|||||||
// Mock cache set
|
// Mock cache set
|
||||||
host.CacheMock.On("SetString", "uguu.artwork.track1", "https://a.uguu.se/uploaded.jpg", int64(9000)).Return(nil)
|
host.CacheMock.On("SetString", "uguu.artwork.track1", "https://a.uguu.se/uploaded.jpg", int64(9000)).Return(nil)
|
||||||
|
|
||||||
url := getImageURL("testuser", "track1")
|
url := getImageURL("testuser", scrobbler.TrackInfo{ID: "track1"})
|
||||||
Expect(url).To(Equal("https://a.uguu.se/uploaded.jpg"))
|
Expect(url).To(Equal("https://a.uguu.se/uploaded.jpg"))
|
||||||
host.CacheMock.AssertCalled(GinkgoT(), "SetString", "uguu.artwork.track1", "https://a.uguu.se/uploaded.jpg", int64(9000))
|
host.CacheMock.AssertCalled(GinkgoT(), "SetString", "uguu.artwork.track1", "https://a.uguu.se/uploaded.jpg", int64(9000))
|
||||||
})
|
})
|
||||||
@@ -89,7 +93,7 @@ var _ = Describe("getImageURL", func() {
|
|||||||
host.SubsonicAPIMock.On("CallRaw", "/getCoverArt?u=testuser&id=track1&size=300").
|
host.SubsonicAPIMock.On("CallRaw", "/getCoverArt?u=testuser&id=track1&size=300").
|
||||||
Return("", []byte(nil), errors.New("fetch failed"))
|
Return("", []byte(nil), errors.New("fetch failed"))
|
||||||
|
|
||||||
url := getImageURL("testuser", "track1")
|
url := getImageURL("testuser", scrobbler.TrackInfo{ID: "track1"})
|
||||||
Expect(url).To(BeEmpty())
|
Expect(url).To(BeEmpty())
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -102,8 +106,79 @@ var _ = Describe("getImageURL", func() {
|
|||||||
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, "https://uguu.se/upload").Return(uguuReq)
|
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, "https://uguu.se/upload").Return(uguuReq)
|
||||||
pdk.PDKMock.On("Send", uguuReq).Return(pdk.NewStubHTTPResponse(500, nil, []byte(`{"success":false}`)))
|
pdk.PDKMock.On("Send", uguuReq).Return(pdk.NewStubHTTPResponse(500, nil, []byte(`{"success":false}`)))
|
||||||
|
|
||||||
url := getImageURL("testuser", "track1")
|
url := getImageURL("testuser", scrobbler.TrackInfo{ID: "track1"})
|
||||||
Expect(url).To(BeEmpty())
|
Expect(url).To(BeEmpty())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Describe("caa enabled", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
pdk.PDKMock.On("GetConfig", uguuEnabledKey).Return("", false)
|
||||||
|
pdk.PDKMock.On("GetConfig", caaEnabledKey).Return("true", true)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns cached URL when available", func() {
|
||||||
|
host.CacheMock.On("GetString", "caa.artwork.test").Return("https://coverartarchive.org/release/test/0-250.jpg", true, nil)
|
||||||
|
|
||||||
|
url := getImageURL("testuser", scrobbler.TrackInfo{MBZAlbumID: "test"})
|
||||||
|
Expect(url).To(Equal("https://coverartarchive.org/release/test/0-250.jpg"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("fetches 250px thumbnail and caches the result", func() {
|
||||||
|
host.CacheMock.On("GetString", "caa.artwork.test").Return("", false, nil)
|
||||||
|
|
||||||
|
// Mock coverartarchive.org HTTP get
|
||||||
|
caaReq := &pdk.HTTPRequest{}
|
||||||
|
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodGet, "https://coverartarchive.org/release/test").Return(caaReq)
|
||||||
|
pdk.PDKMock.On("Send", caaReq).Return(pdk.NewStubHTTPResponse(200, nil,
|
||||||
|
[]byte(`{"images":[{"front":true,"thumbnails":{"250":"https://coverartarchive.org/release/test/0-250.jpg"}}]}`)))
|
||||||
|
|
||||||
|
// Mock cache set
|
||||||
|
host.CacheMock.On("SetString", "caa.artwork.test", "https://coverartarchive.org/release/test/0-250.jpg", int64(86400)).Return(nil)
|
||||||
|
|
||||||
|
url := getImageURL("testuser", scrobbler.TrackInfo{MBZAlbumID: "test"})
|
||||||
|
Expect(url).To(Equal("https://coverartarchive.org/release/test/0-250.jpg"))
|
||||||
|
host.CacheMock.AssertCalled(GinkgoT(), "SetString", "caa.artwork.test", "https://coverartarchive.org/release/test/0-250.jpg", int64(86400))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns artwork directly when no MBZAlbumID is provided", func() {
|
||||||
|
host.CacheMock.On("GetString", "caa.artwork.test").Return("", false, nil)
|
||||||
|
host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("https://example.com/art.jpg", nil)
|
||||||
|
|
||||||
|
url := getImageURL("testuser", scrobbler.TrackInfo{ID: "track1"})
|
||||||
|
Expect(url).To(Equal("https://example.com/art.jpg"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns artwork directly when no suitable artwork is found", func() {
|
||||||
|
host.CacheMock.On("GetString", "caa.artwork.test").Return("", false, nil)
|
||||||
|
host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("https://example.com/art.jpg", nil)
|
||||||
|
|
||||||
|
// Mock coverartarchive.org HTTP get
|
||||||
|
caaReq := &pdk.HTTPRequest{}
|
||||||
|
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodGet, "https://coverartarchive.org/release/test").Return(caaReq)
|
||||||
|
pdk.PDKMock.On("Send", caaReq).Return(pdk.NewStubHTTPResponse(200, nil,
|
||||||
|
[]byte(`{"images":[{"front":false,"thumbnails":{"250":"https://coverartarchive.org/release/test/0-250.jpg"}}]}`)))
|
||||||
|
|
||||||
|
// Mock cache set
|
||||||
|
host.CacheMock.On("SetString", "caa.artwork.test", "", int64(86400)).Return(nil)
|
||||||
|
|
||||||
|
url := getImageURL("testuser", scrobbler.TrackInfo{ID: "track1", MBZAlbumID: "test"})
|
||||||
|
Expect(url).To(Equal("https://example.com/art.jpg"))
|
||||||
|
host.CacheMock.AssertCalled(GinkgoT(), "SetString", "caa.artwork.test", "", int64(86400))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns artwork directly after 5 second timeout", func() {
|
||||||
|
host.CacheMock.On("GetString", "caa.artwork.test").Return("", false, nil)
|
||||||
|
host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("https://example.com/art.jpg", nil)
|
||||||
|
|
||||||
|
// Mock coverartarchive.org HTTP get
|
||||||
|
caaReq := &pdk.HTTPRequest{}
|
||||||
|
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodGet, "https://coverartarchive.org/release/test").Return(caaReq)
|
||||||
|
pdk.PDKMock.On("Send", caaReq).WaitUntil(time.After(7 * time.Second)).Return(pdk.NewStubHTTPResponse(200, nil,
|
||||||
|
[]byte(`{"images":[{"front":false,"thumbnails":{"250":"https://coverartarchive.org/release/test/0-250.jpg"}}]}`)))
|
||||||
|
|
||||||
|
url := getImageURL("testuser", scrobbler.TrackInfo{ID: "track1", MBZAlbumID: "test"})
|
||||||
|
Expect(url).To(Equal("https://example.com/art.jpg"))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ func (p *discordPlugin) NowPlaying(input scrobbler.NowPlayingRequest) error {
|
|||||||
End: endTime,
|
End: endTime,
|
||||||
},
|
},
|
||||||
Assets: activityAssets{
|
Assets: activityAssets{
|
||||||
LargeImage: getImageURL(input.Username, input.Track.ID),
|
LargeImage: getImageURL(input.Username, input.Track),
|
||||||
LargeText: input.Track.Album,
|
LargeText: input.Track.Album,
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ var _ = Describe("discordPlugin", func() {
|
|||||||
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
|
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
|
||||||
pdk.PDKMock.On("GetConfig", usersKey).Return(`[{"username":"testuser","token":"test-token"}]`, true)
|
pdk.PDKMock.On("GetConfig", usersKey).Return(`[{"username":"testuser","token":"test-token"}]`, true)
|
||||||
pdk.PDKMock.On("GetConfig", uguuEnabledKey).Return("", false)
|
pdk.PDKMock.On("GetConfig", uguuEnabledKey).Return("", false)
|
||||||
|
pdk.PDKMock.On("GetConfig", caaEnabledKey).Return("", false)
|
||||||
pdk.PDKMock.On("GetConfig", activityNameKey).Return("", false)
|
pdk.PDKMock.On("GetConfig", activityNameKey).Return("", false)
|
||||||
|
|
||||||
// Connect mocks (isConnected check via heartbeat)
|
// Connect mocks (isConnected check via heartbeat)
|
||||||
@@ -177,6 +178,7 @@ var _ = Describe("discordPlugin", func() {
|
|||||||
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
|
pdk.PDKMock.On("GetConfig", clientIDKey).Return("test-client-id", true)
|
||||||
pdk.PDKMock.On("GetConfig", usersKey).Return(`[{"username":"testuser","token":"test-token"}]`, true)
|
pdk.PDKMock.On("GetConfig", usersKey).Return(`[{"username":"testuser","token":"test-token"}]`, true)
|
||||||
pdk.PDKMock.On("GetConfig", uguuEnabledKey).Return("", false)
|
pdk.PDKMock.On("GetConfig", uguuEnabledKey).Return("", false)
|
||||||
|
pdk.PDKMock.On("GetConfig", caaEnabledKey).Return("", false)
|
||||||
pdk.PDKMock.On("GetConfig", activityNameKey).Return(configValue, configExists)
|
pdk.PDKMock.On("GetConfig", activityNameKey).Return(configValue, configExists)
|
||||||
|
|
||||||
// Connect mocks
|
// Connect mocks
|
||||||
|
|||||||
+11
-1
@@ -13,7 +13,8 @@
|
|||||||
"reason": "To communicate with Discord API for gateway discovery and image uploads",
|
"reason": "To communicate with Discord API for gateway discovery and image uploads",
|
||||||
"requiredHosts": [
|
"requiredHosts": [
|
||||||
"discord.com",
|
"discord.com",
|
||||||
"uguu.se"
|
"uguu.se",
|
||||||
|
"coverartarchive.org"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"websocket": {
|
"websocket": {
|
||||||
@@ -64,6 +65,11 @@
|
|||||||
"title": "Upload artwork to uguu.se (enable if Navidrome is not publicly accessible)",
|
"title": "Upload artwork to uguu.se (enable if Navidrome is not publicly accessible)",
|
||||||
|
The default should be false. The default should be false.
|
|||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
|
"caaenabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"title": "Use artwork from Cover Art Archive when available",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
"users": {
|
"users": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"title": "User Tokens",
|
"title": "User Tokens",
|
||||||
@@ -111,6 +117,10 @@
|
|||||||
"format": "radio"
|
"format": "radio"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "Control",
|
||||||
|
"scope": "#/properties/caaenabled"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "Control",
|
"type": "Control",
|
||||||
"scope": "#/properties/uguuenabled"
|
"scope": "#/properties/uguuenabled"
|
||||||
|
|||||||
Reference in New Issue
Block a user
Caching an empty URL string prevents the fallback logic in
getImageURLfrom working on subsequent requests for the same track. The cache should only be set if a valid URL is obtained.Not relevant