From 2f846f2a8760aae9ea2b145623c1747633a711bc Mon Sep 17 00:00:00 2001 From: deluan Date: Wed, 4 Mar 2026 12:17:58 -0500 Subject: [PATCH] fix: truncate long activity fields before sending to Discord Apply truncateText to Name, Details, State, and LargeText fields. Apply truncateURL to DetailsURL, StateURL, LargeURL, and SmallURL fields. This prevents Discord from silently rejecting the entire presence update. Fixes #16 --- rpc.go | 12 ++++++++++++ rpc_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) 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() {