mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
feat: implement Artwork service for generating artwork URLs in Navidrome plugins - WIP
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
e60efde4d4
commit
a78bbca741
53
plugins/host/artwork.go
Normal file
53
plugins/host/artwork.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package host
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// ArtworkService provides artwork URL generation capabilities for plugins.
|
||||||
|
//
|
||||||
|
// This service allows plugins to generate public URLs for artwork images of
|
||||||
|
// various entity types (artists, albums, tracks, playlists). The generated URLs
|
||||||
|
// include authentication tokens and can be used to display artwork in external
|
||||||
|
// services or custom UIs.
|
||||||
|
//
|
||||||
|
//nd:hostservice name=Artwork permission=artwork
|
||||||
|
type ArtworkService interface {
|
||||||
|
// GetArtistUrl generates a public URL for an artist's artwork.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - id: The artist's unique identifier
|
||||||
|
// - size: Desired image size in pixels (0 for original size)
|
||||||
|
//
|
||||||
|
// Returns the public URL for the artwork, or an error if generation fails.
|
||||||
|
//nd:hostfunc
|
||||||
|
GetArtistUrl(ctx context.Context, id string, size int32) (url string, err error)
|
||||||
|
|
||||||
|
// GetAlbumUrl generates a public URL for an album's artwork.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - id: The album's unique identifier
|
||||||
|
// - size: Desired image size in pixels (0 for original size)
|
||||||
|
//
|
||||||
|
// Returns the public URL for the artwork, or an error if generation fails.
|
||||||
|
//nd:hostfunc
|
||||||
|
GetAlbumUrl(ctx context.Context, id string, size int32) (url string, err error)
|
||||||
|
|
||||||
|
// GetTrackUrl generates a public URL for a track's artwork.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - id: The track's (media file) unique identifier
|
||||||
|
// - size: Desired image size in pixels (0 for original size)
|
||||||
|
//
|
||||||
|
// Returns the public URL for the artwork, or an error if generation fails.
|
||||||
|
//nd:hostfunc
|
||||||
|
GetTrackUrl(ctx context.Context, id string, size int32) (url string, err error)
|
||||||
|
|
||||||
|
// GetPlaylistUrl generates a public URL for a playlist's artwork.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - id: The playlist's unique identifier
|
||||||
|
// - size: Desired image size in pixels (0 for original size)
|
||||||
|
//
|
||||||
|
// Returns the public URL for the artwork, or an error if generation fails.
|
||||||
|
//nd:hostfunc
|
||||||
|
GetPlaylistUrl(ctx context.Context, id string, size int32) (url string, err error)
|
||||||
|
}
|
||||||
182
plugins/host/artwork_gen.go
Normal file
182
plugins/host/artwork_gen.go
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
// Code generated by hostgen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package host
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
extism "github.com/extism/go-sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ArtworkGetArtistUrlResponse is the response type for Artwork.GetArtistUrl.
|
||||||
|
type ArtworkGetArtistUrlResponse struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetAlbumUrlResponse is the response type for Artwork.GetAlbumUrl.
|
||||||
|
type ArtworkGetAlbumUrlResponse struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetTrackUrlResponse is the response type for Artwork.GetTrackUrl.
|
||||||
|
type ArtworkGetTrackUrlResponse struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetPlaylistUrlResponse is the response type for Artwork.GetPlaylistUrl.
|
||||||
|
type ArtworkGetPlaylistUrlResponse struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterArtworkHostFunctions registers Artwork service host functions.
|
||||||
|
// The returned host functions should be added to the plugin's configuration.
|
||||||
|
func RegisterArtworkHostFunctions(service ArtworkService) []extism.HostFunction {
|
||||||
|
return []extism.HostFunction{
|
||||||
|
newArtworkGetArtistUrlHostFunction(service),
|
||||||
|
newArtworkGetAlbumUrlHostFunction(service),
|
||||||
|
newArtworkGetTrackUrlHostFunction(service),
|
||||||
|
newArtworkGetPlaylistUrlHostFunction(service),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newArtworkGetArtistUrlHostFunction(service ArtworkService) extism.HostFunction {
|
||||||
|
return extism.NewHostFunctionWithStack(
|
||||||
|
"artwork_getartisturl",
|
||||||
|
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
|
||||||
|
// Read parameters from stack
|
||||||
|
id, err := p.ReadString(stack[0])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
size := extism.DecodeI32(stack[1])
|
||||||
|
|
||||||
|
// Call the service method
|
||||||
|
url, err := service.GetArtistUrl(ctx, id, size)
|
||||||
|
if err != nil {
|
||||||
|
artworkWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Write JSON response to plugin memory
|
||||||
|
resp := ArtworkGetArtistUrlResponse{
|
||||||
|
Url: url,
|
||||||
|
}
|
||||||
|
artworkWriteResponse(p, stack, resp)
|
||||||
|
},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR, extism.ValueTypeI32},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newArtworkGetAlbumUrlHostFunction(service ArtworkService) extism.HostFunction {
|
||||||
|
return extism.NewHostFunctionWithStack(
|
||||||
|
"artwork_getalbumurl",
|
||||||
|
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
|
||||||
|
// Read parameters from stack
|
||||||
|
id, err := p.ReadString(stack[0])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
size := extism.DecodeI32(stack[1])
|
||||||
|
|
||||||
|
// Call the service method
|
||||||
|
url, err := service.GetAlbumUrl(ctx, id, size)
|
||||||
|
if err != nil {
|
||||||
|
artworkWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Write JSON response to plugin memory
|
||||||
|
resp := ArtworkGetAlbumUrlResponse{
|
||||||
|
Url: url,
|
||||||
|
}
|
||||||
|
artworkWriteResponse(p, stack, resp)
|
||||||
|
},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR, extism.ValueTypeI32},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newArtworkGetTrackUrlHostFunction(service ArtworkService) extism.HostFunction {
|
||||||
|
return extism.NewHostFunctionWithStack(
|
||||||
|
"artwork_gettrackurl",
|
||||||
|
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
|
||||||
|
// Read parameters from stack
|
||||||
|
id, err := p.ReadString(stack[0])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
size := extism.DecodeI32(stack[1])
|
||||||
|
|
||||||
|
// Call the service method
|
||||||
|
url, err := service.GetTrackUrl(ctx, id, size)
|
||||||
|
if err != nil {
|
||||||
|
artworkWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Write JSON response to plugin memory
|
||||||
|
resp := ArtworkGetTrackUrlResponse{
|
||||||
|
Url: url,
|
||||||
|
}
|
||||||
|
artworkWriteResponse(p, stack, resp)
|
||||||
|
},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR, extism.ValueTypeI32},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newArtworkGetPlaylistUrlHostFunction(service ArtworkService) extism.HostFunction {
|
||||||
|
return extism.NewHostFunctionWithStack(
|
||||||
|
"artwork_getplaylisturl",
|
||||||
|
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
|
||||||
|
// Read parameters from stack
|
||||||
|
id, err := p.ReadString(stack[0])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
size := extism.DecodeI32(stack[1])
|
||||||
|
|
||||||
|
// Call the service method
|
||||||
|
url, err := service.GetPlaylistUrl(ctx, id, size)
|
||||||
|
if err != nil {
|
||||||
|
artworkWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Write JSON response to plugin memory
|
||||||
|
resp := ArtworkGetPlaylistUrlResponse{
|
||||||
|
Url: url,
|
||||||
|
}
|
||||||
|
artworkWriteResponse(p, stack, resp)
|
||||||
|
},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR, extism.ValueTypeI32},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// artworkWriteResponse writes a JSON response to plugin memory.
|
||||||
|
func artworkWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) {
|
||||||
|
respBytes, err := json.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
artworkWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respPtr, err := p.WriteBytes(respBytes)
|
||||||
|
if err != nil {
|
||||||
|
stack[0] = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stack[0] = respPtr
|
||||||
|
}
|
||||||
|
|
||||||
|
// artworkWriteError writes an error response to plugin memory.
|
||||||
|
func artworkWriteError(p *extism.CurrentPlugin, stack []uint64, err error) {
|
||||||
|
errResp := struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}{Error: err.Error()}
|
||||||
|
respBytes, _ := json.Marshal(errResp)
|
||||||
|
respPtr, _ := p.WriteBytes(respBytes)
|
||||||
|
stack[0] = respPtr
|
||||||
|
}
|
||||||
170
plugins/host/go/nd_host_artwork.go
Normal file
170
plugins/host/go/nd_host_artwork.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
// Code generated by hostgen. DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// This file contains client wrappers for the Artwork host service.
|
||||||
|
// It is intended for use in Navidrome plugins built with TinyGo.
|
||||||
|
//
|
||||||
|
//go:build wasip1
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/extism/go-pdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// artwork_getartisturl is the host function provided by Navidrome.
|
||||||
|
//
|
||||||
|
//go:wasmimport extism:host/user artwork_getartisturl
|
||||||
|
func artwork_getartisturl(uint64, int32) uint64
|
||||||
|
|
||||||
|
// artwork_getalbumurl is the host function provided by Navidrome.
|
||||||
|
//
|
||||||
|
//go:wasmimport extism:host/user artwork_getalbumurl
|
||||||
|
func artwork_getalbumurl(uint64, int32) uint64
|
||||||
|
|
||||||
|
// artwork_gettrackurl is the host function provided by Navidrome.
|
||||||
|
//
|
||||||
|
//go:wasmimport extism:host/user artwork_gettrackurl
|
||||||
|
func artwork_gettrackurl(uint64, int32) uint64
|
||||||
|
|
||||||
|
// artwork_getplaylisturl is the host function provided by Navidrome.
|
||||||
|
//
|
||||||
|
//go:wasmimport extism:host/user artwork_getplaylisturl
|
||||||
|
func artwork_getplaylisturl(uint64, int32) uint64
|
||||||
|
|
||||||
|
// ArtworkGetArtistUrlResponse is the response type for Artwork.GetArtistUrl.
|
||||||
|
type ArtworkGetArtistUrlResponse struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetAlbumUrlResponse is the response type for Artwork.GetAlbumUrl.
|
||||||
|
type ArtworkGetAlbumUrlResponse struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetTrackUrlResponse is the response type for Artwork.GetTrackUrl.
|
||||||
|
type ArtworkGetTrackUrlResponse struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetPlaylistUrlResponse is the response type for Artwork.GetPlaylistUrl.
|
||||||
|
type ArtworkGetPlaylistUrlResponse struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetArtistUrl calls the artwork_getartisturl host function.
|
||||||
|
// GetArtistUrl generates a public URL for an artist's artwork.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - id: The artist's unique identifier
|
||||||
|
// - size: Desired image size in pixels (0 for original size)
|
||||||
|
//
|
||||||
|
// Returns the public URL for the artwork, or an error if generation fails.
|
||||||
|
func ArtworkGetArtistUrl(id string, size int32) (*ArtworkGetArtistUrlResponse, error) {
|
||||||
|
idMem := pdk.AllocateString(id)
|
||||||
|
defer idMem.Free()
|
||||||
|
|
||||||
|
// Call the host function
|
||||||
|
responsePtr := artwork_getartisturl(idMem.Offset(), size)
|
||||||
|
|
||||||
|
// Read the response from memory
|
||||||
|
responseMem := pdk.FindMemory(responsePtr)
|
||||||
|
responseBytes := responseMem.ReadBytes()
|
||||||
|
|
||||||
|
// Parse the response
|
||||||
|
var response ArtworkGetArtistUrlResponse
|
||||||
|
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetAlbumUrl calls the artwork_getalbumurl host function.
|
||||||
|
// GetAlbumUrl generates a public URL for an album's artwork.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - id: The album's unique identifier
|
||||||
|
// - size: Desired image size in pixels (0 for original size)
|
||||||
|
//
|
||||||
|
// Returns the public URL for the artwork, or an error if generation fails.
|
||||||
|
func ArtworkGetAlbumUrl(id string, size int32) (*ArtworkGetAlbumUrlResponse, error) {
|
||||||
|
idMem := pdk.AllocateString(id)
|
||||||
|
defer idMem.Free()
|
||||||
|
|
||||||
|
// Call the host function
|
||||||
|
responsePtr := artwork_getalbumurl(idMem.Offset(), size)
|
||||||
|
|
||||||
|
// Read the response from memory
|
||||||
|
responseMem := pdk.FindMemory(responsePtr)
|
||||||
|
responseBytes := responseMem.ReadBytes()
|
||||||
|
|
||||||
|
// Parse the response
|
||||||
|
var response ArtworkGetAlbumUrlResponse
|
||||||
|
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetTrackUrl calls the artwork_gettrackurl host function.
|
||||||
|
// GetTrackUrl generates a public URL for a track's artwork.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - id: The track's (media file) unique identifier
|
||||||
|
// - size: Desired image size in pixels (0 for original size)
|
||||||
|
//
|
||||||
|
// Returns the public URL for the artwork, or an error if generation fails.
|
||||||
|
func ArtworkGetTrackUrl(id string, size int32) (*ArtworkGetTrackUrlResponse, error) {
|
||||||
|
idMem := pdk.AllocateString(id)
|
||||||
|
defer idMem.Free()
|
||||||
|
|
||||||
|
// Call the host function
|
||||||
|
responsePtr := artwork_gettrackurl(idMem.Offset(), size)
|
||||||
|
|
||||||
|
// Read the response from memory
|
||||||
|
responseMem := pdk.FindMemory(responsePtr)
|
||||||
|
responseBytes := responseMem.ReadBytes()
|
||||||
|
|
||||||
|
// Parse the response
|
||||||
|
var response ArtworkGetTrackUrlResponse
|
||||||
|
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetPlaylistUrl calls the artwork_getplaylisturl host function.
|
||||||
|
// GetPlaylistUrl generates a public URL for a playlist's artwork.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - id: The playlist's unique identifier
|
||||||
|
// - size: Desired image size in pixels (0 for original size)
|
||||||
|
//
|
||||||
|
// Returns the public URL for the artwork, or an error if generation fails.
|
||||||
|
func ArtworkGetPlaylistUrl(id string, size int32) (*ArtworkGetPlaylistUrlResponse, error) {
|
||||||
|
idMem := pdk.AllocateString(id)
|
||||||
|
defer idMem.Free()
|
||||||
|
|
||||||
|
// Call the host function
|
||||||
|
responsePtr := artwork_getplaylisturl(idMem.Offset(), size)
|
||||||
|
|
||||||
|
// Read the response from memory
|
||||||
|
responseMem := pdk.FindMemory(responsePtr)
|
||||||
|
responseBytes := responseMem.ReadBytes()
|
||||||
|
|
||||||
|
// Parse the response
|
||||||
|
var response ArtworkGetPlaylistUrlResponse
|
||||||
|
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
92
plugins/host_artwork.go
Normal file
92
plugins/host_artwork.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/conf"
|
||||||
|
"github.com/navidrome/navidrome/consts"
|
||||||
|
"github.com/navidrome/navidrome/core/auth"
|
||||||
|
"github.com/navidrome/navidrome/model"
|
||||||
|
"github.com/navidrome/navidrome/plugins/host"
|
||||||
|
)
|
||||||
|
|
||||||
|
type artworkServiceImpl struct{}
|
||||||
|
|
||||||
|
func newArtworkService() host.ArtworkService {
|
||||||
|
return &artworkServiceImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *artworkServiceImpl) GetArtistUrl(_ context.Context, id string, size int32) (string, error) {
|
||||||
|
artID := model.ArtworkID{Kind: model.KindArtistArtwork, ID: id}
|
||||||
|
return a.imageURL(artID, int(size)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *artworkServiceImpl) GetAlbumUrl(_ context.Context, id string, size int32) (string, error) {
|
||||||
|
artID := model.ArtworkID{Kind: model.KindAlbumArtwork, ID: id}
|
||||||
|
return a.imageURL(artID, int(size)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *artworkServiceImpl) GetTrackUrl(_ context.Context, id string, size int32) (string, error) {
|
||||||
|
artID := model.ArtworkID{Kind: model.KindMediaFileArtwork, ID: id}
|
||||||
|
return a.imageURL(artID, int(size)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *artworkServiceImpl) GetPlaylistUrl(_ context.Context, id string, size int32) (string, error) {
|
||||||
|
artID := model.ArtworkID{Kind: model.KindPlaylistArtwork, ID: id}
|
||||||
|
return a.imageURL(artID, int(size)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageURL generates a public URL for artwork, replicating the logic from server/public
|
||||||
|
// to avoid import cycles.
|
||||||
|
func (a *artworkServiceImpl) imageURL(artID model.ArtworkID, size int) string {
|
||||||
|
token, _ := auth.CreatePublicToken(map[string]any{"id": artID.String()})
|
||||||
|
uri := path.Join(consts.URLPathPublicImages, token)
|
||||||
|
params := url.Values{}
|
||||||
|
if size > 0 {
|
||||||
|
params.Add("size", strconv.Itoa(size))
|
||||||
|
}
|
||||||
|
return a.publicURL(uri, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// publicURL builds the full URL using ShareURL config or falling back to localhost.
|
||||||
|
func (a *artworkServiceImpl) publicURL(u string, params url.Values) string {
|
||||||
|
var scheme, host string
|
||||||
|
if conf.Server.ShareURL != "" {
|
||||||
|
shareURL, _ := url.Parse(conf.Server.ShareURL)
|
||||||
|
scheme = shareURL.Scheme
|
||||||
|
host = shareURL.Host
|
||||||
|
} else {
|
||||||
|
scheme = "http"
|
||||||
|
host = "localhost"
|
||||||
|
}
|
||||||
|
buildURL, _ := url.Parse(u)
|
||||||
|
buildURL.Scheme = scheme
|
||||||
|
buildURL.Host = host
|
||||||
|
if len(params) > 0 {
|
||||||
|
buildURL.RawQuery = params.Encode()
|
||||||
|
}
|
||||||
|
return buildURL.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// createRequest creates a dummy HTTP request for URL generation.
|
||||||
|
// Kept for reference but no longer used after refactoring.
|
||||||
|
func (a *artworkServiceImpl) createRequest() *http.Request {
|
||||||
|
var scheme, host string
|
||||||
|
if conf.Server.ShareURL != "" {
|
||||||
|
shareURL, _ := url.Parse(conf.Server.ShareURL)
|
||||||
|
scheme = shareURL.Scheme
|
||||||
|
host = shareURL.Host
|
||||||
|
} else {
|
||||||
|
scheme = "http"
|
||||||
|
host = "localhost"
|
||||||
|
}
|
||||||
|
r, _ := http.NewRequest("GET", fmt.Sprintf("%s://%s", scheme, host), nil)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ host.ArtworkService = (*artworkServiceImpl)(nil)
|
||||||
219
plugins/host_artwork_test.go
Normal file
219
plugins/host_artwork_test.go
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/conf"
|
||||||
|
"github.com/navidrome/navidrome/conf/configtest"
|
||||||
|
"github.com/navidrome/navidrome/core/auth"
|
||||||
|
"github.com/navidrome/navidrome/model"
|
||||||
|
"github.com/navidrome/navidrome/tests"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("ArtworkService", Ordered, func() {
|
||||||
|
var (
|
||||||
|
manager *Manager
|
||||||
|
tmpDir string
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeAll(func() {
|
||||||
|
var err error
|
||||||
|
tmpDir, err = os.MkdirTemp("", "artwork-test-*")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Copy the fake-artwork plugin
|
||||||
|
srcPath := filepath.Join(testdataDir, "fake-artwork.wasm")
|
||||||
|
destPath := filepath.Join(tmpDir, "fake-artwork.wasm")
|
||||||
|
data, err := os.ReadFile(srcPath)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
err = os.WriteFile(destPath, data, 0600)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Setup config
|
||||||
|
DeferCleanup(configtest.SetupConfig())
|
||||||
|
conf.Server.Plugins.Enabled = true
|
||||||
|
conf.Server.Plugins.Folder = tmpDir
|
||||||
|
conf.Server.Plugins.AutoReload = false
|
||||||
|
conf.Server.CacheFolder = filepath.Join(tmpDir, "cache")
|
||||||
|
|
||||||
|
// Initialize auth (required for token generation)
|
||||||
|
ds := &tests.MockDataStore{MockedProperty: &tests.MockedPropertyRepo{}}
|
||||||
|
auth.Init(ds)
|
||||||
|
|
||||||
|
// Create and start manager
|
||||||
|
manager = &Manager{
|
||||||
|
plugins: make(map[string]*plugin),
|
||||||
|
}
|
||||||
|
err = manager.Start(GinkgoT().Context())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
DeferCleanup(func() {
|
||||||
|
_ = manager.Stop()
|
||||||
|
_ = os.RemoveAll(tmpDir)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("Plugin Loading", func() {
|
||||||
|
It("should load plugin with artwork permission", func() {
|
||||||
|
manager.mu.RLock()
|
||||||
|
p, ok := manager.plugins["fake-artwork"]
|
||||||
|
manager.mu.RUnlock()
|
||||||
|
Expect(ok).To(BeTrue())
|
||||||
|
Expect(p.manifest.Permissions).ToNot(BeNil())
|
||||||
|
Expect(p.manifest.Permissions.Artwork).ToNot(BeNil())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("Artwork URL Generation", func() {
|
||||||
|
type testArtworkInput struct {
|
||||||
|
ArtworkType string `json:"artwork_type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Size int32 `json:"size"`
|
||||||
|
}
|
||||||
|
type testArtworkOutput struct {
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
Error *string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
callTestArtwork := func(ctx context.Context, artworkType, id string, size int32) (string, error) {
|
||||||
|
manager.mu.RLock()
|
||||||
|
p := manager.plugins["fake-artwork"]
|
||||||
|
manager.mu.RUnlock()
|
||||||
|
|
||||||
|
instance, err := p.instance()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer instance.Close(ctx)
|
||||||
|
|
||||||
|
input := testArtworkInput{
|
||||||
|
ArtworkType: artworkType,
|
||||||
|
ID: id,
|
||||||
|
Size: size,
|
||||||
|
}
|
||||||
|
inputBytes, _ := json.Marshal(input)
|
||||||
|
_, outputBytes, err := instance.Call("nd_test_artwork", inputBytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var output testArtworkOutput
|
||||||
|
if err := json.Unmarshal(outputBytes, &output); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if output.Error != nil {
|
||||||
|
return "", Errorf(*output.Error)
|
||||||
|
}
|
||||||
|
return output.URL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
It("should generate artist artwork URL", func() {
|
||||||
|
url, err := callTestArtwork(GinkgoT().Context(), "artist", "ar-123", 0)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(url).To(ContainSubstring("/img/"))
|
||||||
|
Expect(url).ToNot(ContainSubstring("size="))
|
||||||
|
|
||||||
|
// Decode JWT and verify artwork ID
|
||||||
|
artID := decodeArtworkURL(url)
|
||||||
|
Expect(artID.Kind).To(Equal(model.KindArtistArtwork))
|
||||||
|
Expect(artID.ID).To(Equal("ar-123"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should generate album artwork URL", func() {
|
||||||
|
url, err := callTestArtwork(GinkgoT().Context(), "album", "al-456", 0)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(url).To(ContainSubstring("/img/"))
|
||||||
|
|
||||||
|
artID := decodeArtworkURL(url)
|
||||||
|
Expect(artID.Kind).To(Equal(model.KindAlbumArtwork))
|
||||||
|
Expect(artID.ID).To(Equal("al-456"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should generate track artwork URL", func() {
|
||||||
|
url, err := callTestArtwork(GinkgoT().Context(), "track", "mf-789", 0)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(url).To(ContainSubstring("/img/"))
|
||||||
|
|
||||||
|
artID := decodeArtworkURL(url)
|
||||||
|
Expect(artID.Kind).To(Equal(model.KindMediaFileArtwork))
|
||||||
|
Expect(artID.ID).To(Equal("mf-789"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should generate playlist artwork URL", func() {
|
||||||
|
url, err := callTestArtwork(GinkgoT().Context(), "playlist", "pl-abc", 0)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(url).To(ContainSubstring("/img/"))
|
||||||
|
|
||||||
|
artID := decodeArtworkURL(url)
|
||||||
|
Expect(artID.Kind).To(Equal(model.KindPlaylistArtwork))
|
||||||
|
Expect(artID.ID).To(Equal("pl-abc"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should include size parameter when specified", func() {
|
||||||
|
url, err := callTestArtwork(GinkgoT().Context(), "album", "al-456", 300)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(url).To(ContainSubstring("size=300"))
|
||||||
|
|
||||||
|
artID := decodeArtworkURL(url)
|
||||||
|
Expect(artID.Kind).To(Equal(model.KindAlbumArtwork))
|
||||||
|
Expect(artID.ID).To(Equal("al-456"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should handle unknown artwork type", func() {
|
||||||
|
_, err := callTestArtwork(GinkgoT().Context(), "unknown", "id-123", 0)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring("unknown artwork type"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Errorf creates an error from a format string (helper for tests)
|
||||||
|
func Errorf(format string, args ...any) error {
|
||||||
|
return &errorString{s: format}
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorString struct {
|
||||||
|
s string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errorString) Error() string {
|
||||||
|
return e.s
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeArtworkURL extracts and decodes the JWT token from an artwork URL,
|
||||||
|
// returning the parsed ArtworkID. Panics on error (test helper).
|
||||||
|
func decodeArtworkURL(artworkURL string) model.ArtworkID {
|
||||||
|
// URL format: http://localhost/img/<token>?size=...
|
||||||
|
// Extract token from path after /img/
|
||||||
|
idx := strings.Index(artworkURL, "/img/")
|
||||||
|
Expect(idx).To(BeNumerically(">=", 0), "URL should contain /img/")
|
||||||
|
|
||||||
|
tokenPart := artworkURL[idx+5:] // skip "/img/"
|
||||||
|
// Remove query string if present
|
||||||
|
if qIdx := strings.Index(tokenPart, "?"); qIdx >= 0 {
|
||||||
|
tokenPart = tokenPart[:qIdx]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode JWT token
|
||||||
|
token, err := auth.TokenAuth.Decode(tokenPart)
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "Failed to decode JWT token")
|
||||||
|
|
||||||
|
claims, err := token.AsMap(context.Background())
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "Failed to get claims from token")
|
||||||
|
|
||||||
|
id, ok := claims["id"].(string)
|
||||||
|
Expect(ok).To(BeTrue(), "Token should contain 'id' claim")
|
||||||
|
|
||||||
|
artID, err := model.ParseArtworkID(id)
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "Failed to parse artwork ID from token")
|
||||||
|
|
||||||
|
return artID
|
||||||
|
}
|
||||||
@ -362,6 +362,7 @@ func (m *Manager) loadPlugin(name, wasmPath string) error {
|
|||||||
stubHostFunctions := host.RegisterSubsonicAPIHostFunctions(nil)
|
stubHostFunctions := host.RegisterSubsonicAPIHostFunctions(nil)
|
||||||
stubHostFunctions = append(stubHostFunctions, host.RegisterSchedulerHostFunctions(nil)...)
|
stubHostFunctions = append(stubHostFunctions, host.RegisterSchedulerHostFunctions(nil)...)
|
||||||
stubHostFunctions = append(stubHostFunctions, host.RegisterWebSocketHostFunctions(nil)...)
|
stubHostFunctions = append(stubHostFunctions, host.RegisterWebSocketHostFunctions(nil)...)
|
||||||
|
stubHostFunctions = append(stubHostFunctions, host.RegisterArtworkHostFunctions(nil)...)
|
||||||
|
|
||||||
// Create initial compiled plugin with stub host functions
|
// Create initial compiled plugin with stub host functions
|
||||||
compiled, err := extism.NewCompiledPlugin(m.ctx, pluginManifest, extismConfig, stubHostFunctions)
|
compiled, err := extism.NewCompiledPlugin(m.ctx, pluginManifest, extismConfig, stubHostFunctions)
|
||||||
@ -433,6 +434,12 @@ func (m *Manager) loadPlugin(name, wasmPath string) error {
|
|||||||
hostFunctions = append(hostFunctions, host.RegisterWebSocketHostFunctions(service)...)
|
hostFunctions = append(hostFunctions, host.RegisterWebSocketHostFunctions(service)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register Artwork host functions if permission is granted
|
||||||
|
if manifest.Permissions != nil && manifest.Permissions.Artwork != nil {
|
||||||
|
service := newArtworkService()
|
||||||
|
hostFunctions = append(hostFunctions, host.RegisterArtworkHostFunctions(service)...)
|
||||||
|
}
|
||||||
|
|
||||||
// Check if recompilation is needed (AllowedHosts or host functions)
|
// Check if recompilation is needed (AllowedHosts or host functions)
|
||||||
needsRecompile := len(pluginManifest.AllowedHosts) > 0 || len(hostFunctions) > 0
|
needsRecompile := len(pluginManifest.AllowedHosts) > 0 || len(hostFunctions) > 0
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,20 @@
|
|||||||
},
|
},
|
||||||
"websocket": {
|
"websocket": {
|
||||||
"$ref": "#/$defs/WebSocketPermission"
|
"$ref": "#/$defs/WebSocketPermission"
|
||||||
|
},
|
||||||
|
"artwork": {
|
||||||
|
"$ref": "#/$defs/ArtworkPermission"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ArtworkPermission": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Artwork service permissions for generating artwork URLs",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"reason": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Explanation for why artwork access is needed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,6 +5,12 @@ package plugins
|
|||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
|
// Artwork service permissions for generating artwork URLs
|
||||||
|
type ArtworkPermission struct {
|
||||||
|
// Explanation for why artwork access is needed
|
||||||
|
Reason *string `json:"reason,omitempty" yaml:"reason,omitempty" mapstructure:"reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Configuration access permissions for a plugin
|
// Configuration access permissions for a plugin
|
||||||
type ConfigPermission struct {
|
type ConfigPermission struct {
|
||||||
// Explanation for why config access is needed
|
// Explanation for why config access is needed
|
||||||
@ -77,6 +83,9 @@ func (j *Manifest) UnmarshalJSON(value []byte) error {
|
|||||||
|
|
||||||
// Permissions required by the plugin
|
// Permissions required by the plugin
|
||||||
type Permissions struct {
|
type Permissions struct {
|
||||||
|
// Artwork corresponds to the JSON schema field "artwork".
|
||||||
|
Artwork *ArtworkPermission `json:"artwork,omitempty" yaml:"artwork,omitempty" mapstructure:"artwork,omitempty"`
|
||||||
|
|
||||||
// Http corresponds to the JSON schema field "http".
|
// Http corresponds to the JSON schema field "http".
|
||||||
Http *HTTPPermission `json:"http,omitempty" yaml:"http,omitempty" mapstructure:"http,omitempty"`
|
Http *HTTPPermission `json:"http,omitempty" yaml:"http,omitempty" mapstructure:"http,omitempty"`
|
||||||
|
|
||||||
|
|||||||
5
plugins/testdata/fake-artwork/go.mod
vendored
Normal file
5
plugins/testdata/fake-artwork/go.mod
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module fake-artwork
|
||||||
|
|
||||||
|
go 1.23
|
||||||
|
|
||||||
|
require github.com/extism/go-pdk v1.1.3
|
||||||
2
plugins/testdata/fake-artwork/go.sum
vendored
Normal file
2
plugins/testdata/fake-artwork/go.sum
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github.com/extism/go-pdk v1.1.3 h1:hfViMPWrqjN6u67cIYRALZTZLk/enSPpNKa+rZ9X2SQ=
|
||||||
|
github.com/extism/go-pdk v1.1.3/go.mod h1:Gz+LIU/YCKnKXhgge8yo5Yu1F/lbv7KtKFkiCSzW/P4=
|
||||||
139
plugins/testdata/fake-artwork/main.go
vendored
Normal file
139
plugins/testdata/fake-artwork/main.go
vendored
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// Fake Artwork plugin for Navidrome plugin system integration tests.
|
||||||
|
// Build with: tinygo build -o ../fake-artwork.wasm -target wasip1 -buildmode=c-shared .
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
pdk "github.com/extism/go-pdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manifest types
|
||||||
|
type Manifest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Permissions *Permissions `json:"permissions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Permissions struct {
|
||||||
|
Artwork *ArtworkPermission `json:"artwork,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArtworkPermission struct {
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:wasmexport nd_manifest
|
||||||
|
func ndManifest() int32 {
|
||||||
|
manifest := Manifest{
|
||||||
|
Name: "Fake Artwork",
|
||||||
|
Author: "Navidrome Test",
|
||||||
|
Version: "1.0.0",
|
||||||
|
Description: "A fake artwork plugin for integration testing",
|
||||||
|
Permissions: &Permissions{
|
||||||
|
Artwork: &ArtworkPermission{
|
||||||
|
Reason: "For testing artwork URL generation",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
out, err := json.Marshal(manifest)
|
||||||
|
if err != nil {
|
||||||
|
pdk.SetError(err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
pdk.Output(out)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInput is the input for nd_test_artwork callback.
|
||||||
|
type TestInput struct {
|
||||||
|
ArtworkType string `json:"artwork_type"` // "artist", "album", "track", "playlist"
|
||||||
|
ID string `json:"id"`
|
||||||
|
Size int32 `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOutput is the output from nd_test_artwork callback.
|
||||||
|
type TestOutput struct {
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
Error *string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// nd_test_artwork is the test callback that tests the artwork host functions.
|
||||||
|
//
|
||||||
|
//go:wasmexport nd_test_artwork
|
||||||
|
func ndTestArtwork() int32 {
|
||||||
|
var input TestInput
|
||||||
|
if err := pdk.InputJSON(&input); err != nil {
|
||||||
|
errStr := err.Error()
|
||||||
|
pdk.OutputJSON(TestOutput{Error: &errStr})
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var url string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch strings.ToLower(input.ArtworkType) {
|
||||||
|
case "artist":
|
||||||
|
resp, e := ArtworkGetArtistUrl(input.ID, input.Size)
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
} else if resp.Error != "" {
|
||||||
|
errStr := resp.Error
|
||||||
|
pdk.OutputJSON(TestOutput{Error: &errStr})
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
url = resp.Url
|
||||||
|
}
|
||||||
|
case "album":
|
||||||
|
resp, e := ArtworkGetAlbumUrl(input.ID, input.Size)
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
} else if resp.Error != "" {
|
||||||
|
errStr := resp.Error
|
||||||
|
pdk.OutputJSON(TestOutput{Error: &errStr})
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
url = resp.Url
|
||||||
|
}
|
||||||
|
case "track":
|
||||||
|
resp, e := ArtworkGetTrackUrl(input.ID, input.Size)
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
} else if resp.Error != "" {
|
||||||
|
errStr := resp.Error
|
||||||
|
pdk.OutputJSON(TestOutput{Error: &errStr})
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
url = resp.Url
|
||||||
|
}
|
||||||
|
case "playlist":
|
||||||
|
resp, e := ArtworkGetPlaylistUrl(input.ID, input.Size)
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
} else if resp.Error != "" {
|
||||||
|
errStr := resp.Error
|
||||||
|
pdk.OutputJSON(TestOutput{Error: &errStr})
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
url = resp.Url
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
errStr := "unknown artwork type: " + input.ArtworkType
|
||||||
|
pdk.OutputJSON(TestOutput{Error: &errStr})
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errStr := err.Error()
|
||||||
|
pdk.OutputJSON(TestOutput{Error: &errStr})
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pdk.OutputJSON(TestOutput{URL: url})
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {}
|
||||||
170
plugins/testdata/fake-artwork/nd_host_artwork.go
vendored
Normal file
170
plugins/testdata/fake-artwork/nd_host_artwork.go
vendored
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
// Code generated by hostgen. DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// This file contains client wrappers for the Artwork host service.
|
||||||
|
// It is intended for use in Navidrome plugins built with TinyGo.
|
||||||
|
//
|
||||||
|
//go:build wasip1
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/extism/go-pdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// artwork_getartisturl is the host function provided by Navidrome.
|
||||||
|
//
|
||||||
|
//go:wasmimport extism:host/user artwork_getartisturl
|
||||||
|
func artwork_getartisturl(uint64, int32) uint64
|
||||||
|
|
||||||
|
// artwork_getalbumurl is the host function provided by Navidrome.
|
||||||
|
//
|
||||||
|
//go:wasmimport extism:host/user artwork_getalbumurl
|
||||||
|
func artwork_getalbumurl(uint64, int32) uint64
|
||||||
|
|
||||||
|
// artwork_gettrackurl is the host function provided by Navidrome.
|
||||||
|
//
|
||||||
|
//go:wasmimport extism:host/user artwork_gettrackurl
|
||||||
|
func artwork_gettrackurl(uint64, int32) uint64
|
||||||
|
|
||||||
|
// artwork_getplaylisturl is the host function provided by Navidrome.
|
||||||
|
//
|
||||||
|
//go:wasmimport extism:host/user artwork_getplaylisturl
|
||||||
|
func artwork_getplaylisturl(uint64, int32) uint64
|
||||||
|
|
||||||
|
// ArtworkGetArtistUrlResponse is the response type for Artwork.GetArtistUrl.
|
||||||
|
type ArtworkGetArtistUrlResponse struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetAlbumUrlResponse is the response type for Artwork.GetAlbumUrl.
|
||||||
|
type ArtworkGetAlbumUrlResponse struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetTrackUrlResponse is the response type for Artwork.GetTrackUrl.
|
||||||
|
type ArtworkGetTrackUrlResponse struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetPlaylistUrlResponse is the response type for Artwork.GetPlaylistUrl.
|
||||||
|
type ArtworkGetPlaylistUrlResponse struct {
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetArtistUrl calls the artwork_getartisturl host function.
|
||||||
|
// GetArtistUrl generates a public URL for an artist's artwork.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - id: The artist's unique identifier
|
||||||
|
// - size: Desired image size in pixels (0 for original size)
|
||||||
|
//
|
||||||
|
// Returns the public URL for the artwork, or an error if generation fails.
|
||||||
|
func ArtworkGetArtistUrl(id string, size int32) (*ArtworkGetArtistUrlResponse, error) {
|
||||||
|
idMem := pdk.AllocateString(id)
|
||||||
|
defer idMem.Free()
|
||||||
|
|
||||||
|
// Call the host function
|
||||||
|
responsePtr := artwork_getartisturl(idMem.Offset(), size)
|
||||||
|
|
||||||
|
// Read the response from memory
|
||||||
|
responseMem := pdk.FindMemory(responsePtr)
|
||||||
|
responseBytes := responseMem.ReadBytes()
|
||||||
|
|
||||||
|
// Parse the response
|
||||||
|
var response ArtworkGetArtistUrlResponse
|
||||||
|
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetAlbumUrl calls the artwork_getalbumurl host function.
|
||||||
|
// GetAlbumUrl generates a public URL for an album's artwork.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - id: The album's unique identifier
|
||||||
|
// - size: Desired image size in pixels (0 for original size)
|
||||||
|
//
|
||||||
|
// Returns the public URL for the artwork, or an error if generation fails.
|
||||||
|
func ArtworkGetAlbumUrl(id string, size int32) (*ArtworkGetAlbumUrlResponse, error) {
|
||||||
|
idMem := pdk.AllocateString(id)
|
||||||
|
defer idMem.Free()
|
||||||
|
|
||||||
|
// Call the host function
|
||||||
|
responsePtr := artwork_getalbumurl(idMem.Offset(), size)
|
||||||
|
|
||||||
|
// Read the response from memory
|
||||||
|
responseMem := pdk.FindMemory(responsePtr)
|
||||||
|
responseBytes := responseMem.ReadBytes()
|
||||||
|
|
||||||
|
// Parse the response
|
||||||
|
var response ArtworkGetAlbumUrlResponse
|
||||||
|
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetTrackUrl calls the artwork_gettrackurl host function.
|
||||||
|
// GetTrackUrl generates a public URL for a track's artwork.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - id: The track's (media file) unique identifier
|
||||||
|
// - size: Desired image size in pixels (0 for original size)
|
||||||
|
//
|
||||||
|
// Returns the public URL for the artwork, or an error if generation fails.
|
||||||
|
func ArtworkGetTrackUrl(id string, size int32) (*ArtworkGetTrackUrlResponse, error) {
|
||||||
|
idMem := pdk.AllocateString(id)
|
||||||
|
defer idMem.Free()
|
||||||
|
|
||||||
|
// Call the host function
|
||||||
|
responsePtr := artwork_gettrackurl(idMem.Offset(), size)
|
||||||
|
|
||||||
|
// Read the response from memory
|
||||||
|
responseMem := pdk.FindMemory(responsePtr)
|
||||||
|
responseBytes := responseMem.ReadBytes()
|
||||||
|
|
||||||
|
// Parse the response
|
||||||
|
var response ArtworkGetTrackUrlResponse
|
||||||
|
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtworkGetPlaylistUrl calls the artwork_getplaylisturl host function.
|
||||||
|
// GetPlaylistUrl generates a public URL for a playlist's artwork.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - id: The playlist's unique identifier
|
||||||
|
// - size: Desired image size in pixels (0 for original size)
|
||||||
|
//
|
||||||
|
// Returns the public URL for the artwork, or an error if generation fails.
|
||||||
|
func ArtworkGetPlaylistUrl(id string, size int32) (*ArtworkGetPlaylistUrlResponse, error) {
|
||||||
|
idMem := pdk.AllocateString(id)
|
||||||
|
defer idMem.Free()
|
||||||
|
|
||||||
|
// Call the host function
|
||||||
|
responsePtr := artwork_getplaylisturl(idMem.Offset(), size)
|
||||||
|
|
||||||
|
// Read the response from memory
|
||||||
|
responseMem := pdk.FindMemory(responsePtr)
|
||||||
|
responseBytes := responseMem.ReadBytes()
|
||||||
|
|
||||||
|
// Parse the response
|
||||||
|
var response ArtworkGetPlaylistUrlResponse
|
||||||
|
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user