Navidrome's plugin config editor renders manifest.json's config.uiSchema
directly through @jsonforms/react (JsonForms component), not
react-jsonschema-form. The previous uiSchema ({"field": {"ui:widget":
"password"}}) is RJSF syntax, which JsonForms can't map to any
renderer, producing "No applicable renderer found" and blocking
plugin configuration/enabling entirely.
Replace it with a proper JSONForms UI schema (VerticalLayout of
Control elements referencing each property by JSON pointer), using
options.format: "password" on the two secret fields, matching how
Navidrome's own OutlinedTextRenderer picks the password input type.
Bump to 0.1.1.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018n5ffHCn5QUjpDtuWTk3fh
Scrobbled Navidrome Plugin
A Navidrome scrobbler plugin that sends now-playing updates and scrobbles to a self-hosted Scrobbled server (a Last.fm Audioscrobbler 2.0-compatible service).
It's a WebAssembly plugin built with Extism, written in
Python. Navidrome has no first-class Python SDK for plugins ("no Python PDK"),
so this plugin calls Navidrome's host services directly, following the pattern
used by Navidrome's own nowplaying-py
and coverartarchive-py
example plugins.
How it works
Navidrome calls this plugin's four nd_scrobbler_* exports as users play
music. The plugin translates each call into a signed, session-authenticated
request against Scrobbled's /2.0/ Last.fm-compatible API:
nd_scrobbler_is_authorized— a fast local check (no network call) that the plugin is configured and, ifnavidrome_usernameis set, that the request is for that user.nd_scrobbler_now_playing→track.updateNowPlayingnd_scrobbler_scrobble→track.scrobblend_scrobbler_playback_report— required by Navidrome's Scrobbler capability, but implemented as a no-op since Scrobbled has no playback-state concept beyond now-playing/scrobble.
The Scrobbled session key (sk), obtained once via the auth.getMobileSession
handshake, is cached in Navidrome's per-plugin KVStore so it isn't re-derived
from the configured password on every call. If Scrobbled reports the cached
session key as invalid, the plugin re-authenticates once and retries.
This is a single-account plugin: one Navidrome user's plays scrobble to one Scrobbled account, configured directly in the plugin's config.
Configuration
Set these via the Navidrome admin UI (Plugins page) or
navidrome plugin edit <id> --config '...':
| Key | Required | Description |
|---|---|---|
server_url |
yes | Base URL of your Scrobbled instance, e.g. https://scrobbled.uk |
api_key |
yes | API key from a Scrobbled "app" you create in its web UI |
shared_secret |
yes | Shared secret paired with the API key |
scrobbled_username |
yes | Your Scrobbled account username |
scrobbled_password |
yes | Your Scrobbled account password (used only for the session handshake, never sent on scrobble calls) |
navidrome_username |
no | If set, only this Navidrome username is treated as authorized |
You also need to assign the plugin to the relevant Navidrome user(s), e.g.:
navidrome plugin edit navidrome-scrobbled --users <your-navidrome-username>
Building
Requires extism-py and
Binaryen (wasm-opt/wasm-merge)
on PATH. The official installer fetches both:
curl -fsSL https://raw.githubusercontent.com/extism/python-pdk/main/install.sh | bash
make build # compiles plugin/__init__.py -> dist/plugin.wasm
make package # packages manifest.json + dist/plugin.wasm -> dist/navidrome-scrobbled.ndp
make validate # navidrome plugin validate dist/navidrome-scrobbled.ndp (needs a navidrome binary on PATH)
Testing
make test # or: python3 -m unittest discover -s tests -v
tests/test_logic.py exercises the plugin's pure business logic (request
signing, form-encoding, track-field mapping) under plain CPython, using a
minimal fake extism module (tests/stubs/extism.py) — no wasm toolchain
required. If a checkout of the ScrobblingService server source is available
on the same machine, it also cross-checks the signing algorithm against that
project's own implementation.
Installing
Copy the built .ndp file into your Navidrome plugins folder (or install it
via whatever mechanism your Navidrome deployment provides for plugin files),
then configure and assign users as described above.
CI/CD
Gitea Actions (.gitea/workflows/ci.yml) lints and tests every push and pull
request, builds, packages and validates the .ndp on every push, and — on
pushes to main or v* tags — publishes the built artifact both to this
repo's own Gitea package registry and to the same
pkgs.daubney.dev ProGet asset feed used by
ScrobblingService, under the navidrome-scrobbled content path.
License
BSD 3-Clause. See LICENSE.