Files
Deluan Quintão f03ca44a8e feat(plugins): add lyrics provider plugin capability (#5126)
* feat(plugins): add lyrics provider plugin capability

Refactor the lyrics system from a static function to an interface-based
service that supports WASM plugin providers. Plugins listed in the
LyricsPriority config (alongside "embedded" and file extensions) are
now resolved through the plugin system.

Includes capability definition, Go/Rust PDK, adapter, Wire integration,
and tests for plugin fallback behavior.

* test(plugins): add lyrics capability integration test with test plugin

* fix(plugins): default lyrics language to 'xxx' when plugin omits it

Per the OpenSubsonic spec, the server must return 'und' or 'xxx' when
the lyrics language is unknown. The lyrics plugin adapter was passing
an empty string through when a plugin didn't provide a language value.
This defaults the language to 'xxx', consistent with all other callers
of model.ToLyrics() in the codebase.

* refactor(plugins): rename lyrics import to improve clarity

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(lyrics): update TrackInfo description for clarity

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(lyrics): enhance lyrics plugin handling and case sensitivity

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(plugins): update payload type to string with byte format for task data

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-03 15:48:39 -05:00

198 lines
6.1 KiB
Go

// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains export wrappers for the Scrobbler capability.
// It is intended for use in Navidrome plugins built with TinyGo.
//
//go:build wasip1
package scrobbler
import (
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
)
// ScrobblerError represents an error type for scrobbling operations.
type ScrobblerError string
const (
// ScrobblerErrorNotAuthorized indicates the user is not authorized.
ScrobblerErrorNotAuthorized ScrobblerError = "scrobbler(not_authorized)"
// ScrobblerErrorRetryLater indicates the operation should be retried later.
ScrobblerErrorRetryLater ScrobblerError = "scrobbler(retry_later)"
// ScrobblerErrorUnrecoverable indicates an unrecoverable error.
ScrobblerErrorUnrecoverable ScrobblerError = "scrobbler(unrecoverable)"
)
// Error implements the error interface for ScrobblerError.
func (e ScrobblerError) Error() string { return string(e) }
// ArtistRef is a reference to an artist with name and optional MBID.
type ArtistRef struct {
// ID is the internal Navidrome artist ID (if known).
ID string `json:"id,omitempty"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist.
MBID string `json:"mbid,omitempty"`
}
// IsAuthorizedRequest is the request for authorization check.
type IsAuthorizedRequest struct {
// Username is the username of the user.
Username string `json:"username"`
}
// NowPlayingRequest is the request for now playing notification.
type NowPlayingRequest struct {
// Username is the username of the user.
Username string `json:"username"`
// Track is the track currently playing.
Track TrackInfo `json:"track"`
// Position is the current playback position in seconds.
Position int32 `json:"position"`
}
// ScrobbleRequest is the request for submitting a scrobble.
type ScrobbleRequest struct {
// Username is the username of the user.
Username string `json:"username"`
// Track is the track that was played.
Track TrackInfo `json:"track"`
// Timestamp is the Unix timestamp when the track started playing.
Timestamp int64 `json:"timestamp"`
}
// TrackInfo contains track metadata.
type TrackInfo struct {
// ID is the internal Navidrome track ID.
ID string `json:"id"`
// Title is the track title.
Title string `json:"title"`
// Album is the album name.
Album string `json:"album"`
// Artist is the formatted artist name for display (e.g., "Artist1 • Artist2").
Artist string `json:"artist"`
// AlbumArtist is the formatted album artist name for display.
AlbumArtist string `json:"albumArtist"`
// Artists is the list of track artists.
Artists []ArtistRef `json:"artists"`
// AlbumArtists is the list of album artists.
AlbumArtists []ArtistRef `json:"albumArtists"`
// Duration is the track duration in seconds.
Duration float32 `json:"duration"`
// TrackNumber is the track number on the album.
TrackNumber int32 `json:"trackNumber"`
// DiscNumber is the disc number.
DiscNumber int32 `json:"discNumber"`
// MBZRecordingID is the MusicBrainz recording ID.
MBZRecordingID string `json:"mbzRecordingId,omitempty"`
// MBZAlbumID is the MusicBrainz album/release ID.
MBZAlbumID string `json:"mbzAlbumId,omitempty"`
// MBZReleaseGroupID is the MusicBrainz release group ID.
MBZReleaseGroupID string `json:"mbzReleaseGroupId,omitempty"`
// MBZReleaseTrackID is the MusicBrainz release track ID.
MBZReleaseTrackID string `json:"mbzReleaseTrackId,omitempty"`
}
// Scrobbler requires all methods to be implemented.
// Scrobbler provides scrobbling functionality to external services.
// This capability allows plugins to submit listening history to services like Last.fm,
// ListenBrainz, or custom scrobbling backends.
//
// All methods are required - plugins implementing this capability must provide
// all three functions: IsAuthorized, NowPlaying, and Scrobble.
type Scrobbler interface {
// IsAuthorized - IsAuthorized checks if a user is authorized to scrobble to this service.
IsAuthorized(IsAuthorizedRequest) (bool, error)
// NowPlaying - NowPlaying sends a now playing notification to the scrobbling service.
NowPlaying(NowPlayingRequest) error
// Scrobble - Scrobble submits a completed scrobble to the scrobbling service.
Scrobble(ScrobbleRequest) error
} // Internal implementation holders
var (
isAuthorizedImpl func(IsAuthorizedRequest) (bool, error)
nowPlayingImpl func(NowPlayingRequest) error
scrobbleImpl func(ScrobbleRequest) error
)
// Register registers a scrobbler implementation.
// All methods are required.
func Register(impl Scrobbler) {
isAuthorizedImpl = impl.IsAuthorized
nowPlayingImpl = impl.NowPlaying
scrobbleImpl = impl.Scrobble
}
// NotImplementedCode is the standard return code for unimplemented functions.
// The host recognizes this and skips the plugin gracefully.
const NotImplementedCode int32 = -2
//go:wasmexport nd_scrobbler_is_authorized
func _NdScrobblerIsAuthorized() int32 {
if isAuthorizedImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input IsAuthorizedRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
output, err := isAuthorizedImpl(input)
if err != nil {
pdk.SetError(err)
return -1
}
if err := pdk.OutputJSON(output); err != nil {
pdk.SetError(err)
return -1
}
return 0
}
//go:wasmexport nd_scrobbler_now_playing
func _NdScrobblerNowPlaying() int32 {
if nowPlayingImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input NowPlayingRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
if err := nowPlayingImpl(input); err != nil {
pdk.SetError(err)
return -1
}
return 0
}
//go:wasmexport nd_scrobbler_scrobble
func _NdScrobblerScrobble() int32 {
if scrobbleImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input ScrobbleRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
if err := scrobbleImpl(input); err != nil {
pdk.SetError(err)
return -1
}
return 0
}