mirror of
https://github.com/navidrome/navidrome.git
synced 2026-05-03 06:51:16 +00:00
feat(plugins): add metadata agent and scrobbler schemas for bootstrapping plugins
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
1733129537
commit
9481ba3662
176
plugins/schemas/README.md
Normal file
176
plugins/schemas/README.md
Normal 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)
|
||||
299
plugins/schemas/metadata_agent.yaml
Normal file
299
plugins/schemas/metadata_agent.yaml
Normal 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
|
||||
182
plugins/schemas/scrobbler.yaml
Normal file
182
plugins/schemas/scrobbler.yaml
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user