diff --git a/coverart.go b/coverart.go index 94fd46b..46592a0 100644 --- a/coverart.go +++ b/coverart.go @@ -1,29 +1,16 @@ package main import ( - "encoding/base64" "encoding/json" "fmt" - "net/url" "strings" "github.com/navidrome/navidrome/plugins/pdk/go/host" "github.com/navidrome/navidrome/plugins/pdk/go/pdk" ) -// Configuration keys for image hosting -const ( - imageHostKey = "imagehost" - imgbbApiKeyKey = "imgbbapikey" -) - -// imgbb API response -type imgbbResponse struct { - Data struct { - DisplayURL string `json:"display_url"` - } `json:"data"` - Success bool `json:"success"` -} +// Configuration key for uguu.se image hosting +const uguuEnabledKey = "uguuenabled" // uguu.se API response type uguuResponse struct { @@ -33,18 +20,13 @@ type uguuResponse struct { } `json:"files"` } -// getImageURL retrieves the track artwork URL, optionally uploading to a public image host. +// getImageURL retrieves the track artwork URL, optionally uploading to uguu.se. func getImageURL(username, trackID string) string { - imageHost, _ := pdk.GetConfig(imageHostKey) - - switch imageHost { - case "imgbb": - return getImageViaImgbb(username, trackID) - case "uguu": + uguuEnabled, _ := pdk.GetConfig(uguuEnabledKey) + if uguuEnabled == "true" { return getImageViaUguu(username, trackID) - default: - return getImageDirect(trackID) } + return getImageDirect(trackID) } // getImageDirect returns the artwork URL directly from Navidrome (current behavior). @@ -62,16 +44,13 @@ func getImageDirect(trackID string) string { return artworkURL } -// uploadFunc uploads image data to an external host and returns the public URL. -type uploadFunc func(contentType string, data []byte) (string, error) - -// getImageViaHost fetches artwork and uploads it using the provided upload function. -func getImageViaHost(provider, username, trackID string, cacheTTL int64, upload uploadFunc) string { +// getImageViaUguu fetches artwork and uploads it to uguu.se. +func getImageViaUguu(username, trackID string) string { // Check cache first - cacheKey := fmt.Sprintf("%s.artwork.%s", provider, trackID) + cacheKey := fmt.Sprintf("uguu.artwork.%s", trackID) cachedURL, exists, err := host.CacheGetString(cacheKey) if err == nil && exists { - pdk.Log(pdk.LogDebug, fmt.Sprintf("Cache hit for %s artwork: %s", provider, trackID)) + pdk.Log(pdk.LogDebug, fmt.Sprintf("Cache hit for uguu artwork: %s", trackID)) return cachedURL } @@ -82,67 +61,17 @@ func getImageViaHost(provider, username, trackID string, cacheTTL int64, upload return "" } - // Upload to external host - url, err := upload(contentType, data) + // Upload to uguu.se + url, err := uploadToUguu(data, contentType) if err != nil { - pdk.Log(pdk.LogWarn, fmt.Sprintf("Failed to upload to %s: %v", provider, err)) + pdk.Log(pdk.LogWarn, fmt.Sprintf("Failed to upload to uguu.se: %v", err)) return "" } - _ = host.CacheSetString(cacheKey, url, cacheTTL) + _ = host.CacheSetString(cacheKey, url, 9000) return url } -// getImageViaImgbb fetches artwork and uploads it to imgbb. -func getImageViaImgbb(username, trackID string) string { - apiKey, ok := pdk.GetConfig(imgbbApiKeyKey) - if !ok || apiKey == "" { - pdk.Log(pdk.LogWarn, "imgbb image host selected but no API key configured") - return "" - } - - return getImageViaHost("imgbb", username, trackID, 82800, func(_ string, data []byte) (string, error) { - return uploadToImgbb(apiKey, data) - }) -} - -// getImageViaUguu fetches artwork and uploads it to uguu.se. -func getImageViaUguu(username, trackID string) string { - return getImageViaHost("uguu", username, trackID, 9000, func(contentType string, data []byte) (string, error) { - return uploadToUguu(data, contentType) - }) -} - -// uploadToImgbb uploads image data to imgbb and returns the display URL. -func uploadToImgbb(apiKey string, imageData []byte) (string, error) { - encoded := base64.StdEncoding.EncodeToString(imageData) - body := fmt.Sprintf("key=%s&image=%s&expiration=86400", url.QueryEscape(apiKey), url.QueryEscape(encoded)) - - req := pdk.NewHTTPRequest(pdk.MethodPost, "https://api.imgbb.com/1/upload") - req.SetHeader("Content-Type", "application/x-www-form-urlencoded") - req.SetBody([]byte(body)) - - resp := req.Send() - if resp.Status() >= 400 { - return "", fmt.Errorf("imgbb upload failed: HTTP %d", resp.Status()) - } - - var result imgbbResponse - if err := json.Unmarshal(resp.Body(), &result); err != nil { - return "", fmt.Errorf("failed to parse imgbb response: %w", err) - } - - if !result.Success { - return "", fmt.Errorf("imgbb upload was not successful") - } - - if result.Data.DisplayURL == "" { - return "", fmt.Errorf("imgbb returned empty display URL") - } - - return result.Data.DisplayURL, nil -} - // uploadToUguu uploads image data to uguu.se and returns the file URL. func uploadToUguu(imageData []byte, contentType string) (string, error) { // Build multipart/form-data body manually (TinyGo-compatible) diff --git a/coverart_test.go b/coverart_test.go index 4d0dfcb..e3131e8 100644 --- a/coverart_test.go +++ b/coverart_test.go @@ -23,9 +23,9 @@ var _ = Describe("getImageURL", func() { pdk.PDKMock.On("Log", mock.Anything, mock.Anything).Maybe() }) - Describe("no image host configured", func() { + Describe("uguu disabled (default)", func() { BeforeEach(func() { - pdk.PDKMock.On("GetConfig", imageHostKey).Return("", false) + pdk.PDKMock.On("GetConfig", uguuEnabledKey).Return("", false) }) It("returns artwork URL directly", func() { @@ -50,77 +50,9 @@ var _ = Describe("getImageURL", func() { }) }) - Describe("imgbb image host", func() { + Describe("uguu enabled", func() { BeforeEach(func() { - pdk.PDKMock.On("GetConfig", imageHostKey).Return("imgbb", true) - }) - - It("returns cached URL when available", func() { - pdk.PDKMock.On("GetConfig", imgbbApiKeyKey).Return("test-api-key", true) - host.CacheMock.On("GetString", "imgbb.artwork.track1").Return("https://i.ibb.co/cached.jpg", true, nil) - - url := getImageURL("testuser", "track1") - Expect(url).To(Equal("https://i.ibb.co/cached.jpg")) - }) - - It("uploads artwork and caches the result", func() { - pdk.PDKMock.On("GetConfig", imgbbApiKeyKey).Return("test-api-key", true) - host.CacheMock.On("GetString", "imgbb.artwork.track1").Return("", false, nil) - - // Mock SubsonicAPICallRaw - imageData := []byte("fake-image-data") - host.SubsonicAPIMock.On("CallRaw", "/getCoverArt?u=testuser&id=track1&size=300"). - Return("image/jpeg", imageData, nil) - - // Mock imgbb HTTP upload - imgbbReq := &pdk.HTTPRequest{} - pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, "https://api.imgbb.com/1/upload").Return(imgbbReq) - pdk.PDKMock.On("Send", imgbbReq).Return(pdk.NewStubHTTPResponse(200, nil, - []byte(`{"success":true,"data":{"display_url":"https://i.ibb.co/uploaded.jpg"}}`))) - - // Mock cache set - host.CacheMock.On("SetString", "imgbb.artwork.track1", "https://i.ibb.co/uploaded.jpg", int64(82800)).Return(nil) - - url := getImageURL("testuser", "track1") - Expect(url).To(Equal("https://i.ibb.co/uploaded.jpg")) - host.CacheMock.AssertCalled(GinkgoT(), "SetString", "imgbb.artwork.track1", "https://i.ibb.co/uploaded.jpg", int64(82800)) - }) - - It("returns empty when no API key configured", func() { - pdk.PDKMock.On("GetConfig", imgbbApiKeyKey).Return("", false) - - url := getImageURL("testuser", "track1") - Expect(url).To(BeEmpty()) - }) - - It("returns empty when artwork data fetch fails", func() { - pdk.PDKMock.On("GetConfig", imgbbApiKeyKey).Return("test-api-key", true) - host.CacheMock.On("GetString", "imgbb.artwork.track1").Return("", false, nil) - host.SubsonicAPIMock.On("CallRaw", "/getCoverArt?u=testuser&id=track1&size=300"). - Return("", []byte(nil), errors.New("fetch failed")) - - url := getImageURL("testuser", "track1") - Expect(url).To(BeEmpty()) - }) - - It("returns empty when imgbb upload fails", func() { - pdk.PDKMock.On("GetConfig", imgbbApiKeyKey).Return("test-api-key", true) - host.CacheMock.On("GetString", "imgbb.artwork.track1").Return("", false, nil) - host.SubsonicAPIMock.On("CallRaw", "/getCoverArt?u=testuser&id=track1&size=300"). - Return("image/jpeg", []byte("fake-image-data"), nil) - - imgbbReq := &pdk.HTTPRequest{} - pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, "https://api.imgbb.com/1/upload").Return(imgbbReq) - pdk.PDKMock.On("Send", imgbbReq).Return(pdk.NewStubHTTPResponse(500, nil, []byte(`{"success":false}`))) - - url := getImageURL("testuser", "track1") - Expect(url).To(BeEmpty()) - }) - }) - - Describe("uguu image host", func() { - BeforeEach(func() { - pdk.PDKMock.On("GetConfig", imageHostKey).Return("uguu", true) + pdk.PDKMock.On("GetConfig", uguuEnabledKey).Return("true", true) }) It("returns cached URL when available", func() { diff --git a/main_test.go b/main_test.go index 3033efb..dfe944b 100644 --- a/main_test.go +++ b/main_test.go @@ -118,7 +118,7 @@ var _ = Describe("discordPlugin", func() { It("successfully sends now playing update", func() { 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", imageHostKey).Return("", false) + pdk.PDKMock.On("GetConfig", uguuEnabledKey).Return("", false) // Connect mocks (isConnected check via heartbeat) host.CacheMock.On("GetInt", "discord.seq.testuser").Return(int64(0), false, errors.New("not found")) diff --git a/manifest.json b/manifest.json index 8328bf8..cdf26ed 100644 --- a/manifest.json +++ b/manifest.json @@ -11,7 +11,7 @@ }, "http": { "reason": "To communicate with Discord API for gateway discovery and image uploads", - "requiredHosts": ["discord.com", "api.imgbb.com", "uguu.se"] + "requiredHosts": ["discord.com", "uguu.se"] }, "websocket": { "reason": "To maintain real-time connection with Discord gateway", @@ -42,16 +42,10 @@ "maxLength": 20, "pattern": "^[0-9]+$" }, - "imagehost": { - "type": "string", - "title": "Image Hosting Service", - "description": "Upload artwork to a public image host so Discord can display it. Required when Navidrome is not publicly accessible.", - "enum": ["", "imgbb", "uguu"] - }, - "imgbbapikey": { - "type": "string", - "title": "imgbb API Key", - "description": "API key from imgbb.com. Get one at https://api.imgbb.com/" + "uguuenabled": { + "type": "boolean", + "title": "Upload artwork to uguu.se (enable if Navidrome is not publicly accessible)", + "default": false }, "users": { "type": "array", @@ -89,19 +83,7 @@ }, { "type": "Control", - "scope": "#/properties/imagehost" - }, - { - "type": "Control", - "scope": "#/properties/imgbbapikey", - "options": { "format": "password" }, - "rule": { - "effect": "SHOW", - "condition": { - "scope": "#/properties/imagehost", - "schema": { "const": "imgbb" } - } - } + "scope": "#/properties/uguuenabled" }, { "type": "Control",