feat(plugins): add metadata agent and scrobbler schemas for bootstrapping plugins

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan 2025-12-22 22:00:40 -05:00
parent 1733129537
commit 9481ba3662
3 changed files with 657 additions and 0 deletions

176
plugins/schemas/README.md Normal file
View File

@ -0,0 +1,176 @@
# Navidrome Plugin Schemas
This directory contains [XTP schemas](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema)
that define the plugin capabilities for Navidrome. These schemas can be used to bootstrap
new plugins using the `xtp` CLI tool.
## Available Schemas
| Schema | Description |
|--------------------------------------------|-----------------------------------------------------------------|
| [metadata_agent.yaml](metadata_agent.yaml) | Metadata agent for retrieving artist/album information |
| [scrobbler.yaml](scrobbler.yaml) | Scrobbler capability for sending play data to external services |
## Prerequisites
Install the `xtp` CLI tool. See the [XTP CLI documentation](https://docs.xtp.dylibso.com/docs/cli)
for installation instructions, or install via:
```bash
curl -fsSL https://static.dylibso.com/cli/install.sh | bash
```
## Bootstrapping a Plugin
Use the `xtp plugin init` command to generate boilerplate code from a schema.
### Supported Languages
The XTP CLI supports multiple languages via bindgen templates:
- Go
- Rust
- TypeScript
- Python
- C#
- Zig
- C++
### Examples
**Create a Go scrobbler plugin:**
```bash
xtp plugin init \
--schema-file plugins/schemas/scrobbler.yaml \
--template go \
--path ./my-scrobbler \
--name my-scrobbler
```
**Create a Rust metadata agent plugin:**
```bash
xtp plugin init \
--schema-file plugins/schemas/metadata_agent.yaml \
--template rust \
--path ./my-agent \
--name my-agent
```
**Create a TypeScript scrobbler plugin:**
```bash
xtp plugin init \
--schema-file plugins/schemas/scrobbler.yaml \
--template typescript \
--path ./ts-scrobbler \
--name ts-scrobbler
```
### Generated Files
After running `xtp plugin init`, you'll get a project structure with:
- `main.go` (or equivalent for your language) - Plugin implementation with stub functions
- `pdk.gen.go` - Generated types from the schema
- `xtp.toml` - Plugin configuration
- Build scripts for your language
### Implementing the Plugin
Edit the generated `main.go` file and replace the `panic()` calls with your implementation.
> **Note:** You don't need to implement all generated functions. Remove any functions that
> your plugin doesn't need. Navidrome will only call the functions that are exported by your
> plugin, and will gracefully handle missing capabilities.
#### Required: The `nd_manifest` Function
In addition to the capability functions generated from the schema, **every plugin must
implement the `nd_manifest` function**. This function returns metadata about your plugin
that Navidrome uses to identify and describe it.
**Go example:**
```go
import (
"encoding/json"
"github.com/extism/go-pdk"
)
type Manifest struct {
Name string `json:"name"`
Author string `json:"author"`
Version string `json:"version"`
Description string `json:"description"`
}
//go:wasmexport nd_manifest
func ndManifest() int32 {
manifest := Manifest{
Name: "My Scrobbler Plugin",
Author: "Your Name",
Version: "1.0.0",
Description: "A custom scrobbler for My Service",
}
out, err := json.Marshal(manifest)
if err != nil {
pdk.SetError(err)
return 1
}
pdk.Output(out)
return 0
}
```
**Python example:**
```python
import extism
@extism.plugin_fn
def nd_manifest():
import json
manifest = {
"name": "My Scrobbler Plugin",
"author": "Your Name",
"version": "1.0.0",
"description": "A custom scrobbler for My Service"
}
extism.output_str(json.dumps(manifest))
```
#### Implementing Capability Functions
Replace the `panic()` calls in the generated stubs with your implementation:
```go
// Example: Implement the IsAuthorized function
func NdScrobblerIsAuthorized(input AuthInput) (AuthOutput, error) {
// Your authorization logic here
authorized := checkUserAuthorization(input.UserID, input.Username)
return AuthOutput{Authorized: authorized}, nil
}
```
### Building the Plugin
Build the plugin to WebAssembly:
```bash
xtp plugin build
```
This creates a `.wasm` file that can be loaded by Navidrome.
## Schema Format
These schemas use the [XTP Schema v1-draft](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema) format,
which is based on JSON Schema with extensions for defining plugin exports and imports.
## Resources
- [XTP Documentation](https://docs.xtp.dylibso.com/)
- [XTP Bindgen Repository](https://github.com/dylibso/xtp-bindgen)
- [Extism Plugin Development Kit](https://extism.org/docs/concepts/pdk)
- [XTP Schema Definition](https://raw.githubusercontent.com/dylibso/xtp-bindgen/5090518dd86ba5e734dc225a33066ecc0ed2e12d/plugin/schema.json)

View File

@ -0,0 +1,299 @@
version: v1-draft
exports:
nd_get_artist_mbid:
description: Retrieve the MusicBrainz ID for an artist
input:
$ref: "#/components/schemas/ArtistMBIDInput"
contentType: application/json
output:
$ref: "#/components/schemas/ArtistMBIDOutput"
contentType: application/json
nd_get_artist_url:
description: Retrieve the external URL for an artist
input:
$ref: "#/components/schemas/ArtistInput"
contentType: application/json
output:
$ref: "#/components/schemas/ArtistURLOutput"
contentType: application/json
nd_get_artist_biography:
description: Retrieve the biography for an artist
input:
$ref: "#/components/schemas/ArtistInput"
contentType: application/json
output:
$ref: "#/components/schemas/ArtistBiographyOutput"
contentType: application/json
nd_get_similar_artists:
description: Retrieve similar artists for a given artist
input:
$ref: "#/components/schemas/SimilarArtistsInput"
contentType: application/json
output:
$ref: "#/components/schemas/SimilarArtistsOutput"
contentType: application/json
nd_get_artist_images:
description: Retrieve images for an artist
input:
$ref: "#/components/schemas/ArtistInput"
contentType: application/json
output:
$ref: "#/components/schemas/ArtistImagesOutput"
contentType: application/json
nd_get_artist_top_songs:
description: Retrieve top songs for an artist
input:
$ref: "#/components/schemas/TopSongsInput"
contentType: application/json
output:
$ref: "#/components/schemas/TopSongsOutput"
contentType: application/json
nd_get_album_info:
description: Retrieve album information
input:
$ref: "#/components/schemas/AlbumInput"
contentType: application/json
output:
$ref: "#/components/schemas/AlbumInfoOutput"
contentType: application/json
nd_get_album_images:
description: Retrieve images for an album
input:
$ref: "#/components/schemas/AlbumInput"
contentType: application/json
output:
$ref: "#/components/schemas/AlbumImagesOutput"
contentType: application/json
components:
schemas:
ArtistMBIDInput:
description: Input for GetArtistMBID
properties:
id:
type: string
description: The internal Navidrome artist ID
name:
type: string
description: The artist name
required:
- id
- name
ArtistMBIDOutput:
description: Output for GetArtistMBID
properties:
mbid:
type: string
description: The MusicBrainz ID for the artist
required:
- mbid
ArtistInput:
description: Common input for artist-related functions
properties:
id:
type: string
description: The internal Navidrome artist ID
name:
type: string
description: The artist name
mbid:
type: string
nullable: true
description: The MusicBrainz ID for the artist (if known)
required:
- id
- name
ArtistURLOutput:
description: Output for GetArtistURL
properties:
url:
type: string
description: The external URL for the artist
required:
- url
ArtistBiographyOutput:
description: Output for GetArtistBiography
properties:
biography:
type: string
description: The artist biography text
required:
- biography
SimilarArtistsInput:
description: Input for GetSimilarArtists
properties:
id:
type: string
description: The internal Navidrome artist ID
name:
type: string
description: The artist name
mbid:
type: string
nullable: true
description: The MusicBrainz ID for the artist (if known)
limit:
type: integer
format: int32
description: Maximum number of similar artists to return
required:
- id
- name
- limit
ArtistRef:
description: Reference to an artist with name and optional MBID
properties:
name:
type: string
description: The artist name
mbid:
type: string
nullable: true
description: The MusicBrainz ID for the artist
required:
- name
SimilarArtistsOutput:
description: Output for GetSimilarArtists
properties:
artists:
type: array
items:
$ref: "#/components/schemas/ArtistRef"
description: List of similar artists
required:
- artists
ImageInfo:
description: Image with URL and size
properties:
url:
type: string
description: The URL of the image
size:
type: integer
format: int32
description: The size of the image in pixels (width or height)
required:
- url
- size
ArtistImagesOutput:
description: Output for GetArtistImages
properties:
images:
type: array
items:
$ref: "#/components/schemas/ImageInfo"
description: List of artist images
required:
- images
TopSongsInput:
description: Input for GetArtistTopSongs
properties:
id:
type: string
description: The internal Navidrome artist ID
name:
type: string
description: The artist name
mbid:
type: string
nullable: true
description: The MusicBrainz ID for the artist (if known)
count:
type: integer
format: int32
description: Maximum number of top songs to return
required:
- id
- name
- count
SongRef:
description: Reference to a song with name and optional MBID
properties:
name:
type: string
description: The song name
mbid:
type: string
nullable: true
description: The MusicBrainz ID for the song
required:
- name
TopSongsOutput:
description: Output for GetArtistTopSongs
properties:
songs:
type: array
items:
$ref: "#/components/schemas/SongRef"
description: List of top songs
required:
- songs
AlbumInput:
description: Common input for album-related functions
properties:
name:
type: string
description: The album name
artist:
type: string
description: The album artist name
mbid:
type: string
nullable: true
description: The MusicBrainz ID for the album (if known)
required:
- name
- artist
AlbumInfoOutput:
description: Output for GetAlbumInfo
properties:
name:
type: string
description: The album name
mbid:
type: string
description: The MusicBrainz ID for the album
description:
type: string
description: The album description/notes
url:
type: string
description: The external URL for the album
required:
- name
- mbid
- description
- url
AlbumImagesOutput:
description: Output for GetAlbumImages
properties:
images:
type: array
items:
$ref: "#/components/schemas/ImageInfo"
description: List of album images
required:
- images

View File

@ -0,0 +1,182 @@
version: v1-draft
exports:
nd_scrobbler_is_authorized:
description: Check if a user is authorized to scrobble to this service
input:
$ref: "#/components/schemas/AuthInput"
contentType: application/json
output:
$ref: "#/components/schemas/AuthOutput"
contentType: application/json
nd_scrobbler_now_playing:
description: Send a now playing notification to the scrobbling service
input:
$ref: "#/components/schemas/NowPlayingInput"
contentType: application/json
output:
$ref: "#/components/schemas/ScrobblerOutput"
contentType: application/json
nd_scrobbler_scrobble:
description: Submit a completed scrobble to the scrobbling service
input:
$ref: "#/components/schemas/ScrobbleInput"
contentType: application/json
output:
$ref: "#/components/schemas/ScrobblerOutput"
contentType: application/json
components:
schemas:
AuthInput:
description: Input for authorization check
properties:
user_id:
type: string
description: The internal Navidrome user ID
username:
type: string
description: The username of the user
required:
- user_id
- username
AuthOutput:
description: Output for authorization check
properties:
authorized:
type: boolean
description: Whether the user is authorized to scrobble
required:
- authorized
TrackInfo:
description: Track metadata for scrobbling
properties:
id:
type: string
description: The internal Navidrome track ID
title:
type: string
description: Track title
album:
type: string
description: Album name
artist:
type: string
description: Track artist
album_artist:
type: string
description: Album artist
duration:
type: number
format: float
description: Track duration in seconds
track_number:
type: integer
format: int32
description: Track number on the album
disc_number:
type: integer
format: int32
description: Disc number
mbz_recording_id:
type: string
nullable: true
description: MusicBrainz recording ID
mbz_album_id:
type: string
nullable: true
description: MusicBrainz album/release ID
mbz_artist_id:
type: string
nullable: true
description: MusicBrainz artist ID
mbz_release_group_id:
type: string
nullable: true
description: MusicBrainz release group ID
mbz_album_artist_id:
type: string
nullable: true
description: MusicBrainz album artist ID
mbz_release_track_id:
type: string
nullable: true
description: MusicBrainz release track ID
required:
- id
- title
- album
- artist
- album_artist
- duration
- track_number
- disc_number
NowPlayingInput:
description: Input for now playing notification
properties:
user_id:
type: string
description: The internal Navidrome user ID
username:
type: string
description: The username of the user
track:
$ref: "#/components/schemas/TrackInfo"
description: The track currently playing
position:
type: integer
format: int32
description: Current playback position in seconds
required:
- user_id
- username
- track
- position
ScrobbleInput:
description: Input for submitting a scrobble
properties:
user_id:
type: string
description: The internal Navidrome user ID
username:
type: string
description: The username of the user
track:
$ref: "#/components/schemas/TrackInfo"
description: The track that was played
timestamp:
type: integer
format: int64
description: Unix timestamp when the track started playing
required:
- user_id
- username
- track
- timestamp
ScrobblerOutput:
description: Output for scrobbler operations (now_playing and scrobble)
properties:
error:
type: string
nullable: true
description: Error message if the operation failed
error_type:
$ref: "#/components/schemas/ScrobblerErrorType"
nullable: true
description: Type of error for handling
ScrobblerErrorType:
type: string
description: Error type indicating how Navidrome should handle the error
enum:
- none
- not_authorized
- retry_later
- unrecoverable