Files
navidrome/plugins/examples/discord-rich-presence-rs
Deluan Quintão f1e75c40dc feat(plugins): add JSONForms-based plugin configuration UI (#4911)
* feat(plugins): add JSONForms schema for plugin configuration

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: enhance error handling by formatting validation errors with field names

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: enforce required fields in config validation and improve error handling

Signed-off-by: Deluan <deluan@navidrome.org>

* format JS code

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add config schema validation and enhance manifest structure

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: refactor plugin config parsing and add unit tests

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add config validation error message in Portuguese

* feat: enhance AlwaysExpandedArrayLayout with description support and improve array control testing

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: update Discord Rust plugin configuration to use JSONForm for user tokens and enhance schema validation

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: resolve React Hooks linting issues in plugin UI components

* Apply suggestions from code review

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* format code

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: migrate schema validation to use santhosh-tekuri/jsonschema and improve error formatting

Signed-off-by: Deluan <deluan@navidrome.org>

* address PR comments

Signed-off-by: Deluan <deluan@navidrome.org>

* fix flaky test

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: enhance array layout and configuration handling with AJV defaults

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement custom tester to exclude enum arrays from AlwaysExpandedArrayLayout

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add error boundary for schema rendering and improve error messages

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: refine non-enum array control logic by utilizing JSONForms schema resolution

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add error styling to ToggleEnabledSwitch for disabled state

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: adjust label positioning and styling in SchemaConfigEditor for improved layout

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement outlined input controls renderers to replace custom fragile CSS

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: remove margin from last form control inside array items for better spacing

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: enhance AJV error handling to transform required errors for field-level validation

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: set default value for User Tokens in manifest.json to improve user experience

Signed-off-by: Deluan <deluan@navidrome.org>

* format

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add margin to outlined input controls for improved spacing

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: remove redundant margin rule for last form control in array items

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: adjust font size of label elements in SchemaConfigEditor for improved readability

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-01-19 20:51:00 -05:00
..

Discord Rich Presence Plugin (Rust)

A Navidrome plugin that displays your currently playing track on Discord using Rich Presence. This is the Rust implementation demonstrating how to use the nd-pdk library.

⚠️ Warning

This plugin is for demonstration purposes only. It requires storing your Discord token in the Navidrome configuration file, which:

  1. Is not secure (tokens should never be stored in plain text)
  2. May violate Discord's Terms of Service

Use at your own risk.

Features

  • Shows currently playing track on Discord Rich Presence
  • Displays album artwork
  • Shows track progress with start/end timestamps
  • Automatically clears presence when track finishes
  • Supports multiple users

Capabilities

This plugin implements multiple capabilities to demonstrate the nd-pdk library:

  • Scrobbler: Receives now-playing events from Navidrome
  • SchedulerCallback: Handles heartbeat and activity clearing timers
  • WebSocketCallback: Communicates with Discord gateway (text, binary, error, and close handlers)

Configuration

Configure in the Navidrome UI (Settings → Plugins → discord-rich-presence):

Key Description Example
clientid Your Discord application ID 123456789012345678
user.<name> Discord token for the specified user user.alice = token123

Each user is configured as a separate key with the user. prefix.

Getting Configuration Values

  1. Client ID: Create a Discord Application at https://discord.com/developers/applications and copy the Application ID

  2. Discord Token: This requires extracting your user token from Discord (not recommended for security reasons)

  3. Multiple Users: Add multiple user keys:

    user.user1 = "token1"
    user.user2 = "token2"
    

Building

# From the plugins/examples directory
make discord-rich-presence-rs.ndp

# This creates discord-rich-presence-rs.ndp containing:
# - manifest.json
# - plugin.wasm

Installation

  1. Build the plugin using the command above
  2. Copy the .ndp file to your Navidrome plugins directory
  3. Enable and configure the plugin in the Navidrome UI (Settings → Plugins)
  4. Restart Navidrome if needed

Using nd-pdk Library

This plugin demonstrates how to use the Rust plugin development kit:

use nd_pdk::host::{artwork, cache, scheduler, websocket};
use std::collections::HashMap;

// Get artwork URL
let url = artwork::get_track_url(track_id, 300)?;

// Cache operations
cache::set_string("key", "value", 3600)?;
if let Some(value) = cache::get_string("key")? {
    // Use the cached value
}

// Schedule tasks
scheduler::schedule_one_time(60, "payload", "task-id")?;
scheduler::schedule_recurring("@every 30s", "heartbeat", "heartbeat-task")?;

// WebSocket operations
let conn_id = websocket::connect("wss://example.com/socket", HashMap::new(), "my-conn")?;
websocket::send_text(&conn_id, "Hello")?;

License

GPL-3.0