update
Some checks failed
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v5) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v6) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v7) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to GHCR (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Has been cancelled
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
POEditor import / update-translations (push) Has been cancelled

This commit is contained in:
2025-12-08 16:16:23 +01:00
commit c251f174ed
1349 changed files with 194301 additions and 0 deletions

View File

@@ -0,0 +1,199 @@
{
"$id": "navidrome://plugins/manifest",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Navidrome Plugin Manifest",
"description": "Schema for Navidrome Plugin manifest.json files",
"type": "object",
"required": [
"name",
"author",
"version",
"description",
"website",
"capabilities",
"permissions"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the plugin"
},
"author": {
"type": "string",
"description": "Author or organization that created the plugin"
},
"version": {
"type": "string",
"description": "Plugin version using semantic versioning format"
},
"description": {
"type": "string",
"description": "A brief description of the plugin's functionality"
},
"website": {
"type": "string",
"format": "uri",
"description": "Website URL for the plugin or its documentation"
},
"capabilities": {
"type": "array",
"description": "List of capabilities implemented by this plugin",
"minItems": 1,
"items": {
"type": "string",
"enum": [
"MetadataAgent",
"Scrobbler",
"SchedulerCallback",
"LifecycleManagement",
"WebSocketCallback"
]
}
},
"permissions": {
"type": "object",
"description": "Host services the plugin is allowed to access",
"additionalProperties": true,
"properties": {
"http": {
"allOf": [
{ "$ref": "#/$defs/basePermission" },
{
"type": "object",
"description": "HTTP service permissions",
"required": ["allowedUrls"],
"properties": {
"allowedUrls": {
"type": "object",
"description": "Map of URL patterns (e.g., 'https://api.example.com/*') to allowed HTTP methods. Redirect destinations must also be included.",
"additionalProperties": {
"type": "array",
"items": {
"type": "string",
"enum": [
"GET",
"POST",
"PUT",
"DELETE",
"PATCH",
"HEAD",
"OPTIONS",
"*"
]
},
"minItems": 1,
"uniqueItems": true
},
"minProperties": 1
},
"allowLocalNetwork": {
"type": "boolean",
"description": "Whether to allow requests to local/private network addresses",
"default": false
}
}
}
]
},
"config": {
"allOf": [
{ "$ref": "#/$defs/basePermission" },
{
"type": "object",
"description": "Configuration service permissions"
}
]
},
"scheduler": {
"allOf": [
{ "$ref": "#/$defs/basePermission" },
{
"type": "object",
"description": "Scheduler service permissions"
}
]
},
"websocket": {
"allOf": [
{ "$ref": "#/$defs/basePermission" },
{
"type": "object",
"description": "WebSocket service permissions",
"required": ["allowedUrls"],
"properties": {
"allowedUrls": {
"type": "array",
"description": "List of WebSocket URL patterns that the plugin is allowed to connect to",
"items": {
"type": "string",
"pattern": "^wss?://.*$"
},
"minItems": 1,
"uniqueItems": true
},
"allowLocalNetwork": {
"type": "boolean",
"description": "Whether to allow connections to local/private network addresses",
"default": false
}
}
}
]
},
"cache": {
"allOf": [
{ "$ref": "#/$defs/basePermission" },
{
"type": "object",
"description": "Cache service permissions"
}
]
},
"artwork": {
"allOf": [
{ "$ref": "#/$defs/basePermission" },
{
"type": "object",
"description": "Artwork service permissions"
}
]
},
"subsonicapi": {
"allOf": [
{ "$ref": "#/$defs/basePermission" },
{
"type": "object",
"description": "SubsonicAPI service permissions",
"properties": {
"allowedUsernames": {
"type": "array",
"description": "List of usernames the plugin can pass as u. Any user if empty",
"items": { "type": "string" }
},
"allowAdmins": {
"type": "boolean",
"description": "If false, reject calls where the u is an admin",
"default": false
}
}
}
]
}
}
}
},
"$defs": {
"basePermission": {
"type": "object",
"required": ["reason"],
"properties": {
"reason": {
"type": "string",
"minLength": 1,
"description": "Explanation of why this permission is needed"
}
},
"additionalProperties": false
}
}
}

View File

@@ -0,0 +1,426 @@
// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.
package schema
import "encoding/json"
import "fmt"
import "reflect"
type BasePermission struct {
// Explanation of why this permission is needed
Reason string `json:"reason" yaml:"reason" mapstructure:"reason"`
}
// UnmarshalJSON implements json.Unmarshaler.
func (j *BasePermission) UnmarshalJSON(value []byte) error {
var raw map[string]interface{}
if err := json.Unmarshal(value, &raw); err != nil {
return err
}
if _, ok := raw["reason"]; raw != nil && !ok {
return fmt.Errorf("field reason in BasePermission: required")
}
type Plain BasePermission
var plain Plain
if err := json.Unmarshal(value, &plain); err != nil {
return err
}
if len(plain.Reason) < 1 {
return fmt.Errorf("field %s length: must be >= %d", "reason", 1)
}
*j = BasePermission(plain)
return nil
}
// Schema for Navidrome Plugin manifest.json files
type PluginManifest struct {
// Author or organization that created the plugin
Author string `json:"author" yaml:"author" mapstructure:"author"`
// List of capabilities implemented by this plugin
Capabilities []PluginManifestCapabilitiesElem `json:"capabilities" yaml:"capabilities" mapstructure:"capabilities"`
// A brief description of the plugin's functionality
Description string `json:"description" yaml:"description" mapstructure:"description"`
// Name of the plugin
Name string `json:"name" yaml:"name" mapstructure:"name"`
// Host services the plugin is allowed to access
Permissions PluginManifestPermissions `json:"permissions" yaml:"permissions" mapstructure:"permissions"`
// Plugin version using semantic versioning format
Version string `json:"version" yaml:"version" mapstructure:"version"`
// Website URL for the plugin or its documentation
Website string `json:"website" yaml:"website" mapstructure:"website"`
}
type PluginManifestCapabilitiesElem string
const PluginManifestCapabilitiesElemLifecycleManagement PluginManifestCapabilitiesElem = "LifecycleManagement"
const PluginManifestCapabilitiesElemMetadataAgent PluginManifestCapabilitiesElem = "MetadataAgent"
const PluginManifestCapabilitiesElemSchedulerCallback PluginManifestCapabilitiesElem = "SchedulerCallback"
const PluginManifestCapabilitiesElemScrobbler PluginManifestCapabilitiesElem = "Scrobbler"
const PluginManifestCapabilitiesElemWebSocketCallback PluginManifestCapabilitiesElem = "WebSocketCallback"
var enumValues_PluginManifestCapabilitiesElem = []interface{}{
"MetadataAgent",
"Scrobbler",
"SchedulerCallback",
"LifecycleManagement",
"WebSocketCallback",
}
// UnmarshalJSON implements json.Unmarshaler.
func (j *PluginManifestCapabilitiesElem) UnmarshalJSON(value []byte) error {
var v string
if err := json.Unmarshal(value, &v); err != nil {
return err
}
var ok bool
for _, expected := range enumValues_PluginManifestCapabilitiesElem {
if reflect.DeepEqual(v, expected) {
ok = true
break
}
}
if !ok {
return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_PluginManifestCapabilitiesElem, v)
}
*j = PluginManifestCapabilitiesElem(v)
return nil
}
// Host services the plugin is allowed to access
type PluginManifestPermissions struct {
// Artwork corresponds to the JSON schema field "artwork".
Artwork *PluginManifestPermissionsArtwork `json:"artwork,omitempty" yaml:"artwork,omitempty" mapstructure:"artwork,omitempty"`
// Cache corresponds to the JSON schema field "cache".
Cache *PluginManifestPermissionsCache `json:"cache,omitempty" yaml:"cache,omitempty" mapstructure:"cache,omitempty"`
// Config corresponds to the JSON schema field "config".
Config *PluginManifestPermissionsConfig `json:"config,omitempty" yaml:"config,omitempty" mapstructure:"config,omitempty"`
// Http corresponds to the JSON schema field "http".
Http *PluginManifestPermissionsHttp `json:"http,omitempty" yaml:"http,omitempty" mapstructure:"http,omitempty"`
// Scheduler corresponds to the JSON schema field "scheduler".
Scheduler *PluginManifestPermissionsScheduler `json:"scheduler,omitempty" yaml:"scheduler,omitempty" mapstructure:"scheduler,omitempty"`
// Subsonicapi corresponds to the JSON schema field "subsonicapi".
Subsonicapi *PluginManifestPermissionsSubsonicapi `json:"subsonicapi,omitempty" yaml:"subsonicapi,omitempty" mapstructure:"subsonicapi,omitempty"`
// Websocket corresponds to the JSON schema field "websocket".
Websocket *PluginManifestPermissionsWebsocket `json:"websocket,omitempty" yaml:"websocket,omitempty" mapstructure:"websocket,omitempty"`
AdditionalProperties interface{} `mapstructure:",remain"`
}
// Artwork service permissions
type PluginManifestPermissionsArtwork struct {
// Explanation of why this permission is needed
Reason string `json:"reason" yaml:"reason" mapstructure:"reason"`
}
// UnmarshalJSON implements json.Unmarshaler.
func (j *PluginManifestPermissionsArtwork) UnmarshalJSON(value []byte) error {
var raw map[string]interface{}
if err := json.Unmarshal(value, &raw); err != nil {
return err
}
if _, ok := raw["reason"]; raw != nil && !ok {
return fmt.Errorf("field reason in PluginManifestPermissionsArtwork: required")
}
type Plain PluginManifestPermissionsArtwork
var plain Plain
if err := json.Unmarshal(value, &plain); err != nil {
return err
}
if len(plain.Reason) < 1 {
return fmt.Errorf("field %s length: must be >= %d", "reason", 1)
}
*j = PluginManifestPermissionsArtwork(plain)
return nil
}
// Cache service permissions
type PluginManifestPermissionsCache struct {
// Explanation of why this permission is needed
Reason string `json:"reason" yaml:"reason" mapstructure:"reason"`
}
// UnmarshalJSON implements json.Unmarshaler.
func (j *PluginManifestPermissionsCache) UnmarshalJSON(value []byte) error {
var raw map[string]interface{}
if err := json.Unmarshal(value, &raw); err != nil {
return err
}
if _, ok := raw["reason"]; raw != nil && !ok {
return fmt.Errorf("field reason in PluginManifestPermissionsCache: required")
}
type Plain PluginManifestPermissionsCache
var plain Plain
if err := json.Unmarshal(value, &plain); err != nil {
return err
}
if len(plain.Reason) < 1 {
return fmt.Errorf("field %s length: must be >= %d", "reason", 1)
}
*j = PluginManifestPermissionsCache(plain)
return nil
}
// Configuration service permissions
type PluginManifestPermissionsConfig struct {
// Explanation of why this permission is needed
Reason string `json:"reason" yaml:"reason" mapstructure:"reason"`
}
// UnmarshalJSON implements json.Unmarshaler.
func (j *PluginManifestPermissionsConfig) UnmarshalJSON(value []byte) error {
var raw map[string]interface{}
if err := json.Unmarshal(value, &raw); err != nil {
return err
}
if _, ok := raw["reason"]; raw != nil && !ok {
return fmt.Errorf("field reason in PluginManifestPermissionsConfig: required")
}
type Plain PluginManifestPermissionsConfig
var plain Plain
if err := json.Unmarshal(value, &plain); err != nil {
return err
}
if len(plain.Reason) < 1 {
return fmt.Errorf("field %s length: must be >= %d", "reason", 1)
}
*j = PluginManifestPermissionsConfig(plain)
return nil
}
// HTTP service permissions
type PluginManifestPermissionsHttp struct {
// Whether to allow requests to local/private network addresses
AllowLocalNetwork bool `json:"allowLocalNetwork,omitempty" yaml:"allowLocalNetwork,omitempty" mapstructure:"allowLocalNetwork,omitempty"`
// Map of URL patterns (e.g., 'https://api.example.com/*') to allowed HTTP
// methods. Redirect destinations must also be included.
AllowedUrls map[string][]PluginManifestPermissionsHttpAllowedUrlsValueElem `json:"allowedUrls" yaml:"allowedUrls" mapstructure:"allowedUrls"`
// Explanation of why this permission is needed
Reason string `json:"reason" yaml:"reason" mapstructure:"reason"`
}
type PluginManifestPermissionsHttpAllowedUrlsValueElem string
const PluginManifestPermissionsHttpAllowedUrlsValueElemDELETE PluginManifestPermissionsHttpAllowedUrlsValueElem = "DELETE"
const PluginManifestPermissionsHttpAllowedUrlsValueElemGET PluginManifestPermissionsHttpAllowedUrlsValueElem = "GET"
const PluginManifestPermissionsHttpAllowedUrlsValueElemHEAD PluginManifestPermissionsHttpAllowedUrlsValueElem = "HEAD"
const PluginManifestPermissionsHttpAllowedUrlsValueElemOPTIONS PluginManifestPermissionsHttpAllowedUrlsValueElem = "OPTIONS"
const PluginManifestPermissionsHttpAllowedUrlsValueElemPATCH PluginManifestPermissionsHttpAllowedUrlsValueElem = "PATCH"
const PluginManifestPermissionsHttpAllowedUrlsValueElemPOST PluginManifestPermissionsHttpAllowedUrlsValueElem = "POST"
const PluginManifestPermissionsHttpAllowedUrlsValueElemPUT PluginManifestPermissionsHttpAllowedUrlsValueElem = "PUT"
const PluginManifestPermissionsHttpAllowedUrlsValueElemWildcard PluginManifestPermissionsHttpAllowedUrlsValueElem = "*"
var enumValues_PluginManifestPermissionsHttpAllowedUrlsValueElem = []interface{}{
"GET",
"POST",
"PUT",
"DELETE",
"PATCH",
"HEAD",
"OPTIONS",
"*",
}
// UnmarshalJSON implements json.Unmarshaler.
func (j *PluginManifestPermissionsHttpAllowedUrlsValueElem) UnmarshalJSON(value []byte) error {
var v string
if err := json.Unmarshal(value, &v); err != nil {
return err
}
var ok bool
for _, expected := range enumValues_PluginManifestPermissionsHttpAllowedUrlsValueElem {
if reflect.DeepEqual(v, expected) {
ok = true
break
}
}
if !ok {
return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_PluginManifestPermissionsHttpAllowedUrlsValueElem, v)
}
*j = PluginManifestPermissionsHttpAllowedUrlsValueElem(v)
return nil
}
// UnmarshalJSON implements json.Unmarshaler.
func (j *PluginManifestPermissionsHttp) UnmarshalJSON(value []byte) error {
var raw map[string]interface{}
if err := json.Unmarshal(value, &raw); err != nil {
return err
}
if _, ok := raw["allowedUrls"]; raw != nil && !ok {
return fmt.Errorf("field allowedUrls in PluginManifestPermissionsHttp: required")
}
if _, ok := raw["reason"]; raw != nil && !ok {
return fmt.Errorf("field reason in PluginManifestPermissionsHttp: required")
}
type Plain PluginManifestPermissionsHttp
var plain Plain
if err := json.Unmarshal(value, &plain); err != nil {
return err
}
if v, ok := raw["allowLocalNetwork"]; !ok || v == nil {
plain.AllowLocalNetwork = false
}
if len(plain.Reason) < 1 {
return fmt.Errorf("field %s length: must be >= %d", "reason", 1)
}
*j = PluginManifestPermissionsHttp(plain)
return nil
}
// Scheduler service permissions
type PluginManifestPermissionsScheduler struct {
// Explanation of why this permission is needed
Reason string `json:"reason" yaml:"reason" mapstructure:"reason"`
}
// UnmarshalJSON implements json.Unmarshaler.
func (j *PluginManifestPermissionsScheduler) UnmarshalJSON(value []byte) error {
var raw map[string]interface{}
if err := json.Unmarshal(value, &raw); err != nil {
return err
}
if _, ok := raw["reason"]; raw != nil && !ok {
return fmt.Errorf("field reason in PluginManifestPermissionsScheduler: required")
}
type Plain PluginManifestPermissionsScheduler
var plain Plain
if err := json.Unmarshal(value, &plain); err != nil {
return err
}
if len(plain.Reason) < 1 {
return fmt.Errorf("field %s length: must be >= %d", "reason", 1)
}
*j = PluginManifestPermissionsScheduler(plain)
return nil
}
// SubsonicAPI service permissions
type PluginManifestPermissionsSubsonicapi struct {
// If false, reject calls where the u is an admin
AllowAdmins bool `json:"allowAdmins,omitempty" yaml:"allowAdmins,omitempty" mapstructure:"allowAdmins,omitempty"`
// List of usernames the plugin can pass as u. Any user if empty
AllowedUsernames []string `json:"allowedUsernames,omitempty" yaml:"allowedUsernames,omitempty" mapstructure:"allowedUsernames,omitempty"`
// Explanation of why this permission is needed
Reason string `json:"reason" yaml:"reason" mapstructure:"reason"`
}
// UnmarshalJSON implements json.Unmarshaler.
func (j *PluginManifestPermissionsSubsonicapi) UnmarshalJSON(value []byte) error {
var raw map[string]interface{}
if err := json.Unmarshal(value, &raw); err != nil {
return err
}
if _, ok := raw["reason"]; raw != nil && !ok {
return fmt.Errorf("field reason in PluginManifestPermissionsSubsonicapi: required")
}
type Plain PluginManifestPermissionsSubsonicapi
var plain Plain
if err := json.Unmarshal(value, &plain); err != nil {
return err
}
if v, ok := raw["allowAdmins"]; !ok || v == nil {
plain.AllowAdmins = false
}
if len(plain.Reason) < 1 {
return fmt.Errorf("field %s length: must be >= %d", "reason", 1)
}
*j = PluginManifestPermissionsSubsonicapi(plain)
return nil
}
// WebSocket service permissions
type PluginManifestPermissionsWebsocket struct {
// Whether to allow connections to local/private network addresses
AllowLocalNetwork bool `json:"allowLocalNetwork,omitempty" yaml:"allowLocalNetwork,omitempty" mapstructure:"allowLocalNetwork,omitempty"`
// List of WebSocket URL patterns that the plugin is allowed to connect to
AllowedUrls []string `json:"allowedUrls" yaml:"allowedUrls" mapstructure:"allowedUrls"`
// Explanation of why this permission is needed
Reason string `json:"reason" yaml:"reason" mapstructure:"reason"`
}
// UnmarshalJSON implements json.Unmarshaler.
func (j *PluginManifestPermissionsWebsocket) UnmarshalJSON(value []byte) error {
var raw map[string]interface{}
if err := json.Unmarshal(value, &raw); err != nil {
return err
}
if _, ok := raw["allowedUrls"]; raw != nil && !ok {
return fmt.Errorf("field allowedUrls in PluginManifestPermissionsWebsocket: required")
}
if _, ok := raw["reason"]; raw != nil && !ok {
return fmt.Errorf("field reason in PluginManifestPermissionsWebsocket: required")
}
type Plain PluginManifestPermissionsWebsocket
var plain Plain
if err := json.Unmarshal(value, &plain); err != nil {
return err
}
if v, ok := raw["allowLocalNetwork"]; !ok || v == nil {
plain.AllowLocalNetwork = false
}
if plain.AllowedUrls != nil && len(plain.AllowedUrls) < 1 {
return fmt.Errorf("field %s length: must be >= %d", "allowedUrls", 1)
}
if len(plain.Reason) < 1 {
return fmt.Errorf("field %s length: must be >= %d", "reason", 1)
}
*j = PluginManifestPermissionsWebsocket(plain)
return nil
}
// UnmarshalJSON implements json.Unmarshaler.
func (j *PluginManifest) UnmarshalJSON(value []byte) error {
var raw map[string]interface{}
if err := json.Unmarshal(value, &raw); err != nil {
return err
}
if _, ok := raw["author"]; raw != nil && !ok {
return fmt.Errorf("field author in PluginManifest: required")
}
if _, ok := raw["capabilities"]; raw != nil && !ok {
return fmt.Errorf("field capabilities in PluginManifest: required")
}
if _, ok := raw["description"]; raw != nil && !ok {
return fmt.Errorf("field description in PluginManifest: required")
}
if _, ok := raw["name"]; raw != nil && !ok {
return fmt.Errorf("field name in PluginManifest: required")
}
if _, ok := raw["permissions"]; raw != nil && !ok {
return fmt.Errorf("field permissions in PluginManifest: required")
}
if _, ok := raw["version"]; raw != nil && !ok {
return fmt.Errorf("field version in PluginManifest: required")
}
if _, ok := raw["website"]; raw != nil && !ok {
return fmt.Errorf("field website in PluginManifest: required")
}
type Plain PluginManifest
var plain Plain
if err := json.Unmarshal(value, &plain); err != nil {
return err
}
if plain.Capabilities != nil && len(plain.Capabilities) < 1 {
return fmt.Errorf("field %s length: must be >= %d", "capabilities", 1)
}
*j = PluginManifest(plain)
return nil
}