mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
docs: update READMEs
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
505b3c529f
commit
aa83d3af6c
@ -89,7 +89,6 @@ type configOptions struct {
|
||||
PasswordEncryptionKey string
|
||||
ExtAuth extAuthOptions
|
||||
Plugins pluginsOptions
|
||||
PluginConfig map[string]map[string]string
|
||||
HTTPHeaders httpHeaderOptions `json:",omitzero"`
|
||||
Prometheus prometheusOptions `json:",omitzero"`
|
||||
Scanner scannerOptions `json:",omitzero"`
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,55 +1,57 @@
|
||||
# Navidrome Plugin Examples
|
||||
|
||||
This folder contains example plugins for Navidrome that demonstrate how to build metadata agents using the plugin system.
|
||||
This folder contains example plugins demonstrating various capabilities and languages supported by Navidrome's plugin system.
|
||||
|
||||
## Available Examples
|
||||
|
||||
| Plugin | Language | Capabilities | Description |
|
||||
|-------------------------------------------------|----------|-------------------------------------------------|--------------------------------|
|
||||
| [minimal](minimal/) | Go | MetadataAgent | Basic plugin structure |
|
||||
| [wikimedia](wikimedia/) | Go | MetadataAgent | Wikidata/Wikipedia metadata |
|
||||
| [crypto-ticker](crypto-ticker/) | Go | Scheduler, WebSocket, Cache | Real-time crypto prices (demo) |
|
||||
| [discord-rich-presence](discord-rich-presence/) | Go | Scrobbler, Scheduler, WebSocket, Cache, Artwork | Discord integration |
|
||||
| [coverartarchive-py](coverartarchive-py/) | Python | MetadataAgent | Cover Art Archive |
|
||||
| [nowplaying-py](nowplaying-py/) | Python | Scheduler, SubsonicAPI | Now playing logger |
|
||||
| [webhook-rs](webhook-rs/) | Rust | Scrobbler | HTTP webhook on scrobble |
|
||||
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [TinyGo](https://tinygo.org/getting-started/install/) (recommended) or Go 1.23+ (for Go plugins)
|
||||
- [extism-py](https://github.com/extism/python-pdk) (for Python plugins)
|
||||
- [Rust](https://rustup.rs/) with `wasm32-unknown-unknown` target (for Rust plugins)
|
||||
- [Extism CLI](https://extism.org/docs/install) (optional, for testing)
|
||||
- **Go plugins:** [TinyGo](https://tinygo.org/getting-started/install/) 0.30+
|
||||
- **Python plugins:** [extism-py](https://github.com/extism/python-pdk)
|
||||
- **Rust plugins:** [Rust](https://rustup.rs/) with `wasm32-unknown-unknown` target
|
||||
|
||||
### Build all plugins
|
||||
### Build All (Go plugins)
|
||||
|
||||
```bash
|
||||
make all
|
||||
```
|
||||
|
||||
This will compile all (Go) example plugins and place the `.wasm` files in this directory.
|
||||
|
||||
### Build a specific plugin
|
||||
### Build Individual Plugin
|
||||
|
||||
```bash
|
||||
make minimal.wasm
|
||||
make wikimedia.wasm
|
||||
make discord-rich-presence.wasm
|
||||
```
|
||||
|
||||
### Clean build artifacts
|
||||
### Clean
|
||||
|
||||
```bash
|
||||
make clean
|
||||
```
|
||||
|
||||
## Available Examples
|
||||
## Testing Plugins
|
||||
|
||||
| Plugin | Language | Description |
|
||||
|-------------------------------------------------|----------|-------------------------------------------------------------------------|
|
||||
| [minimal](minimal/) | Go | A minimal example showing the basic plugin structure |
|
||||
| [wikimedia](wikimedia/) | Go | Fetches artist metadata from Wikidata, DBpedia, and Wikipedia |
|
||||
| [crypto-ticker](crypto-ticker/) | Go | Real-time cryptocurrency prices from Coinbase using WebSocket |
|
||||
| [discord-rich-presence](discord-rich-presence/) | Go | Discord Rich Presence integration using Scrobbler, WebSocket, Scheduler |
|
||||
| [coverartarchive-py](coverartarchive-py/) | Python | Album cover art from Cover Art Archive (Python example) |
|
||||
| [nowplaying-py](nowplaying-py/) | Python | Logs currently playing tracks using Scheduler and SubsonicAPI |
|
||||
| [webhook-rs](webhook-rs/) | Rust | Sends HTTP webhooks on scrobble events (Rust example) |
|
||||
### With Extism CLI
|
||||
|
||||
## Testing with Extism CLI
|
||||
|
||||
You can test any plugin using the Extism CLI:
|
||||
Test any plugin without running Navidrome:
|
||||
|
||||
```bash
|
||||
# Test the manifest
|
||||
# Install: https://extism.org/docs/install
|
||||
|
||||
# Test manifest
|
||||
extism call minimal.wasm nd_manifest --wasi
|
||||
|
||||
# Test with input
|
||||
@ -57,58 +59,112 @@ extism call minimal.wasm nd_get_artist_biography --wasi \
|
||||
--input '{"id":"1","name":"The Beatles"}'
|
||||
```
|
||||
|
||||
For plugins that make HTTP requests, use `--allow-host` to permit access:
|
||||
For plugins that make HTTP requests, allow the hosts:
|
||||
|
||||
```bash
|
||||
extism call wikimedia.wasm nd_get_artist_biography --wasi \ 3s ▼
|
||||
extism call wikimedia.wasm nd_get_artist_biography --wasi \
|
||||
--input '{"id":"1","name":"Yussef Dayes"}' \
|
||||
--allow-host "query.wikidata.org" --allow-host "en.wikipedia.org"
|
||||
--allow-host "query.wikidata.org" \
|
||||
--allow-host "en.wikipedia.org"
|
||||
```
|
||||
|
||||
## Installation
|
||||
### With Navidrome
|
||||
|
||||
Copy any `.wasm` file to your Navidrome plugins folder:
|
||||
|
||||
```bash
|
||||
cp wikimedia.wasm /path/to/navidrome/plugins/
|
||||
```
|
||||
|
||||
Then enable plugins in your `navidrome.toml`:
|
||||
|
||||
```toml
|
||||
[Plugins]
|
||||
Enabled = true
|
||||
Folder = "/path/to/navidrome/plugins"
|
||||
```
|
||||
|
||||
And add the plugin to your agents list:
|
||||
|
||||
```toml
|
||||
Agents = "lastfm,spotify,wikimedia"
|
||||
```
|
||||
1. Copy the `.wasm` file to your plugins folder
|
||||
2. Enable plugins in `navidrome.toml`:
|
||||
```toml
|
||||
[Plugins]
|
||||
Enabled = true
|
||||
Folder = "/path/to/plugins"
|
||||
```
|
||||
3. For metadata agents, add to your agents list:
|
||||
```toml
|
||||
Agents = "lastfm,spotify,wikimedia"
|
||||
```
|
||||
|
||||
## Creating Your Own Plugin
|
||||
|
||||
The plugin system supports multiple languages. See the [minimal](minimal/) example for the simplest Go starting point,
|
||||
[discord-rich-presence](discord-rich-presence/) for a more complete Go example with HTTP requests, [coverartarchive-py](coverartarchive-py/)
|
||||
for a Python example, or [webhook-rs](webhook-rs/) for a Rust example.
|
||||
### Option 1: Start from Minimal
|
||||
|
||||
### Bootstrapping a New Plugin
|
||||
Use the XTP CLI to bootstrap a new plugin from a schema:
|
||||
Copy the [minimal](minimal/) example and modify:
|
||||
|
||||
```bash
|
||||
cp -r minimal my-plugin
|
||||
cd my-plugin
|
||||
# Edit main.go
|
||||
tinygo build -o my-plugin.wasm -target wasip1 -buildmode=c-shared .
|
||||
```
|
||||
|
||||
### Option 2: Bootstrap with XTP CLI
|
||||
|
||||
Generate boilerplate from a schema:
|
||||
|
||||
```bash
|
||||
# Install XTP: https://docs.xtp.dylibso.com/docs/cli
|
||||
|
||||
xtp plugin init \
|
||||
--schema-file plugins/schemas/metadata_agent.yaml \
|
||||
--schema-file ../schemas/metadata_agent.yaml \
|
||||
--template go \
|
||||
--path ./my-plugin \
|
||||
--name my-plugin
|
||||
```
|
||||
|
||||
See the [schemas README](../schemas/README.md) for more information about available schemas
|
||||
and supported languages.
|
||||
Available schemas in [../schemas/](../schemas/):
|
||||
- `metadata_agent.yaml` – Artist/album metadata
|
||||
- `scrobbler.yaml` – Scrobbling integration
|
||||
- `lifecycle.yaml` – Init callbacks
|
||||
- `scheduler_callback.yaml` – Scheduled tasks
|
||||
- `websocket_callback.yaml` – WebSocket events
|
||||
|
||||
For the simplest starting point, look at [minimal](minimal/). For a more complete example
|
||||
with HTTP requests, see [wikimedia](wikimedia/).
|
||||
### Option 3: Different Language
|
||||
|
||||
See language-specific examples:
|
||||
- **Python:** [coverartarchive-py](coverartarchive-py/)
|
||||
- **Rust:** [webhook-rs](webhook-rs/)
|
||||
|
||||
For full documentation, see the [Plugin System README](../README.md).
|
||||
## Example Breakdown
|
||||
|
||||
### Minimal (Go)
|
||||
|
||||
The simplest possible plugin. Shows:
|
||||
- Manifest export
|
||||
- Single capability function
|
||||
- Basic input/output handling
|
||||
|
||||
### Wikimedia (Go)
|
||||
|
||||
Real-world metadata agent. Shows:
|
||||
- HTTP requests to external APIs
|
||||
- SPARQL queries (Wikidata)
|
||||
- Error handling
|
||||
- Host allowlisting
|
||||
|
||||
### Discord Rich Presence (Go)
|
||||
|
||||
Complex multi-capability plugin. Shows:
|
||||
- **Scrobbler** – Receives play events
|
||||
- **WebSocket** – Maintains Discord gateway connection
|
||||
- **Scheduler** – Heartbeat and timeout management
|
||||
- **Cache** – Connection state storage
|
||||
- **Artwork** – Getting album art URLs
|
||||
|
||||
### Cover Art Archive (Python)
|
||||
|
||||
Python metadata agent. Shows:
|
||||
- extism-py plugin structure
|
||||
- HTTP requests
|
||||
- JSON handling
|
||||
|
||||
### Webhook (Rust)
|
||||
|
||||
Rust scrobbler. Shows:
|
||||
- extism-rs plugin structure
|
||||
- HTTP POST requests
|
||||
- Minimal dependencies
|
||||
|
||||
## Resources
|
||||
|
||||
- [Plugin System Documentation](../README.md)
|
||||
- [Extism PDK Docs](https://extism.org/docs/concepts/pdk)
|
||||
- [TinyGo WebAssembly](https://tinygo.org/docs/guides/webassembly/)
|
||||
- [XTP CLI](https://docs.xtp.dylibso.com/docs/cli)
|
||||
|
||||
@ -104,7 +104,6 @@ func createTestManagerWithPlugins(pluginConfig map[string]map[string]string, plu
|
||||
conf.Server.Plugins.Folder = tmpDir
|
||||
conf.Server.Plugins.AutoReload = false
|
||||
conf.Server.CacheFolder = filepath.Join(tmpDir, "cache")
|
||||
conf.Server.PluginConfig = pluginConfig
|
||||
|
||||
// Setup mock DataStore with pre-enabled plugins
|
||||
mockPluginRepo := tests.CreateMockPluginRepo()
|
||||
|
||||
@ -1,66 +1,72 @@
|
||||
# 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.
|
||||
This directory contains [XTP schemas](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema) that define plugin capabilities. Use these schemas to bootstrap new plugins with the `xtp` CLI.
|
||||
|
||||
## Available Schemas
|
||||
|
||||
| Schema | Description |
|
||||
|----------------------------------------------------|---------------------------------------------------------------------|
|
||||
| [lifecycle.yaml](lifecycle.yaml) | Lifecycle callbacks (init) for plugin initialization |
|
||||
| [metadata_agent.yaml](metadata_agent.yaml) | Metadata agent for retrieving artist/album information |
|
||||
| [scheduler_callback.yaml](scheduler_callback.yaml) | Scheduler callback for plugins using the scheduler host service |
|
||||
| [scrobbler.yaml](scrobbler.yaml) | Scrobbler capability for sending play data to external services |
|
||||
| [websocket_callback.yaml](websocket_callback.yaml) | WebSocket callbacks for handling messages, errors, and close events |
|
||||
| Schema | Description |
|
||||
|----------------------------------------------------|---------------------------------|
|
||||
| [metadata_agent.yaml](metadata_agent.yaml) | Artist/album metadata retrieval |
|
||||
| [scrobbler.yaml](scrobbler.yaml) | Scrobbling to external services |
|
||||
| [lifecycle.yaml](lifecycle.yaml) | Plugin initialization callback |
|
||||
| [scheduler_callback.yaml](scheduler_callback.yaml) | Scheduled task callbacks |
|
||||
| [websocket_callback.yaml](websocket_callback.yaml) | WebSocket event callbacks |
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install the `xtp` CLI tool. See the [XTP CLI documentation](https://docs.xtp.dylibso.com/docs/cli)
|
||||
for installation instructions, or install via:
|
||||
Install the XTP CLI:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://static.dylibso.com/cli/install.sh | bash
|
||||
```
|
||||
|
||||
Or see [XTP CLI documentation](https://docs.xtp.dylibso.com/docs/cli) for other methods.
|
||||
|
||||
## 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:**
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
xtp plugin init \
|
||||
--schema-file plugins/schemas/scrobbler.yaml \
|
||||
--template go \
|
||||
--path ./my-scrobbler \
|
||||
--name my-scrobbler
|
||||
--schema-file <schema> \
|
||||
--template <language> \
|
||||
--path <output-dir> \
|
||||
--name <plugin-name>
|
||||
```
|
||||
|
||||
**Create a Rust metadata agent plugin:**
|
||||
### Supported Languages
|
||||
|
||||
- `go` – Go (recommended, use with TinyGo)
|
||||
- `rust` – Rust
|
||||
- `typescript` – TypeScript
|
||||
- `python` – Python
|
||||
- `csharp` – C#
|
||||
- `zig` – Zig
|
||||
- `cpp` – C++
|
||||
|
||||
### Examples
|
||||
|
||||
**Go metadata agent:**
|
||||
|
||||
```bash
|
||||
xtp plugin init \
|
||||
--schema-file plugins/schemas/metadata_agent.yaml \
|
||||
--template rust \
|
||||
--template go \
|
||||
--path ./my-agent \
|
||||
--name my-agent
|
||||
```
|
||||
|
||||
**Create a TypeScript scrobbler plugin:**
|
||||
**Rust scrobbler:**
|
||||
|
||||
```bash
|
||||
xtp plugin init \
|
||||
--schema-file plugins/schemas/scrobbler.yaml \
|
||||
--template rust \
|
||||
--path ./my-scrobbler \
|
||||
--name my-scrobbler
|
||||
```
|
||||
|
||||
**TypeScript scrobbler:**
|
||||
|
||||
```bash
|
||||
xtp plugin init \
|
||||
@ -70,30 +76,23 @@ xtp plugin init \
|
||||
--name ts-scrobbler
|
||||
```
|
||||
|
||||
### Generated Files
|
||||
## Generated Files
|
||||
|
||||
After running `xtp plugin init`, you'll get a project structure with:
|
||||
After running `xtp plugin init`, you'll get:
|
||||
|
||||
- `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
|
||||
```
|
||||
my-plugin/
|
||||
├── main.go # Plugin implementation (stubs)
|
||||
├── pdk.gen.go # Generated types from schema
|
||||
├── xtp.toml # Plugin configuration
|
||||
└── go.mod # Go module (for Go plugins)
|
||||
```
|
||||
|
||||
### Implementing the Plugin
|
||||
## Implementing Your Plugin
|
||||
|
||||
Edit the generated `main.go` file and replace the `panic()` calls with your implementation.
|
||||
### 1. Add the Manifest
|
||||
|
||||
> **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:**
|
||||
Every plugin **must** implement `nd_manifest`. This is not in the schemas—add it manually:
|
||||
|
||||
```go
|
||||
import (
|
||||
@ -102,78 +101,82 @@ import (
|
||||
)
|
||||
|
||||
type Manifest struct {
|
||||
Name string `json:"name"`
|
||||
Author string `json:"author"`
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
Author string `json:"author"`
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Website string `json:"website,omitempty"`
|
||||
Permissions *Permissions `json:"permissions,omitempty"`
|
||||
}
|
||||
|
||||
//go:wasmexport nd_manifest
|
||||
func ndManifest() int32 {
|
||||
manifest := Manifest{
|
||||
Name: "My Scrobbler Plugin",
|
||||
Name: "My 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
|
||||
Description: "What this plugin does",
|
||||
}
|
||||
out, _ := json.Marshal(manifest)
|
||||
pdk.Output(out)
|
||||
return 0
|
||||
}
|
||||
```
|
||||
|
||||
**Python example:**
|
||||
### 2. Implement Capability Functions
|
||||
|
||||
```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:
|
||||
Replace the generated `panic()` 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
|
||||
// Generated stub:
|
||||
func NdGetArtistBiography(input ArtistInput) (BiographyOutput, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// Your implementation:
|
||||
func NdGetArtistBiography(input ArtistInput) (BiographyOutput, error) {
|
||||
bio := fetchBiography(input.Name)
|
||||
return BiographyOutput{Biography: bio}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### Building the Plugin
|
||||
### 3. Remove Unused Functions
|
||||
|
||||
Build the plugin to WebAssembly:
|
||||
You don't need to implement all functions from a schema. Delete any you don't need—Navidrome only calls functions that exist.
|
||||
|
||||
### 4. Build
|
||||
|
||||
```bash
|
||||
xtp plugin build
|
||||
```
|
||||
|
||||
This creates a `.wasm` file that can be loaded by Navidrome.
|
||||
Or manually with TinyGo:
|
||||
|
||||
```bash
|
||||
tinygo build -o my-plugin.wasm -target wasip1 -buildmode=c-shared .
|
||||
```
|
||||
|
||||
## Combining Capabilities
|
||||
|
||||
A single plugin can implement multiple capabilities. Generate from one schema, then manually add functions from others:
|
||||
|
||||
```bash
|
||||
# Start with metadata agent
|
||||
xtp plugin init --schema-file metadata_agent.yaml --template go --path ./my-plugin --name my-plugin
|
||||
|
||||
# Manually add scrobbler functions from scrobbler.yaml
|
||||
# Manually add scheduler callback from scheduler_callback.yaml
|
||||
```
|
||||
|
||||
Or combine schemas manually before generating (advanced).
|
||||
|
||||
## 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.
|
||||
These schemas use [XTP Schema v1-draft](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema) format, which extends JSON Schema with plugin-specific extensions for exports and imports.
|
||||
|
||||
## Resources
|
||||
|
||||
- [Plugin System Documentation](../README.md)
|
||||
- [XTP Documentation](https://docs.xtp.dylibso.com/)
|
||||
- [XTP Bindgen Repository](https://github.com/dylibso/xtp-bindgen)
|
||||
- [XTP Schema Definition](https://raw.githubusercontent.com/dylibso/xtp-bindgen/5090518dd86ba5e734dc225a33066ecc0ed2e12d/plugin/schema.json)
|
||||
- [Extism Plugin Development Kit](https://extism.org/docs/concepts/pdk)
|
||||
- [XTP Schema Reference](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema)
|
||||
- [Extism PDK](https://extism.org/docs/concepts/pdk)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user