diff --git a/rpc.go b/rpc.go index bc33b60..8cc8fea 100644 --- a/rpc.go +++ b/rpc.go @@ -224,6 +224,18 @@ func (r *discordRPC) processImage(imageURL, clientID, token string, ttl int64) ( func (r *discordRPC) sendActivity(clientID, username, token string, data activity) error { pdk.Log(pdk.LogInfo, fmt.Sprintf("Sending activity for user %s: %s - %s", username, data.Details, data.State)) + // Truncate text fields to Discord's 128-character limit + data.Name = truncateText(data.Name) + data.Details = truncateText(data.Details) + data.State = truncateText(data.State) + data.Assets.LargeText = truncateText(data.Assets.LargeText) + + // Omit URLs that exceed Discord's 256-character limit + data.DetailsURL = truncateURL(data.DetailsURL) + data.StateURL = truncateURL(data.StateURL) + data.Assets.LargeURL = truncateURL(data.Assets.LargeURL) + data.Assets.SmallURL = truncateURL(data.Assets.SmallURL) + // Try track artwork first, fall back to Navidrome logo usingDefaultImage := false processedImage, err := r.processImage(data.Assets.LargeImage, clientID, token, imageCacheTTL) diff --git a/rpc_test.go b/rpc_test.go index 1b3931b..ba5270d 100644 --- a/rpc_test.go +++ b/rpc_test.go @@ -435,6 +435,49 @@ var _ = Describe("discordRPC", func() { }) Expect(err).ToNot(HaveOccurred()) }) + + It("truncates long text fields and omits long URLs", func() { + host.CacheMock.On("GetString", discordImageKey).Return("mp:cached/art", true, nil).Once() + host.CacheMock.On("GetString", discordImageKey).Return("mp:cached/logo", true, nil).Once() + + longName := strings.Repeat("N", 200) + longTitle := strings.Repeat("T", 200) + longArtist := strings.Repeat("A", 200) + longAlbum := strings.Repeat("B", 200) + longURL := "https://example.com/" + strings.Repeat("x", 237) + + host.WebSocketMock.On("SendText", "testuser", mock.MatchedBy(func(msg string) bool { + // Text fields should be truncated to 128 runes (127 + "…") + truncatedName := strings.Repeat("N", 127) + "…" + truncatedTitle := strings.Repeat("T", 127) + "…" + truncatedArtist := strings.Repeat("A", 127) + "…" + truncatedAlbum := strings.Repeat("B", 127) + "…" + return strings.Contains(msg, truncatedName) && + strings.Contains(msg, truncatedTitle) && + strings.Contains(msg, truncatedArtist) && + strings.Contains(msg, truncatedAlbum) && + !strings.Contains(msg, longURL) // URL should be omitted + })).Return(nil) + + err := r.sendActivity("client123", "testuser", "token123", activity{ + Application: "client123", + Name: longName, + Type: 2, + Details: longTitle, + DetailsURL: longURL, + State: longArtist, + StateURL: longURL, + Assets: activityAssets{ + LargeImage: "https://example.com/art.jpg", + LargeText: longAlbum, + LargeURL: longURL, + SmallImage: navidromeLogoURL, + SmallText: "Navidrome", + SmallURL: longURL, + }, + }) + Expect(err).ToNot(HaveOccurred()) + }) }) Describe("clearActivity", func() {