# Navidrome Plugin Development Kit for GoThis directory contains the auto-generated Go PDK (Plugin Development Kit) for building Navidrome plugins.The PDK provides both **host function wrappers** for interacting with Navidrome and**capability interfaces** for implementing plugin functionality.## ⚠️ Auto-Generated Code**Do not edit files in this directory manually.** They are generated by the `ndpgen` tool.To regenerate:```bashmake gen```## Module StructureThis is a consolidated Go module that includes:- `host/` - Host function wrappers for calling Navidrome services from plugins- `lifecycle/` - Plugin lifecycle hooks (initialization)- `metadata/` - Metadata agent capability for artist/album info- `scheduler/` - Scheduler callback capability for scheduled tasks- `scrobbler/` - Scrobbler capability for play tracking- `websocket/` - WebSocket callback capability for real-time messages## UsageAdd this module as a dependency in your plugin's `go.mod`:```gorequire github.com/navidrome/navidrome/plugins/pdk/go v0.0.0replace github.com/navidrome/navidrome/plugins/pdk/go => ../../pdk/go```Then import the packages you need:```gopackage mainimport ("github.com/navidrome/navidrome/plugins/pdk/go/host""github.com/navidrome/navidrome/plugins/pdk/go/lifecycle""github.com/navidrome/navidrome/plugins/pdk/go/scheduler")func init() {lifecycle.Register(&myPlugin{})scheduler.Register(&myPlugin{})}type myPlugin struct{}func (p *myPlugin) OnInit() error {// Initialize your pluginreturn nil}func (p *myPlugin) OnCallback(req scheduler.SchedulerCallbackRequest) error {// Handle scheduled taskreturn host.WebSocketBroadcast("task-complete", req.ScheduleID)}func main() {}```## Host ServicesThe `host` package provides wrappers for calling Navidrome's host services:| Service | Description ||---------------|----------------------------------------------------|| `Artwork` | Access album and artist artwork || `Cache` | Temporary key-value storage with TTL || `KVStore` | Persistent key-value storage || `Library` | Access the music library (albums, artists, tracks) || `Scheduler` | Schedule one-time and recurring tasks || `SubsonicAPI` | Make Subsonic API calls || `WebSocket` | Send real-time messages to clients |### Example: Using Host Services```gopackage mainimport ("github.com/navidrome/navidrome/plugins/pdk/go/host")func myPluginFunction() error {// Use the cache service_, err := host.CacheSetString("my_key", "my_value", 3600)if err != nil {return err}// Schedule a recurring task_, err = host.SchedulerScheduleRecurring("@every 5m", "payload", "task_id")if err != nil {return err}// Access library data with typed structsresp, err := host.LibraryGetAllLibraries()if err != nil {return err}for _, lib := range resp.Result {// Library: %s with %d songs", lib.Name, lib.TotalSongs}return nil}```## CapabilitiesCapabilities define what functionality your plugin implements. Register your implementationsin the `init()` function.### LifecycleProvides plugin initialization hooks.```goimport "github.com/navidrome/navidrome/plugins/pdk/go/lifecycle"func init() {lifecycle.Register(&myPlugin{})}type myPlugin struct{}func (p *myPlugin) OnInit() error {// Called once when plugin is loadedreturn nil}```### MetadataAgentProvides artist and album metadata from external sources.```goimport "github.com/navidrome/navidrome/plugins/pdk/go/metadata"func init() {metadata.Register(&myAgent{})}type myAgent struct{}func (a *myAgent) GetArtistBiography(req metadata.ArtistRequest) (*metadata.ArtistBiographyResponse, error) {return &metadata.ArtistBiographyResponse{Biography: "Artist biography text...",}, nil}func (a *myAgent) GetArtistImages(req metadata.ArtistRequest) (*metadata.ArtistImagesResponse, error) {return &metadata.ArtistImagesResponse{Images: []metadata.ImageInfo{{URL: "https://example.com/image.jpg", Size: 1000},},}, nil}```### SchedulerHandles callbacks from scheduled tasks.```goimport ("github.com/navidrome/navidrome/plugins/pdk/go/host""github.com/navidrome/navidrome/plugins/pdk/go/scheduler")func init() {scheduler.Register(&myScheduler{})}type myScheduler struct{}func (s *myScheduler) OnCallback(req scheduler.SchedulerCallbackRequest) error {// Handle the scheduled taskif req.Payload == "update-data" {// Do work...return host.WebSocketBroadcast("data-updated", "")}return nil}```### ScrobblerTracks play activity.```goimport "github.com/navidrome/navidrome/plugins/pdk/go/scrobbler"func init() {scrobbler.Register(&myScrobbler{})}type myScrobbler struct{}func (s *myScrobbler) Scrobble(req scrobbler.ScrobbleRequest) error {// Track the playreturn nil}func (s *myScrobbler) NowPlaying(req scrobbler.NowPlayingRequest) error {// Update now playing statusreturn nil}```### WebSocketHandles incoming WebSocket messages.```goimport "github.com/navidrome/navidrome/plugins/pdk/go/websocket"func init() {websocket.Register(&myHandler{})}type myHandler struct{}func (h *myHandler) OnWebSocketMessage(req websocket.WebSocketMessageRequest) error {// Handle incoming messagereturn nil}```## Building PluginsGo plugins must be compiled to WebAssembly using TinyGo:```bashtinygo build -o plugin.wasm -target=wasip1 -buildmode=c-shared .```Or use the provided Makefile targets in plugin examples:```bashmake plugin.wasm```## Testing PluginsThe PDK includes [testify/mock](https://github.com/stretchr/testify) implementations for all host services,allowing you to unit test your plugin code on non-WASM platforms (your development machine).### PDK Abstraction LayerThe `pdk` subpackage provides a testable wrapper around the Extism PDK functions. Instead of importing`github.com/extism/go-pdk` directly, import the abstraction layer:```goimport "github.com/navidrome/navidrome/plugins/pdk/go/pdk"func myFunction() {// Use pdk functions - same API as extism/go-pdkconfig, ok := pdk.GetConfig("my_setting")if ok {pdk.Log(pdk.LogInfo, "Setting: " + config)}var input MyInputif err := pdk.InputJSON(&input); err != nil {pdk.SetError(err)return}output := processInput(input)pdk.OutputJSON(output)}```For WASM builds, these functions delegate directly to `extism/go-pdk` with zero overhead.For native builds (tests), they use mocks that you can configure:```gopackage mypluginimport ("testing""github.com/navidrome/navidrome/plugins/pdk/go/pdk")func TestMyFunction(t *testing.T) {// Reset mock state before each testpdk.ResetMock()// Set up expectationspdk.PDKMock.On("GetConfig", "my_setting").Return("test_value", true)pdk.PDKMock.On("Log", pdk.LogInfo, "Setting: test_value").Return()pdk.PDKMock.On("InputJSON", mock.Anything).Return(nil).Run(func(args mock.Arguments) {// Populate the input structinput := args.Get(0).(*MyInput)input.Name = "test"})pdk.PDKMock.On("OutputJSON", mock.Anything).Return(nil)// Call your functionmyFunction()// Verify expectationspdk.PDKMock.AssertExpectations(t)}```### Mock InstancesEach host service has an auto-instantiated mock instance:| Service | Mock Instance ||---------------|--------------------------|| `Artwork` | `host.ArtworkMock` || `Cache` | `host.CacheMock` || `Config` | `host.ConfigMock` || `KVStore` | `host.KVStoreMock` || `Library` | `host.LibraryMock` || `Scheduler` | `host.SchedulerMock` || `SubsonicAPI` | `host.SubsonicAPIMock` || `WebSocket` | `host.WebSocketMock` |### Example Test```gopackage mypluginimport ("testing""github.com/navidrome/navidrome/plugins/pdk/go/host")func TestMyPluginFunction(t *testing.T) {// Set expectations on the mockhost.CacheMock.On("GetString", "my-key").Return("cached-value", true, nil)host.CacheMock.On("SetString", "new-key", "new-value", int64(3600)).Return(nil)// Call your plugin code that uses host.CacheGetString / host.CacheSetStringresult, err := myPluginFunction()if err != nil {t.Fatalf("unexpected error: %v", err)}// Assert the resultif result != "expected" {t.Errorf("unexpected result: %s", result)}// Verify all expected calls were madehost.CacheMock.AssertExpectations(t)}```### Running TestsSince tests run on your development machine (not WASM), use standard Go testing:```bashgo test ./...```The stub files with mocks are only compiled for non-WASM builds (`//go:build !wasip1`),so they won't affect your production WASM binary.### Complete ExamplesFor more comprehensive examples including HTTP requests, Memory handling, and various testing patterns,see [pdk/example_test.go](pdk/example_test.go).