fix: truncate long activity fields to prevent Discord rejection #18

Merged
deluan merged 3 commits from fix/truncate-long-fields into main 2026-03-04 10:48:56 -07:00
deluan commented 2026-03-04 10:25:40 -07:00 (Migrated from github.com)

Summary

  • Discord silently rejects the entire presence update when any text field exceeds 128 characters or any URL field exceeds 256 characters. This causes Rich Presence to simply not appear for tracks with long artist names, titles, or album names.
  • Adds truncateText and truncateURL helpers applied in sendActivity() before sending to Discord:
    • Text fields (Name, Details, State, LargeText): truncated to 127 chars + (Unicode ellipsis) if over 128 runes. Uses []rune for correct multi-byte character handling.
    • URL fields (DetailsURL, StateURL, LargeURL, SmallURL): omitted entirely if over 256 characters (a truncated URL would be broken).

Fixes #16

Inspired by the same fix in Feishin:

Test plan

  • Unit tests for truncateText (short, exactly 128, over 128, multi-byte, empty)
  • Unit tests for truncateURL (short, exactly 256, over 256, empty)
  • Integration test for sendActivity with long fields verifying truncation and URL omission
  • All existing tests still pass
## Summary - Discord silently rejects the entire presence update when any text field exceeds 128 characters or any URL field exceeds 256 characters. This causes Rich Presence to simply not appear for tracks with long artist names, titles, or album names. - Adds `truncateText` and `truncateURL` helpers applied in `sendActivity()` before sending to Discord: - **Text fields** (`Name`, `Details`, `State`, `LargeText`): truncated to 127 chars + `…` (Unicode ellipsis) if over 128 runes. Uses `[]rune` for correct multi-byte character handling. - **URL fields** (`DetailsURL`, `StateURL`, `LargeURL`, `SmallURL`): omitted entirely if over 256 characters (a truncated URL would be broken). Fixes #16 Inspired by the same fix in Feishin: - https://github.com/jeffvli/feishin/pull/1263 - https://github.com/jeffvli/feishin/pull/1620 ## Test plan - [x] Unit tests for `truncateText` (short, exactly 128, over 128, multi-byte, empty) - [x] Unit tests for `truncateURL` (short, exactly 256, over 256, empty) - [x] Integration test for `sendActivity` with long fields verifying truncation and URL omission - [x] All existing tests still pass
github-actions[bot] commented 2026-03-04 10:26:55 -07:00 (Migrated from github.com)

Download the plugin for this PR: discord-rich-presence.zip

Built from bb7d6aaa5e on 2026-03-04T17:45:29Z

Download the plugin for this PR: [discord-rich-presence.zip](https://nightly.link/navidrome/discord-rich-presence-plugin/actions/runs/22681742348/discord-rich-presence.zip) Built from bb7d6aaa5e5be4d89e93d94bb97e38e50971ff88 on 2026-03-04T17:45:29Z <!-- Sticky Pull Request Comment -->
gemini-code-assist[bot] commented 2026-03-04 10:27:57 -07:00 (Migrated from github.com)

Summary of Changes

Hello, I'm Gemini Code Assist1 ! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses an issue where Discord silently rejects Rich Presence updates when certain text or URL fields exceed their character limits. The solution involves introducing helper functions to automatically truncate long text fields and omit overly long URL fields before sending activity data to Discord, thereby ensuring that Rich Presence updates are consistently displayed.

Highlights

  • Discord API Field Limits: Implemented truncation for text fields (e.g., Name, Details, State) to 128 characters (127 + ellipsis) to comply with Discord's 128-character limit, preventing silent rejection of presence updates.
  • URL Handling: URLs exceeding Discord's 256-character limit are now entirely omitted, as truncation would render them invalid.
  • Robustness: truncateText uses []rune for correct handling of multi-byte characters, ensuring accurate truncation regardless of character encoding.
  • Testing: Comprehensive unit tests were added for truncateText and truncateURL, along with an integration test for sendActivity to verify the new truncation and omission logic.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • rpc.go
    • Added maxTextLength and maxURLLength constants for Discord API field limits.
    • Implemented truncateText function to shorten strings to 128 runes, appending an ellipsis if truncated.
    • Implemented truncateURL function to return an empty string if a URL exceeds 256 characters, otherwise returns the original URL.
    • Applied truncateText and truncateURL to relevant fields within the sendActivity function to enforce Discord's character limits.
  • rpc_test.go
    • Added a new integration test case for sendActivity to verify that long text fields are truncated and long URLs are omitted as expected.
    • Included unit tests for truncateText covering various scenarios like short strings, exact length, over length, multi-byte characters, and empty strings.
    • Included unit tests for truncateURL covering short URLs, exact length, over length, and empty strings.
Activity
  • New truncateText and truncateURL helper functions were introduced to handle Discord API field length constraints.
  • The sendActivity function was updated to apply these new truncation and omission rules to all relevant text and URL fields.
  • Comprehensive unit tests were added for the new truncateText and truncateURL functions, covering edge cases like multi-byte characters and exact length limits.
  • An integration test was added for sendActivity to confirm that the truncation and URL omission logic works correctly when sending activity updates.
  • All existing tests were verified to still pass after these changes.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩︎

## Summary of Changes Hello, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses an issue where Discord silently rejects Rich Presence updates when certain text or URL fields exceed their character limits. The solution involves introducing helper functions to automatically truncate long text fields and omit overly long URL fields before sending activity data to Discord, thereby ensuring that Rich Presence updates are consistently displayed. ### Highlights * **Discord API Field Limits**: Implemented truncation for text fields (e.g., Name, Details, State) to 128 characters (127 + ellipsis) to comply with Discord's 128-character limit, preventing silent rejection of presence updates. * **URL Handling**: URLs exceeding Discord's 256-character limit are now entirely omitted, as truncation would render them invalid. * **Robustness**: `truncateText` uses `[]rune` for correct handling of multi-byte characters, ensuring accurate truncation regardless of character encoding. * **Testing**: Comprehensive unit tests were added for `truncateText` and `truncateURL`, along with an integration test for `sendActivity` to verify the new truncation and omission logic. 🧠 **New Feature in Public Preview:** You can now enable **Memory** to help **Gemini Code Assist** learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. **Click [here](https://codeassist.google/code-review/login) to enable Memory in your admin console.** <details> <summary><b>Changelog</b></summary> * **rpc.go** * Added `maxTextLength` and `maxURLLength` constants for Discord API field limits. * Implemented `truncateText` function to shorten strings to 128 runes, appending an ellipsis if truncated. * Implemented `truncateURL` function to return an empty string if a URL exceeds 256 characters, otherwise returns the original URL. * Applied `truncateText` and `truncateURL` to relevant fields within the `sendActivity` function to enforce Discord's character limits. * **rpc_test.go** * Added a new integration test case for `sendActivity` to verify that long text fields are truncated and long URLs are omitted as expected. * Included unit tests for `truncateText` covering various scenarios like short strings, exact length, over length, multi-byte characters, and empty strings. * Included unit tests for `truncateURL` covering short URLs, exact length, over length, and empty strings. </details> <details> <summary><b>Activity</b></summary> * New `truncateText` and `truncateURL` helper functions were introduced to handle Discord API field length constraints. * The `sendActivity` function was updated to apply these new truncation and omission rules to all relevant text and URL fields. * Comprehensive unit tests were added for the new `truncateText` and `truncateURL` functions, covering edge cases like multi-byte characters and exact length limits. * An integration test was added for `sendActivity` to confirm that the truncation and URL omission logic works correctly when sending activity updates. * All existing tests were verified to still pass after these changes. </details> <details> <summary><b>Using Gemini Code Assist</b></summary> <br> The full guide for Gemini Code Assist can be found on our [documentation page](https://developers.google.com/gemini-code-assist/docs/review-github-code), here are some quick tips. <b>Invoking Gemini</b> You can request assistance from Gemini at any point by creating a comment using either `/gemini <command>` or `@gemini-code-assist <command>`. Below is a summary of the supported commands on the current page. Feature | Command | Description --- | --- | --- Code Review | `/gemini review` | Performs a code review for the current pull request in its current state. Pull Request Summary | `/gemini summary` | Provides a summary of the current pull request in its current state. Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in pull request comments and review comments. Help | `/gemini help` | Displays a list of available commands. <b>Customization</b> To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a `.gemini/` folder in the base of the repository. Detailed instructions can be found [here](https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github). <b>Limitations & Feedback</b> Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up [here](https://google.qualtrics.com/jfe/form/SV_2cyuGuTWsEw84yG). <b>You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the [Gemini Code Assist IDE Extension](https://cloud.google.com/products/gemini/code-assist).</b> </details> [^1]: Review the [Privacy Notices](https://policies.google.com/privacy), [Generative AI Prohibited Use Policy](https://policies.google.com/terms/generative-ai/use-policy), [Terms of Service](https://policies.google.com/terms), and learn how to configure Gemini Code Assist in GitHub [here](https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github). Gemini can make mistakes, so double check it and [use code with caution](https://support.google.com/legal/answer/13505487).
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2026-03-04 10:28:59 -07:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull request overview

Prevents Discord Rich Presence updates from being silently rejected by enforcing Discord’s field length limits before sending presence payloads.

Changes:

  • Added truncateText (128-rune limit with ellipsis) and truncateURL (omit URLs > 256 chars) helpers.
  • Applied truncation/omission to activity payload fields in sendActivity() prior to sending to Discord.
  • Added unit tests for both helpers plus an integration-style test covering sendActivity() behavior with oversized fields.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
rpc.go Introduces truncation helpers and applies them in sendActivity() to keep presence updates within Discord limits.
rpc_test.go Adds targeted unit tests for truncation helpers and validates sendActivity() truncates/omits fields as intended.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

## Pull request overview Prevents Discord Rich Presence updates from being silently rejected by enforcing Discord’s field length limits before sending presence payloads. **Changes:** - Added `truncateText` (128-rune limit with ellipsis) and `truncateURL` (omit URLs > 256 chars) helpers. - Applied truncation/omission to activity payload fields in `sendActivity()` prior to sending to Discord. - Added unit tests for both helpers plus an integration-style test covering `sendActivity()` behavior with oversized fields. ### Reviewed changes Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments. | File | Description | | ---- | ----------- | | `rpc.go` | Introduces truncation helpers and applies them in `sendActivity()` to keep presence updates within Discord limits. | | `rpc_test.go` | Adds targeted unit tests for truncation helpers and validates `sendActivity()` truncates/omits fields as intended. | --- 💡 <a href="/navidrome/discord-rich-presence-plugin/new/main?filename=.github/instructions/*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.
gemini-code-assist[bot] (Migrated from github.com) reviewed 2026-03-04 10:33:39 -07:00
gemini-code-assist[bot] (Migrated from github.com) left a comment

Code Review

This pull request addresses an issue where long field values in Discord presence updates could lead to silent rejection, by introducing truncateText and truncateURL helper functions. No security vulnerabilities were found. The truncation logic and URL omission appear sound, and the unit tests are comprehensive. My only suggestion is to improve the robustness of the new integration test for sendActivity to make the validation less fragile.

## Code Review This pull request addresses an issue where long field values in Discord presence updates could lead to silent rejection, by introducing `truncateText` and `truncateURL` helper functions. No security vulnerabilities were found. The truncation logic and URL omission appear sound, and the unit tests are comprehensive. My only suggestion is to improve the robustness of the new integration test for `sendActivity` to make the validation less fragile.
@@ -438,0 +482,4 @@
Name: longName,
Type: 2,
Details: longTitle,
DetailsURL: longURL,
gemini-code-assist[bot] (Migrated from github.com) commented 2026-03-04 10:33:39 -07:00

medium

The test validation using strings.Contains is fragile. It could lead to false positives if the checked substring appears elsewhere in the JSON payload, or if JSON encoding adds escape characters. For example, it doesn't strictly check that the URL fields are omitted, only that the long URL string isn't present.

A more robust approach would be to unmarshal the JSON message and assert on the specific field values. This would ensure the correct fields are being truncated and that the URL fields are correctly omitted (by checking for an empty string value, or that the key is not present due to omitempty).

Consider refactoring the check like this:

// You would need to add: import "encoding/json"
host.WebSocketMock.On("SendText", "testuser", mock.MatchedBy(func(msg string) bool {
    var message struct {
        D json.RawMessage `json:"d"`
    }
    if err := json.Unmarshal([]byte(msg), &message); err != nil {
        return false
    }

    var presence presencePayload
    if err := json.Unmarshal(message.D, &presence); err != nil {
        return false
    }

    if len(presence.Activities) != 1 {
        return false
    }
    act := presence.Activities[0]

    truncatedName := strings.Repeat("N", 127) + "…"
    truncatedTitle := strings.Repeat("T", 127) + "…"
    truncatedArtist := strings.Repeat("A", 127) + "…"
    truncatedAlbum := strings.Repeat("B", 127) + "…"

    return act.Name == truncatedName &&
        act.Details == truncatedTitle &&
        act.State == truncatedArtist &&
        act.Assets.LargeText == truncatedAlbum &&
        act.DetailsURL == "" &&
        act.StateURL == "" &&
        act.Assets.LargeURL == "" &&
        act.Assets.SmallURL == ""
})).Return(nil)
![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg) The test validation using `strings.Contains` is fragile. It could lead to false positives if the checked substring appears elsewhere in the JSON payload, or if JSON encoding adds escape characters. For example, it doesn't strictly check that the URL fields are omitted, only that the long URL string isn't present. A more robust approach would be to unmarshal the JSON message and assert on the specific field values. This would ensure the correct fields are being truncated and that the URL fields are correctly omitted (by checking for an empty string value, or that the key is not present due to `omitempty`). Consider refactoring the check like this: ```go // You would need to add: import "encoding/json" host.WebSocketMock.On("SendText", "testuser", mock.MatchedBy(func(msg string) bool { var message struct { D json.RawMessage `json:"d"` } if err := json.Unmarshal([]byte(msg), &message); err != nil { return false } var presence presencePayload if err := json.Unmarshal(message.D, &presence); err != nil { return false } if len(presence.Activities) != 1 { return false } act := presence.Activities[0] truncatedName := strings.Repeat("N", 127) + "…" truncatedTitle := strings.Repeat("T", 127) + "…" truncatedArtist := strings.Repeat("A", 127) + "…" truncatedAlbum := strings.Repeat("B", 127) + "…" return act.Name == truncatedName && act.Details == truncatedTitle && act.State == truncatedArtist && act.Assets.LargeText == truncatedAlbum && act.DetailsURL == "" && act.StateURL == "" && act.Assets.LargeURL == "" && act.Assets.SmallURL == "" })).Return(nil) ```
Sign in to join this conversation.