refactor(plugins): consistent naming/types across PDK

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan 2025-12-30 08:59:12 -05:00
parent d81e641cc6
commit 31842b2c3d
33 changed files with 727 additions and 956 deletions

View File

@ -53,4 +53,7 @@
// }
//
// func Register(impl Scrobbler) { ... }
//
//go:generate go run ../cmd/ndpgen -capability-only -input=. -output=../pdk -go
//go:generate go run ../cmd/ndpgen -schemas -input=.
package capabilities

View File

@ -16,17 +16,17 @@ type Lifecycle interface {
// The output can contain an error string if initialization failed, which will be
// logged but will not prevent the plugin from being loaded.
//nd:export name=nd_on_init
OnInit(OnInitInput) (OnInitOutput, error)
OnInit(InitRequest) (InitResponse, error)
}
// OnInitInput is the input provided to the init callback.
// InitRequest is the request provided to the init callback.
// Currently empty, reserved for future use.
type OnInitInput struct{}
type InitRequest struct{}
// OnInitOutput is the output from the init callback.
type OnInitOutput struct {
// InitResponse is the response from the init callback.
type InitResponse struct {
// Error is the error message if initialization failed.
// Empty or null indicates success.
// Empty string indicates success.
// The error is logged but does not prevent the plugin from being loaded.
Error *string `json:"error,omitempty"`
Error string `json:"error,omitempty"`
}

View File

@ -7,27 +7,26 @@ exports:
The output can contain an error string if initialization failed, which will be
logged but will not prevent the plugin from being loaded.
input:
$ref: '#/components/schemas/OnInitInput'
$ref: '#/components/schemas/InitRequest'
contentType: application/json
output:
$ref: '#/components/schemas/OnInitOutput'
$ref: '#/components/schemas/InitResponse'
contentType: application/json
components:
schemas:
OnInitInput:
InitRequest:
description: |-
OnInitInput is the input provided to the init callback.
InitRequest is the request provided to the init callback.
Currently empty, reserved for future use.
type: object
properties: {}
OnInitOutput:
description: OnInitOutput is the output from the init callback.
InitResponse:
description: InitResponse is the response from the init callback.
type: object
properties:
error:
type: string
description: |-
Error is the error message if initialization failed.
Empty or null indicates success.
Empty string indicates success.
The error is logged but does not prevent the plugin from being loaded.
nullable: true

View File

@ -11,81 +11,81 @@ package capabilities
type MetadataAgent interface {
// GetArtistMBID retrieves the MusicBrainz ID for an artist.
//nd:export name=nd_get_artist_mbid
GetArtistMBID(ArtistMBIDInput) (ArtistMBIDOutput, error)
GetArtistMBID(ArtistMBIDRequest) (ArtistMBIDResponse, error)
// GetArtistURL retrieves the external URL for an artist.
//nd:export name=nd_get_artist_url
GetArtistURL(ArtistInput) (ArtistURLOutput, error)
GetArtistURL(ArtistRequest) (ArtistURLResponse, error)
// GetArtistBiography retrieves the biography for an artist.
//nd:export name=nd_get_artist_biography
GetArtistBiography(ArtistInput) (ArtistBiographyOutput, error)
GetArtistBiography(ArtistRequest) (ArtistBiographyResponse, error)
// GetSimilarArtists retrieves similar artists for a given artist.
//nd:export name=nd_get_similar_artists
GetSimilarArtists(SimilarArtistsInput) (SimilarArtistsOutput, error)
GetSimilarArtists(SimilarArtistsRequest) (SimilarArtistsResponse, error)
// GetArtistImages retrieves images for an artist.
//nd:export name=nd_get_artist_images
GetArtistImages(ArtistInput) (ArtistImagesOutput, error)
GetArtistImages(ArtistRequest) (ArtistImagesResponse, error)
// GetArtistTopSongs retrieves top songs for an artist.
//nd:export name=nd_get_artist_top_songs
GetArtistTopSongs(TopSongsInput) (TopSongsOutput, error)
GetArtistTopSongs(TopSongsRequest) (TopSongsResponse, error)
// GetAlbumInfo retrieves album information.
//nd:export name=nd_get_album_info
GetAlbumInfo(AlbumInput) (AlbumInfoOutput, error)
GetAlbumInfo(AlbumRequest) (AlbumInfoResponse, error)
// GetAlbumImages retrieves images for an album.
//nd:export name=nd_get_album_images
GetAlbumImages(AlbumInput) (AlbumImagesOutput, error)
GetAlbumImages(AlbumRequest) (AlbumImagesResponse, error)
}
// ArtistMBIDInput is the input for GetArtistMBID.
type ArtistMBIDInput struct {
// ArtistMBIDRequest is the request for GetArtistMBID.
type ArtistMBIDRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
}
// ArtistMBIDOutput is the output for GetArtistMBID.
type ArtistMBIDOutput struct {
// ArtistMBIDResponse is the response for GetArtistMBID.
type ArtistMBIDResponse struct {
// MBID is the MusicBrainz ID for the artist.
MBID string `json:"mbid"`
}
// ArtistInput is the common input for artist-related functions.
type ArtistInput struct {
// ArtistRequest is the common request for artist-related functions.
type ArtistRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID *string `json:"mbid,omitempty"`
MBID string `json:"mbid,omitempty"`
}
// ArtistURLOutput is the output for GetArtistURL.
type ArtistURLOutput struct {
// ArtistURLResponse is the response for GetArtistURL.
type ArtistURLResponse struct {
// URL is the external URL for the artist.
URL string `json:"url"`
}
// ArtistBiographyOutput is the output for GetArtistBiography.
type ArtistBiographyOutput struct {
// ArtistBiographyResponse is the response for GetArtistBiography.
type ArtistBiographyResponse struct {
// Biography is the artist biography text.
Biography string `json:"biography"`
}
// SimilarArtistsInput is the input for GetSimilarArtists.
type SimilarArtistsInput struct {
// SimilarArtistsRequest is the request for GetSimilarArtists.
type SimilarArtistsRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID *string `json:"mbid,omitempty"`
MBID string `json:"mbid,omitempty"`
// Limit is the maximum number of similar artists to return.
Limit int32 `json:"limit"`
}
@ -95,11 +95,11 @@ type ArtistRef struct {
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist.
MBID *string `json:"mbid,omitempty"`
MBID string `json:"mbid,omitempty"`
}
// SimilarArtistsOutput is the output for GetSimilarArtists.
type SimilarArtistsOutput struct {
// SimilarArtistsResponse is the response for GetSimilarArtists.
type SimilarArtistsResponse struct {
// Artists is the list of similar artists.
Artists []ArtistRef `json:"artists"`
}
@ -112,20 +112,20 @@ type ImageInfo struct {
Size int32 `json:"size"`
}
// ArtistImagesOutput is the output for GetArtistImages.
type ArtistImagesOutput struct {
// ArtistImagesResponse is the response for GetArtistImages.
type ArtistImagesResponse struct {
// Images is the list of artist images.
Images []ImageInfo `json:"images"`
}
// TopSongsInput is the input for GetArtistTopSongs.
type TopSongsInput struct {
// TopSongsRequest is the request for GetArtistTopSongs.
type TopSongsRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID *string `json:"mbid,omitempty"`
MBID string `json:"mbid,omitempty"`
// Count is the maximum number of top songs to return.
Count int32 `json:"count"`
}
@ -135,27 +135,27 @@ type SongRef struct {
// Name is the song name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the song.
MBID *string `json:"mbid,omitempty"`
MBID string `json:"mbid,omitempty"`
}
// TopSongsOutput is the output for GetArtistTopSongs.
type TopSongsOutput struct {
// TopSongsResponse is the response for GetArtistTopSongs.
type TopSongsResponse struct {
// Songs is the list of top songs.
Songs []SongRef `json:"songs"`
}
// AlbumInput is the common input for album-related functions.
type AlbumInput struct {
// AlbumRequest is the common request for album-related functions.
type AlbumRequest struct {
// Name is the album name.
Name string `json:"name"`
// Artist is the album artist name.
Artist string `json:"artist"`
// MBID is the MusicBrainz ID for the album (if known).
MBID *string `json:"mbid,omitempty"`
MBID string `json:"mbid,omitempty"`
}
// AlbumInfoOutput is the output for GetAlbumInfo.
type AlbumInfoOutput struct {
// AlbumInfoResponse is the response for GetAlbumInfo.
type AlbumInfoResponse struct {
// Name is the album name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the album.
@ -166,8 +166,8 @@ type AlbumInfoOutput struct {
URL string `json:"url"`
}
// AlbumImagesOutput is the output for GetAlbumImages.
type AlbumImagesOutput struct {
// AlbumImagesResponse is the response for GetAlbumImages.
type AlbumImagesResponse struct {
// Images is the list of album images.
Images []ImageInfo `json:"images"`
}

View File

@ -3,71 +3,71 @@ exports:
nd_get_artist_mbid:
description: GetArtistMBID retrieves the MusicBrainz ID for an artist.
input:
$ref: '#/components/schemas/ArtistMBIDInput'
$ref: '#/components/schemas/ArtistMBIDRequest'
contentType: application/json
output:
$ref: '#/components/schemas/ArtistMBIDOutput'
$ref: '#/components/schemas/ArtistMBIDResponse'
contentType: application/json
nd_get_artist_url:
description: GetArtistURL retrieves the external URL for an artist.
input:
$ref: '#/components/schemas/ArtistInput'
$ref: '#/components/schemas/ArtistRequest'
contentType: application/json
output:
$ref: '#/components/schemas/ArtistURLOutput'
$ref: '#/components/schemas/ArtistURLResponse'
contentType: application/json
nd_get_artist_biography:
description: GetArtistBiography retrieves the biography for an artist.
input:
$ref: '#/components/schemas/ArtistInput'
$ref: '#/components/schemas/ArtistRequest'
contentType: application/json
output:
$ref: '#/components/schemas/ArtistBiographyOutput'
$ref: '#/components/schemas/ArtistBiographyResponse'
contentType: application/json
nd_get_similar_artists:
description: GetSimilarArtists retrieves similar artists for a given artist.
input:
$ref: '#/components/schemas/SimilarArtistsInput'
$ref: '#/components/schemas/SimilarArtistsRequest'
contentType: application/json
output:
$ref: '#/components/schemas/SimilarArtistsOutput'
$ref: '#/components/schemas/SimilarArtistsResponse'
contentType: application/json
nd_get_artist_images:
description: GetArtistImages retrieves images for an artist.
input:
$ref: '#/components/schemas/ArtistInput'
$ref: '#/components/schemas/ArtistRequest'
contentType: application/json
output:
$ref: '#/components/schemas/ArtistImagesOutput'
$ref: '#/components/schemas/ArtistImagesResponse'
contentType: application/json
nd_get_artist_top_songs:
description: GetArtistTopSongs retrieves top songs for an artist.
input:
$ref: '#/components/schemas/TopSongsInput'
$ref: '#/components/schemas/TopSongsRequest'
contentType: application/json
output:
$ref: '#/components/schemas/TopSongsOutput'
$ref: '#/components/schemas/TopSongsResponse'
contentType: application/json
nd_get_album_info:
description: GetAlbumInfo retrieves album information.
input:
$ref: '#/components/schemas/AlbumInput'
$ref: '#/components/schemas/AlbumRequest'
contentType: application/json
output:
$ref: '#/components/schemas/AlbumInfoOutput'
$ref: '#/components/schemas/AlbumInfoResponse'
contentType: application/json
nd_get_album_images:
description: GetAlbumImages retrieves images for an album.
input:
$ref: '#/components/schemas/AlbumInput'
$ref: '#/components/schemas/AlbumRequest'
contentType: application/json
output:
$ref: '#/components/schemas/AlbumImagesOutput'
$ref: '#/components/schemas/AlbumImagesResponse'
contentType: application/json
components:
schemas:
AlbumImagesOutput:
description: AlbumImagesOutput is the output for GetAlbumImages.
AlbumImagesResponse:
description: AlbumImagesResponse is the response for GetAlbumImages.
type: object
properties:
images:
@ -77,8 +77,8 @@ components:
$ref: '#/components/schemas/ImageInfo'
required:
- images
AlbumInfoOutput:
description: AlbumInfoOutput is the output for GetAlbumInfo.
AlbumInfoResponse:
description: AlbumInfoResponse is the response for GetAlbumInfo.
type: object
properties:
name:
@ -98,8 +98,8 @@ components:
- mbid
- description
- url
AlbumInput:
description: AlbumInput is the common input for album-related functions.
AlbumRequest:
description: AlbumRequest is the common request for album-related functions.
type: object
properties:
name:
@ -111,12 +111,11 @@ components:
mbid:
type: string
description: MBID is the MusicBrainz ID for the album (if known).
nullable: true
required:
- name
- artist
ArtistBiographyOutput:
description: ArtistBiographyOutput is the output for GetArtistBiography.
ArtistBiographyResponse:
description: ArtistBiographyResponse is the response for GetArtistBiography.
type: object
properties:
biography:
@ -124,8 +123,8 @@ components:
description: Biography is the artist biography text.
required:
- biography
ArtistImagesOutput:
description: ArtistImagesOutput is the output for GetArtistImages.
ArtistImagesResponse:
description: ArtistImagesResponse is the response for GetArtistImages.
type: object
properties:
images:
@ -135,25 +134,8 @@ components:
$ref: '#/components/schemas/ImageInfo'
required:
- images
ArtistInput:
description: ArtistInput is the common input for artist-related functions.
type: object
properties:
id:
type: string
description: ID is the internal Navidrome artist ID.
name:
type: string
description: Name is the artist name.
mbid:
type: string
description: MBID is the MusicBrainz ID for the artist (if known).
nullable: true
required:
- id
- name
ArtistMBIDInput:
description: ArtistMBIDInput is the input for GetArtistMBID.
ArtistMBIDRequest:
description: ArtistMBIDRequest is the request for GetArtistMBID.
type: object
properties:
id:
@ -165,8 +147,8 @@ components:
required:
- id
- name
ArtistMBIDOutput:
description: ArtistMBIDOutput is the output for GetArtistMBID.
ArtistMBIDResponse:
description: ArtistMBIDResponse is the response for GetArtistMBID.
type: object
properties:
mbid:
@ -184,11 +166,26 @@ components:
mbid:
type: string
description: MBID is the MusicBrainz ID for the artist.
nullable: true
required:
- name
ArtistURLOutput:
description: ArtistURLOutput is the output for GetArtistURL.
ArtistRequest:
description: ArtistRequest is the common request for artist-related functions.
type: object
properties:
id:
type: string
description: ID is the internal Navidrome artist ID.
name:
type: string
description: Name is the artist name.
mbid:
type: string
description: MBID is the MusicBrainz ID for the artist (if known).
required:
- id
- name
ArtistURLResponse:
description: ArtistURLResponse is the response for GetArtistURL.
type: object
properties:
url:
@ -210,8 +207,8 @@ components:
required:
- url
- size
SimilarArtistsInput:
description: SimilarArtistsInput is the input for GetSimilarArtists.
SimilarArtistsRequest:
description: SimilarArtistsRequest is the request for GetSimilarArtists.
type: object
properties:
id:
@ -223,7 +220,6 @@ components:
mbid:
type: string
description: MBID is the MusicBrainz ID for the artist (if known).
nullable: true
limit:
type: integer
format: int32
@ -232,8 +228,8 @@ components:
- id
- name
- limit
SimilarArtistsOutput:
description: SimilarArtistsOutput is the output for GetSimilarArtists.
SimilarArtistsResponse:
description: SimilarArtistsResponse is the response for GetSimilarArtists.
type: object
properties:
artists:
@ -253,11 +249,10 @@ components:
mbid:
type: string
description: MBID is the MusicBrainz ID for the song.
nullable: true
required:
- name
TopSongsInput:
description: TopSongsInput is the input for GetArtistTopSongs.
TopSongsRequest:
description: TopSongsRequest is the request for GetArtistTopSongs.
type: object
properties:
id:
@ -269,7 +264,6 @@ components:
mbid:
type: string
description: MBID is the MusicBrainz ID for the artist (if known).
nullable: true
count:
type: integer
format: int32
@ -278,8 +272,8 @@ components:
- id
- name
- count
TopSongsOutput:
description: TopSongsOutput is the output for GetArtistTopSongs.
TopSongsResponse:
description: TopSongsResponse is the response for GetArtistTopSongs.
type: object
properties:
songs:

View File

@ -9,11 +9,11 @@ package capabilities
type SchedulerCallback interface {
// OnSchedulerCallback is called when a scheduled task fires.
//nd:export name=nd_scheduler_callback
OnSchedulerCallback(SchedulerCallbackInput) (SchedulerCallbackOutput, error)
OnSchedulerCallback(SchedulerCallbackRequest) (SchedulerCallbackResponse, error)
}
// SchedulerCallbackInput is the input provided when a scheduled task fires.
type SchedulerCallbackInput struct {
// SchedulerCallbackRequest is the request provided when a scheduled task fires.
type SchedulerCallbackRequest struct {
// ScheduleID is the unique identifier for this scheduled task.
// This is either the ID provided when scheduling, or an auto-generated UUID if none was specified.
ScheduleID string `json:"scheduleId"`
@ -25,10 +25,10 @@ type SchedulerCallbackInput struct {
IsRecurring bool `json:"isRecurring"`
}
// SchedulerCallbackOutput is the output from the scheduler callback.
type SchedulerCallbackOutput struct {
// SchedulerCallbackResponse is the response from the scheduler callback.
type SchedulerCallbackResponse struct {
// Error is the error message if the callback failed to process the scheduled task.
// Empty or null indicates success. The error is logged but does not
// Empty string indicates success. The error is logged but does not
// affect the scheduling system.
Error *string `json:"error,omitempty"`
Error string `json:"error,omitempty"`
}

View File

@ -3,15 +3,15 @@ exports:
nd_scheduler_callback:
description: OnSchedulerCallback is called when a scheduled task fires.
input:
$ref: '#/components/schemas/SchedulerCallbackInput'
$ref: '#/components/schemas/SchedulerCallbackRequest'
contentType: application/json
output:
$ref: '#/components/schemas/SchedulerCallbackOutput'
$ref: '#/components/schemas/SchedulerCallbackResponse'
contentType: application/json
components:
schemas:
SchedulerCallbackInput:
description: SchedulerCallbackInput is the input provided when a scheduled task fires.
SchedulerCallbackRequest:
description: SchedulerCallbackRequest is the request provided when a scheduled task fires.
type: object
properties:
scheduleId:
@ -33,14 +33,13 @@ components:
- scheduleId
- payload
- isRecurring
SchedulerCallbackOutput:
description: SchedulerCallbackOutput is the output from the scheduler callback.
SchedulerCallbackResponse:
description: SchedulerCallbackResponse is the response from the scheduler callback.
type: object
properties:
error:
type: string
description: |-
Error is the error message if the callback failed to process the scheduled task.
Empty or null indicates success. The error is logged but does not
Empty string indicates success. The error is logged but does not
affect the scheduling system.
nullable: true

View File

@ -11,27 +11,27 @@ package capabilities
type Scrobbler interface {
// IsAuthorized checks if a user is authorized to scrobble to this service.
//nd:export name=nd_scrobbler_is_authorized
IsAuthorized(AuthInput) (AuthOutput, error)
IsAuthorized(IsAuthorizedRequest) (IsAuthorizedResponse, error)
// NowPlaying sends a now playing notification to the scrobbling service.
//nd:export name=nd_scrobbler_now_playing
NowPlaying(NowPlayingInput) (ScrobblerOutput, error)
NowPlaying(NowPlayingRequest) (ScrobblerResponse, error)
// Scrobble submits a completed scrobble to the scrobbling service.
//nd:export name=nd_scrobbler_scrobble
Scrobble(ScrobbleInput) (ScrobblerOutput, error)
Scrobble(ScrobbleRequest) (ScrobblerResponse, error)
}
// AuthInput is the input for authorization check.
type AuthInput struct {
// IsAuthorizedRequest is the request for authorization check.
type IsAuthorizedRequest struct {
// UserID is the internal Navidrome user ID.
UserID string `json:"userId"`
// Username is the username of the user.
Username string `json:"username"`
}
// AuthOutput is the output for authorization check.
type AuthOutput struct {
// IsAuthorizedResponse is the response for authorization check.
type IsAuthorizedResponse struct {
// Authorized indicates whether the user is authorized to scrobble.
Authorized bool `json:"authorized"`
}
@ -49,27 +49,27 @@ type TrackInfo struct {
// AlbumArtist is the album artist.
AlbumArtist string `json:"albumArtist"`
// Duration is the track duration in seconds.
Duration float64 `json:"duration"`
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"`
MBZRecordingID string `json:"mbzRecordingId,omitempty"`
// MBZAlbumID is the MusicBrainz album/release ID.
MBZAlbumID *string `json:"mbzAlbumId,omitempty"`
MBZAlbumID string `json:"mbzAlbumId,omitempty"`
// MBZArtistID is the MusicBrainz artist ID.
MBZArtistID *string `json:"mbzArtistId,omitempty"`
MBZArtistID string `json:"mbzArtistId,omitempty"`
// MBZReleaseGroupID is the MusicBrainz release group ID.
MBZReleaseGroupID *string `json:"mbzReleaseGroupId,omitempty"`
MBZReleaseGroupID string `json:"mbzReleaseGroupId,omitempty"`
// MBZAlbumArtistID is the MusicBrainz album artist ID.
MBZAlbumArtistID *string `json:"mbzAlbumArtistId,omitempty"`
MBZAlbumArtistID string `json:"mbzAlbumArtistId,omitempty"`
// MBZReleaseTrackID is the MusicBrainz release track ID.
MBZReleaseTrackID *string `json:"mbzReleaseTrackId,omitempty"`
MBZReleaseTrackID string `json:"mbzReleaseTrackId,omitempty"`
}
// NowPlayingInput is the input for now playing notification.
type NowPlayingInput struct {
// NowPlayingRequest is the request for now playing notification.
type NowPlayingRequest struct {
// UserID is the internal Navidrome user ID.
UserID string `json:"userId"`
// Username is the username of the user.
@ -80,8 +80,8 @@ type NowPlayingInput struct {
Position int32 `json:"position"`
}
// ScrobbleInput is the input for submitting a scrobble.
type ScrobbleInput struct {
// ScrobbleRequest is the request for submitting a scrobble.
type ScrobbleRequest struct {
// UserID is the internal Navidrome user ID.
UserID string `json:"userId"`
// Username is the username of the user.
@ -106,10 +106,10 @@ const (
ScrobblerErrorUnrecoverable ScrobblerErrorType = "unrecoverable"
)
// ScrobblerOutput is the output for scrobbler operations.
type ScrobblerOutput struct {
// ScrobblerResponse is the response for scrobbler operations.
type ScrobblerResponse struct {
// Error is the error message if the operation failed.
Error *string `json:"error,omitempty"`
Error string `json:"error,omitempty"`
// ErrorType indicates how Navidrome should handle the error.
ErrorType *ScrobblerErrorType `json:"errorType,omitempty"`
ErrorType ScrobblerErrorType `json:"errorType,omitempty"`
}

View File

@ -3,31 +3,31 @@ exports:
nd_scrobbler_is_authorized:
description: IsAuthorized checks if a user is authorized to scrobble to this service.
input:
$ref: '#/components/schemas/AuthInput'
$ref: '#/components/schemas/IsAuthorizedRequest'
contentType: application/json
output:
$ref: '#/components/schemas/AuthOutput'
$ref: '#/components/schemas/IsAuthorizedResponse'
contentType: application/json
nd_scrobbler_now_playing:
description: NowPlaying sends a now playing notification to the scrobbling service.
input:
$ref: '#/components/schemas/NowPlayingInput'
$ref: '#/components/schemas/NowPlayingRequest'
contentType: application/json
output:
$ref: '#/components/schemas/ScrobblerOutput'
$ref: '#/components/schemas/ScrobblerResponse'
contentType: application/json
nd_scrobbler_scrobble:
description: Scrobble submits a completed scrobble to the scrobbling service.
input:
$ref: '#/components/schemas/ScrobbleInput'
$ref: '#/components/schemas/ScrobbleRequest'
contentType: application/json
output:
$ref: '#/components/schemas/ScrobblerOutput'
$ref: '#/components/schemas/ScrobblerResponse'
contentType: application/json
components:
schemas:
AuthInput:
description: AuthInput is the input for authorization check.
IsAuthorizedRequest:
description: IsAuthorizedRequest is the request for authorization check.
type: object
properties:
userId:
@ -39,8 +39,8 @@ components:
required:
- userId
- username
AuthOutput:
description: AuthOutput is the output for authorization check.
IsAuthorizedResponse:
description: IsAuthorizedResponse is the response for authorization check.
type: object
properties:
authorized:
@ -48,8 +48,8 @@ components:
description: Authorized indicates whether the user is authorized to scrobble.
required:
- authorized
NowPlayingInput:
description: NowPlayingInput is the input for now playing notification.
NowPlayingRequest:
description: NowPlayingRequest is the request for now playing notification.
type: object
properties:
userId:
@ -70,8 +70,8 @@ components:
- username
- track
- position
ScrobbleInput:
description: ScrobbleInput is the input for submitting a scrobble.
ScrobbleRequest:
description: ScrobbleRequest is the request for submitting a scrobble.
type: object
properties:
userId:
@ -92,18 +92,16 @@ components:
- username
- track
- timestamp
ScrobblerOutput:
description: ScrobblerOutput is the output for scrobbler operations.
ScrobblerResponse:
description: ScrobblerResponse is the response for scrobbler operations.
type: object
properties:
error:
type: string
description: Error is the error message if the operation failed.
nullable: true
errorType:
$ref: '#/components/schemas/ScrobblerErrorType'
description: ErrorType indicates how Navidrome should handle the error.
nullable: true
TrackInfo:
description: TrackInfo contains track metadata for scrobbling.
type: object
@ -138,27 +136,21 @@ components:
mbzRecordingId:
type: string
description: MBZRecordingID is the MusicBrainz recording ID.
nullable: true
mbzAlbumId:
type: string
description: MBZAlbumID is the MusicBrainz album/release ID.
nullable: true
mbzArtistId:
type: string
description: MBZArtistID is the MusicBrainz artist ID.
nullable: true
mbzReleaseGroupId:
type: string
description: MBZReleaseGroupID is the MusicBrainz release group ID.
nullable: true
mbzAlbumArtistId:
type: string
description: MBZAlbumArtistID is the MusicBrainz album artist ID.
nullable: true
mbzReleaseTrackId:
type: string
description: MBZReleaseTrackID is the MusicBrainz release track ID.
nullable: true
required:
- id
- title

View File

@ -10,68 +10,68 @@ package capabilities
type WebSocketCallback interface {
// OnTextMessage is called when a text message is received on a WebSocket connection.
//nd:export name=nd_websocket_on_text_message
OnTextMessage(OnTextMessageInput) (OnTextMessageOutput, error)
OnTextMessage(OnTextMessageRequest) (OnTextMessageResponse, error)
// OnBinaryMessage is called when a binary message is received on a WebSocket connection.
//nd:export name=nd_websocket_on_binary_message
OnBinaryMessage(OnBinaryMessageInput) (OnBinaryMessageOutput, error)
OnBinaryMessage(OnBinaryMessageRequest) (OnBinaryMessageResponse, error)
// OnError is called when an error occurs on a WebSocket connection.
//nd:export name=nd_websocket_on_error
OnError(OnErrorInput) (OnErrorOutput, error)
OnError(OnErrorRequest) (OnErrorResponse, error)
// OnClose is called when a WebSocket connection is closed.
//nd:export name=nd_websocket_on_close
OnClose(OnCloseInput) (OnCloseOutput, error)
OnClose(OnCloseRequest) (OnCloseResponse, error)
}
// OnTextMessageInput is the input provided when a text message is received.
type OnTextMessageInput struct {
// OnTextMessageRequest is the request provided when a text message is received.
type OnTextMessageRequest struct {
// ConnectionID is the unique identifier for the WebSocket connection that received the message.
ConnectionID string `json:"connectionId"`
// Message is the text message content received from the WebSocket.
Message string `json:"message"`
}
// OnTextMessageOutput is the output from the text message handler.
type OnTextMessageOutput struct {
// OnTextMessageResponse is the response from the text message handler.
type OnTextMessageResponse struct {
// Error is the error message if the callback failed.
// Empty or null indicates success.
Error *string `json:"error,omitempty"`
// Empty string indicates success.
Error string `json:"error,omitempty"`
}
// OnBinaryMessageInput is the input provided when a binary message is received.
type OnBinaryMessageInput struct {
// OnBinaryMessageRequest is the request provided when a binary message is received.
type OnBinaryMessageRequest struct {
// ConnectionID is the unique identifier for the WebSocket connection that received the message.
ConnectionID string `json:"connectionId"`
// Data is the binary data received from the WebSocket, encoded as base64.
Data string `json:"data"`
}
// OnBinaryMessageOutput is the output from the binary message handler.
type OnBinaryMessageOutput struct {
// OnBinaryMessageResponse is the response from the binary message handler.
type OnBinaryMessageResponse struct {
// Error is the error message if the callback failed.
// Empty or null indicates success.
Error *string `json:"error,omitempty"`
// Empty string indicates success.
Error string `json:"error,omitempty"`
}
// OnErrorInput is the input provided when an error occurs on a WebSocket connection.
type OnErrorInput struct {
// OnErrorRequest is the request provided when an error occurs on a WebSocket connection.
type OnErrorRequest struct {
// ConnectionID is the unique identifier for the WebSocket connection where the error occurred.
ConnectionID string `json:"connectionId"`
// Error is the error message describing what went wrong.
Error string `json:"error"`
}
// OnErrorOutput is the output from the error handler.
type OnErrorOutput struct {
// OnErrorResponse is the response from the error handler.
type OnErrorResponse struct {
// Error is the error message if the callback failed.
// Empty or null indicates success.
Error *string `json:"error,omitempty"`
// Empty string indicates success.
Error string `json:"error,omitempty"`
}
// OnCloseInput is the input provided when a WebSocket connection is closed.
type OnCloseInput struct {
// OnCloseRequest is the request provided when a WebSocket connection is closed.
type OnCloseRequest struct {
// ConnectionID is the unique identifier for the WebSocket connection that was closed.
ConnectionID string `json:"connectionId"`
// Code is the WebSocket close status code (e.g., 1000 for normal closure,
@ -81,9 +81,9 @@ type OnCloseInput struct {
Reason string `json:"reason"`
}
// OnCloseOutput is the output from the close handler.
type OnCloseOutput struct {
// OnCloseResponse is the response from the close handler.
type OnCloseResponse struct {
// Error is the error message if the callback failed.
// Empty or null indicates success.
Error *string `json:"error,omitempty"`
// Empty string indicates success.
Error string `json:"error,omitempty"`
}

View File

@ -3,39 +3,39 @@ exports:
nd_websocket_on_text_message:
description: OnTextMessage is called when a text message is received on a WebSocket connection.
input:
$ref: '#/components/schemas/OnTextMessageInput'
$ref: '#/components/schemas/OnTextMessageRequest'
contentType: application/json
output:
$ref: '#/components/schemas/OnTextMessageOutput'
$ref: '#/components/schemas/OnTextMessageResponse'
contentType: application/json
nd_websocket_on_binary_message:
description: OnBinaryMessage is called when a binary message is received on a WebSocket connection.
input:
$ref: '#/components/schemas/OnBinaryMessageInput'
$ref: '#/components/schemas/OnBinaryMessageRequest'
contentType: application/json
output:
$ref: '#/components/schemas/OnBinaryMessageOutput'
$ref: '#/components/schemas/OnBinaryMessageResponse'
contentType: application/json
nd_websocket_on_error:
description: OnError is called when an error occurs on a WebSocket connection.
input:
$ref: '#/components/schemas/OnErrorInput'
$ref: '#/components/schemas/OnErrorRequest'
contentType: application/json
output:
$ref: '#/components/schemas/OnErrorOutput'
$ref: '#/components/schemas/OnErrorResponse'
contentType: application/json
nd_websocket_on_close:
description: OnClose is called when a WebSocket connection is closed.
input:
$ref: '#/components/schemas/OnCloseInput'
$ref: '#/components/schemas/OnCloseRequest'
contentType: application/json
output:
$ref: '#/components/schemas/OnCloseOutput'
$ref: '#/components/schemas/OnCloseResponse'
contentType: application/json
components:
schemas:
OnBinaryMessageInput:
description: OnBinaryMessageInput is the input provided when a binary message is received.
OnBinaryMessageRequest:
description: OnBinaryMessageRequest is the request provided when a binary message is received.
type: object
properties:
connectionId:
@ -47,18 +47,17 @@ components:
required:
- connectionId
- data
OnBinaryMessageOutput:
description: OnBinaryMessageOutput is the output from the binary message handler.
OnBinaryMessageResponse:
description: OnBinaryMessageResponse is the response from the binary message handler.
type: object
properties:
error:
type: string
description: |-
Error is the error message if the callback failed.
Empty or null indicates success.
nullable: true
OnCloseInput:
description: OnCloseInput is the input provided when a WebSocket connection is closed.
Empty string indicates success.
OnCloseRequest:
description: OnCloseRequest is the request provided when a WebSocket connection is closed.
type: object
properties:
connectionId:
@ -77,18 +76,17 @@ components:
- connectionId
- code
- reason
OnCloseOutput:
description: OnCloseOutput is the output from the close handler.
OnCloseResponse:
description: OnCloseResponse is the response from the close handler.
type: object
properties:
error:
type: string
description: |-
Error is the error message if the callback failed.
Empty or null indicates success.
nullable: true
OnErrorInput:
description: OnErrorInput is the input provided when an error occurs on a WebSocket connection.
Empty string indicates success.
OnErrorRequest:
description: OnErrorRequest is the request provided when an error occurs on a WebSocket connection.
type: object
properties:
connectionId:
@ -100,18 +98,17 @@ components:
required:
- connectionId
- error
OnErrorOutput:
description: OnErrorOutput is the output from the error handler.
OnErrorResponse:
description: OnErrorResponse is the response from the error handler.
type: object
properties:
error:
type: string
description: |-
Error is the error message if the callback failed.
Empty or null indicates success.
nullable: true
OnTextMessageInput:
description: OnTextMessageInput is the input provided when a text message is received.
Empty string indicates success.
OnTextMessageRequest:
description: OnTextMessageRequest is the request provided when a text message is received.
type: object
properties:
connectionId:
@ -123,13 +120,12 @@ components:
required:
- connectionId
- message
OnTextMessageOutput:
description: OnTextMessageOutput is the output from the text message handler.
OnTextMessageResponse:
description: OnTextMessageResponse is the response from the text message handler.
type: object
properties:
error:
type: string
description: |-
Error is the error message if the callback failed.
Empty or null indicates success.
nullable: true
Empty string indicates success.

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/plugins/capabilities"
)
// CapabilityLifecycle indicates the plugin has lifecycle callback functions.
@ -19,14 +20,6 @@ func init() {
)
}
// onInitInput is the input for nd_on_init (currently empty, reserved for future use)
type onInitInput struct{}
// onInitOutput is the output from nd_on_init
type onInitOutput struct {
Error string `json:"error,omitempty"`
}
// callPluginInit calls the plugin's nd_on_init function if it has the Lifecycle capability.
// This is called after the plugin is fully loaded with all services registered.
func callPluginInit(ctx context.Context, instance *plugin) {
@ -36,7 +29,7 @@ func callPluginInit(ctx context.Context, instance *plugin) {
log.Debug(ctx, "Calling plugin init function", "plugin", instance.name)
result, err := callPluginFunction[onInitInput, onInitOutput](ctx, instance, FuncOnInit, onInitInput{})
result, err := callPluginFunction[capabilities.InitRequest, capabilities.InitResponse](ctx, instance, FuncOnInit, capabilities.InitRequest{})
if err != nil {
log.Error(ctx, "Plugin init function failed", "plugin", instance.name, err)
return

View File

@ -71,7 +71,7 @@ var (
// OnInit is called when the plugin is loaded.
// We use this to establish the initial WebSocket connection.
func (p *cryptoTickerPlugin) OnInit(_ lifecycle.OnInitInput) (lifecycle.OnInitOutput, error) {
func (p *cryptoTickerPlugin) OnInit(_ lifecycle.InitRequest) (lifecycle.InitResponse, error) {
pdk.Log(pdk.LogInfo, "Crypto Ticker Plugin initializing...")
// Get ticker configuration
@ -90,7 +90,7 @@ func (p *cryptoTickerPlugin) OnInit(_ lifecycle.OnInitInput) (lifecycle.OnInitOu
// Don't fail init - let reconnect logic handle it
}
return lifecycle.OnInitOutput{}, nil
return lifecycle.InitResponse{}, nil
}
// parseTickerSymbols parses a comma-separated list of ticker symbols
@ -143,10 +143,10 @@ func connectAndSubscribe(tickers []string) error {
}
// OnTextMessage is called when a text message is received
func (p *cryptoTickerPlugin) OnTextMessage(input websocket.OnTextMessageInput) (websocket.OnTextMessageOutput, error) {
func (p *cryptoTickerPlugin) OnTextMessage(input websocket.OnTextMessageRequest) (websocket.OnTextMessageResponse, error) {
// Only process messages from our connection
if input.ConnectionID != connectionID {
return websocket.OnTextMessageOutput{}, nil
return websocket.OnTextMessageResponse{}, nil
}
// Try to parse as a ticker message
@ -154,7 +154,7 @@ func (p *cryptoTickerPlugin) OnTextMessage(input websocket.OnTextMessageInput) (
err := json.Unmarshal([]byte(input.Message), &ticker)
if err != nil {
// Not a valid JSON message, ignore
return websocket.OnTextMessageOutput{}, nil
return websocket.OnTextMessageResponse{}, nil
}
// Only process ticker messages
@ -163,7 +163,7 @@ func (p *cryptoTickerPlugin) OnTextMessage(input websocket.OnTextMessageInput) (
if ticker.Type != "" {
pdk.Log(pdk.LogDebug, fmt.Sprintf("Received %s message", ticker.Type))
}
return websocket.OnTextMessageOutput{}, nil
return websocket.OnTextMessageResponse{}, nil
}
// Calculate 24h change percentage
@ -178,24 +178,24 @@ func (p *cryptoTickerPlugin) OnTextMessage(input websocket.OnTextMessageInput) (
ticker.BestAsk,
))
return websocket.OnTextMessageOutput{}, nil
return websocket.OnTextMessageResponse{}, nil
}
// OnBinaryMessage is called when a binary message is received
func (p *cryptoTickerPlugin) OnBinaryMessage(input websocket.OnBinaryMessageInput) (websocket.OnBinaryMessageOutput, error) {
func (p *cryptoTickerPlugin) OnBinaryMessage(input websocket.OnBinaryMessageRequest) (websocket.OnBinaryMessageResponse, error) {
// Coinbase doesn't send binary messages, but we implement the handler anyway
pdk.Log(pdk.LogWarn, fmt.Sprintf("Received unexpected binary message on connection %s", input.ConnectionID))
return websocket.OnBinaryMessageOutput{}, nil
return websocket.OnBinaryMessageResponse{}, nil
}
// OnError is called when an error occurs on the WebSocket connection
func (p *cryptoTickerPlugin) OnError(input websocket.OnErrorInput) (websocket.OnErrorOutput, error) {
func (p *cryptoTickerPlugin) OnError(input websocket.OnErrorRequest) (websocket.OnErrorResponse, error) {
pdk.Log(pdk.LogError, fmt.Sprintf("WebSocket error on connection %s: %s", input.ConnectionID, input.Error))
return websocket.OnErrorOutput{}, nil
return websocket.OnErrorResponse{}, nil
}
// OnClose is called when the WebSocket connection is closed
func (p *cryptoTickerPlugin) OnClose(input websocket.OnCloseInput) (websocket.OnCloseOutput, error) {
func (p *cryptoTickerPlugin) OnClose(input websocket.OnCloseRequest) (websocket.OnCloseResponse, error) {
pdk.Log(pdk.LogInfo, fmt.Sprintf("WebSocket connection %s closed (code: %d, reason: %s)",
input.ConnectionID, input.Code, input.Reason))
@ -210,14 +210,14 @@ func (p *cryptoTickerPlugin) OnClose(input websocket.OnCloseInput) (websocket.On
}
}
return websocket.OnCloseOutput{}, nil
return websocket.OnCloseResponse{}, nil
}
// OnSchedulerCallback is called when a scheduled task fires
func (p *cryptoTickerPlugin) OnSchedulerCallback(input scheduler.SchedulerCallbackInput) (scheduler.SchedulerCallbackOutput, error) {
func (p *cryptoTickerPlugin) OnSchedulerCallback(input scheduler.SchedulerCallbackRequest) (scheduler.SchedulerCallbackResponse, error) {
// Only handle our reconnection schedule
if input.ScheduleID != reconnectScheduleID {
return scheduler.SchedulerCallbackOutput{}, nil
return scheduler.SchedulerCallbackResponse{}, nil
}
pdk.Log(pdk.LogInfo, "Attempting to reconnect to Coinbase WebSocket API...")
@ -244,7 +244,7 @@ func (p *cryptoTickerPlugin) OnSchedulerCallback(input scheduler.SchedulerCallba
pdk.Log(pdk.LogInfo, "Successfully reconnected!")
}
return scheduler.SchedulerCallbackOutput{}, nil
return scheduler.SchedulerCallbackResponse{}, nil
}
// calculatePercentChange calculates the percentage change between open and current price

View File

@ -93,40 +93,42 @@ func getImageURL(trackID string) string {
// ============================================================================
// IsAuthorized checks if a user is authorized for Discord Rich Presence.
func (p *discordPlugin) IsAuthorized(input scrobbler.AuthInput) (scrobbler.AuthOutput, error) {
func (p *discordPlugin) IsAuthorized(input scrobbler.IsAuthorizedRequest) (scrobbler.IsAuthorizedResponse, error) {
_, users, err := getConfig()
if err != nil {
return scrobbler.AuthOutput{}, fmt.Errorf("failed to check user authorization: %w", err)
return scrobbler.IsAuthorizedResponse{}, fmt.Errorf("failed to check user authorization: %w", err)
}
_, authorized := users[input.Username]
pdk.Log(pdk.LogInfo, fmt.Sprintf("IsAuthorized for user %s: %v", input.Username, authorized))
return scrobbler.AuthOutput{Authorized: authorized}, nil
return scrobbler.IsAuthorizedResponse{Authorized: authorized}, nil
}
// NowPlaying sends a now playing notification to Discord.
func (p *discordPlugin) NowPlaying(input scrobbler.NowPlayingInput) (scrobbler.ScrobblerOutput, error) {
func (p *discordPlugin) NowPlaying(input scrobbler.NowPlayingRequest) (scrobbler.ScrobblerResponse, error) {
pdk.Log(pdk.LogInfo, fmt.Sprintf("Setting presence for user %s, track: %s", input.Username, input.Track.Title))
// Load configuration
clientID, users, err := getConfig()
if err != nil {
return scrobbler.ScrobblerOutput{}, fmt.Errorf("failed to get config: %w", err)
return scrobbler.ScrobblerResponse{}, fmt.Errorf("failed to get config: %w", err)
}
// Check authorization
userToken, authorized := users[input.Username]
if !authorized {
errMsg := fmt.Sprintf("user '%s' not authorized", input.Username)
errType := scrobbler.ScrobblerErrorNotAuthorized
return scrobbler.ScrobblerOutput{Error: &errMsg, ErrorType: &errType}, nil
return scrobbler.ScrobblerResponse{
Error: fmt.Sprintf("user '%s' not authorized", input.Username),
ErrorType: scrobbler.ScrobblerErrorNotAuthorized,
}, nil
}
// Connect to Discord
if err := connect(input.Username, userToken); err != nil {
errMsg := fmt.Sprintf("failed to connect to Discord: %v", err)
errType := scrobbler.ScrobblerErrorRetryLater
return scrobbler.ScrobblerOutput{Error: &errMsg, ErrorType: &errType}, nil
return scrobbler.ScrobblerResponse{
Error: fmt.Sprintf("failed to connect to Discord: %v", err),
ErrorType: scrobbler.ScrobblerErrorRetryLater,
}, nil
}
// Cancel any existing completion schedule
@ -153,9 +155,10 @@ func (p *discordPlugin) NowPlaying(input scrobbler.NowPlayingInput) (scrobbler.S
LargeText: input.Track.Album,
},
}); err != nil {
errMsg := fmt.Sprintf("failed to send activity: %v", err)
errType := scrobbler.ScrobblerErrorRetryLater
return scrobbler.ScrobblerOutput{Error: &errMsg, ErrorType: &errType}, nil
return scrobbler.ScrobblerResponse{
Error: fmt.Sprintf("failed to send activity: %v", err),
ErrorType: scrobbler.ScrobblerErrorRetryLater,
}, nil
}
// Schedule a timer to clear the activity after the track completes
@ -165,13 +168,13 @@ func (p *discordPlugin) NowPlaying(input scrobbler.NowPlayingInput) (scrobbler.S
pdk.Log(pdk.LogWarn, fmt.Sprintf("Failed to schedule completion timer: %v", err))
}
return scrobbler.ScrobblerOutput{}, nil
return scrobbler.ScrobblerResponse{}, nil
}
// Scrobble handles scrobble requests (no-op for Discord).
func (p *discordPlugin) Scrobble(_ scrobbler.ScrobbleInput) (scrobbler.ScrobblerOutput, error) {
func (p *discordPlugin) Scrobble(_ scrobbler.ScrobbleRequest) (scrobbler.ScrobblerResponse, error) {
// Discord Rich Presence doesn't need scrobble events
return scrobbler.ScrobblerOutput{}, nil
return scrobbler.ScrobblerResponse{}, nil
}
// ============================================================================
@ -179,7 +182,7 @@ func (p *discordPlugin) Scrobble(_ scrobbler.ScrobbleInput) (scrobbler.Scrobbler
// ============================================================================
// OnSchedulerCallback handles scheduler callbacks.
func (p *discordPlugin) OnSchedulerCallback(input scheduler.SchedulerCallbackInput) (scheduler.SchedulerCallbackOutput, error) {
func (p *discordPlugin) OnSchedulerCallback(input scheduler.SchedulerCallbackRequest) (scheduler.SchedulerCallbackResponse, error) {
pdk.Log(pdk.LogDebug, fmt.Sprintf("Scheduler callback: id=%s, payload=%s, recurring=%v", input.ScheduleID, input.Payload, input.IsRecurring))
// Route based on payload
@ -187,23 +190,21 @@ func (p *discordPlugin) OnSchedulerCallback(input scheduler.SchedulerCallbackInp
case payloadHeartbeat:
// Heartbeat callback - scheduleId is the username
if err := handleHeartbeatCallback(input.ScheduleID); err != nil {
errMsg := err.Error()
return scheduler.SchedulerCallbackOutput{Error: &errMsg}, nil
return scheduler.SchedulerCallbackResponse{Error: err.Error()}, nil
}
case payloadClearActivity:
// Clear activity callback - scheduleId is "username-clear"
username := strings.TrimSuffix(input.ScheduleID, "-clear")
if err := handleClearActivityCallback(username); err != nil {
errMsg := err.Error()
return scheduler.SchedulerCallbackOutput{Error: &errMsg}, nil
return scheduler.SchedulerCallbackResponse{Error: err.Error()}, nil
}
default:
pdk.Log(pdk.LogWarn, fmt.Sprintf("Unknown scheduler callback payload: %s", input.Payload))
}
return scheduler.SchedulerCallbackOutput{}, nil
return scheduler.SchedulerCallbackResponse{}, nil
}
// ============================================================================
@ -211,30 +212,29 @@ func (p *discordPlugin) OnSchedulerCallback(input scheduler.SchedulerCallbackInp
// ============================================================================
// OnTextMessage handles incoming WebSocket text messages.
func (p *discordPlugin) OnTextMessage(input websocket.OnTextMessageInput) (websocket.OnTextMessageOutput, error) {
func (p *discordPlugin) OnTextMessage(input websocket.OnTextMessageRequest) (websocket.OnTextMessageResponse, error) {
if err := handleWebSocketMessage(input.ConnectionID, input.Message); err != nil {
errMsg := err.Error()
return websocket.OnTextMessageOutput{Error: &errMsg}, nil
return websocket.OnTextMessageResponse{Error: err.Error()}, nil
}
return websocket.OnTextMessageOutput{}, nil
return websocket.OnTextMessageResponse{}, nil
}
// OnBinaryMessage handles incoming WebSocket binary messages.
func (p *discordPlugin) OnBinaryMessage(input websocket.OnBinaryMessageInput) (websocket.OnBinaryMessageOutput, error) {
func (p *discordPlugin) OnBinaryMessage(input websocket.OnBinaryMessageRequest) (websocket.OnBinaryMessageResponse, error) {
pdk.Log(pdk.LogDebug, fmt.Sprintf("Received unexpected binary message for connection '%s'", input.ConnectionID))
return websocket.OnBinaryMessageOutput{}, nil
return websocket.OnBinaryMessageResponse{}, nil
}
// OnError handles WebSocket errors.
func (p *discordPlugin) OnError(input websocket.OnErrorInput) (websocket.OnErrorOutput, error) {
func (p *discordPlugin) OnError(input websocket.OnErrorRequest) (websocket.OnErrorResponse, error) {
pdk.Log(pdk.LogWarn, fmt.Sprintf("WebSocket error for connection '%s': %s", input.ConnectionID, input.Error))
return websocket.OnErrorOutput{}, nil
return websocket.OnErrorResponse{}, nil
}
// OnClose handles WebSocket connection closure.
func (p *discordPlugin) OnClose(input websocket.OnCloseInput) (websocket.OnCloseOutput, error) {
func (p *discordPlugin) OnClose(input websocket.OnCloseRequest) (websocket.OnCloseResponse, error) {
pdk.Log(pdk.LogInfo, fmt.Sprintf("WebSocket connection '%s' closed with code %d: %s", input.ConnectionID, input.Code, input.Reason))
return websocket.OnCloseOutput{}, nil
return websocket.OnCloseResponse{}, nil
}
func main() {}

View File

@ -23,8 +23,8 @@ func init() {
var _ metadata.ArtistBiographyProvider = (*minimalPlugin)(nil)
// GetArtistBiography returns a placeholder biography for the artist.
func (p *minimalPlugin) GetArtistBiography(input metadata.ArtistInput) (metadata.ArtistBiographyOutput, error) {
return metadata.ArtistBiographyOutput{
func (p *minimalPlugin) GetArtistBiography(input metadata.ArtistRequest) (metadata.ArtistBiographyResponse, error) {
return metadata.ArtistBiographyResponse{
Biography: "This is a placeholder biography for " + input.Name + ".",
}, nil
}

View File

@ -232,23 +232,14 @@ func extractPageTitleFromURL(wikiURL string) (string, error) {
return decodedTitle, nil
}
// getMBID extracts the MBID from an optional pointer
func getMBID(mbid *string) string {
if mbid == nil {
return ""
}
return *mbid
}
// GetArtistURL returns the Wikipedia URL for an artist
func (*wikimediaPlugin) GetArtistURL(input metadata.ArtistInput) (metadata.ArtistURLOutput, error) {
mbid := getMBID(input.MBID)
pdk.Log(pdk.LogDebug, fmt.Sprintf("GetArtistURL: name=%s, mbid=%s", input.Name, mbid))
func (*wikimediaPlugin) GetArtistURL(input metadata.ArtistRequest) (metadata.ArtistURLResponse, error) {
pdk.Log(pdk.LogDebug, fmt.Sprintf("GetArtistURL: name=%s, mbid=%s", input.Name, input.MBID))
// 1. Try Wikidata (MBID first, then name)
wikiURL, err := getWikidataWikipediaURL(mbid, input.Name)
wikiURL, err := getWikidataWikipediaURL(input.MBID, input.Name)
if err == nil && wikiURL != "" {
return metadata.ArtistURLOutput{URL: wikiURL}, nil
return metadata.ArtistURLResponse{URL: wikiURL}, nil
}
if err != nil {
pdk.Log(pdk.LogDebug, fmt.Sprintf("Wikidata URL failed: %v", err))
@ -258,7 +249,7 @@ func (*wikimediaPlugin) GetArtistURL(input metadata.ArtistInput) (metadata.Artis
if input.Name != "" {
wikiURL, err = getDBpediaWikipediaURL(input.Name)
if err == nil && wikiURL != "" {
return metadata.ArtistURLOutput{URL: wikiURL}, nil
return metadata.ArtistURLResponse{URL: wikiURL}, nil
}
if err != nil {
pdk.Log(pdk.LogDebug, fmt.Sprintf("DBpedia URL failed: %v", err))
@ -269,20 +260,19 @@ func (*wikimediaPlugin) GetArtistURL(input metadata.ArtistInput) (metadata.Artis
if input.Name != "" {
searchURL := fmt.Sprintf("https://en.wikipedia.org/w/index.php?search=%s", url.QueryEscape(input.Name))
pdk.Log(pdk.LogInfo, fmt.Sprintf("URL not found, falling back to search URL: %s", searchURL))
return metadata.ArtistURLOutput{URL: searchURL}, nil
return metadata.ArtistURLResponse{URL: searchURL}, nil
}
return metadata.ArtistURLOutput{}, errors.New("could not determine Wikipedia URL")
return metadata.ArtistURLResponse{}, errors.New("could not determine Wikipedia URL")
}
// GetArtistBiography returns the biography for an artist from Wikipedia
func (*wikimediaPlugin) GetArtistBiography(input metadata.ArtistInput) (metadata.ArtistBiographyOutput, error) {
mbid := getMBID(input.MBID)
pdk.Log(pdk.LogDebug, fmt.Sprintf("GetArtistBiography: name=%s, mbid=%s", input.Name, mbid))
func (*wikimediaPlugin) GetArtistBiography(input metadata.ArtistRequest) (metadata.ArtistBiographyResponse, error) {
pdk.Log(pdk.LogDebug, fmt.Sprintf("GetArtistBiography: name=%s, mbid=%s", input.Name, input.MBID))
// 1. Get Wikipedia URL (using the logic from GetArtistURL)
wikiURL := ""
tempURL, wdErr := getWikidataWikipediaURL(mbid, input.Name)
tempURL, wdErr := getWikidataWikipediaURL(input.MBID, input.Name)
if wdErr == nil && tempURL != "" {
pdk.Log(pdk.LogDebug, fmt.Sprintf("Found Wikidata URL: %s", tempURL))
wikiURL = tempURL
@ -305,7 +295,7 @@ func (*wikimediaPlugin) GetArtistBiography(input metadata.ArtistInput) (metadata
bio, err := getWikipediaExtract(pageTitle)
if err == nil && bio != "" {
pdk.Log(pdk.LogDebug, "Found Wikipedia extract")
return metadata.ArtistBiographyOutput{Biography: bio}, nil
return metadata.ArtistBiographyResponse{Biography: bio}, nil
}
pdk.Log(pdk.LogDebug, fmt.Sprintf("Wikipedia extract failed: %v", err))
} else {
@ -319,43 +309,42 @@ func (*wikimediaPlugin) GetArtistBiography(input metadata.ArtistInput) (metadata
bio, err := getDBpediaComment(input.Name)
if err == nil && bio != "" {
pdk.Log(pdk.LogDebug, "Found DBpedia comment")
return metadata.ArtistBiographyOutput{Biography: bio}, nil
return metadata.ArtistBiographyResponse{Biography: bio}, nil
}
pdk.Log(pdk.LogDebug, fmt.Sprintf("DBpedia comment failed: %v", err))
}
pdk.Log(pdk.LogInfo, fmt.Sprintf("Biography not found for: %s (%s)", input.Name, mbid))
return metadata.ArtistBiographyOutput{}, errors.New("biography not found")
pdk.Log(pdk.LogInfo, fmt.Sprintf("Biography not found for: %s (%s)", input.Name, input.MBID))
return metadata.ArtistBiographyResponse{}, errors.New("biography not found")
}
// GetArtistImages returns artist images from Wikidata
func (*wikimediaPlugin) GetArtistImages(input metadata.ArtistInput) (metadata.ArtistImagesOutput, error) {
mbid := getMBID(input.MBID)
pdk.Log(pdk.LogDebug, fmt.Sprintf("GetArtistImages: name=%s, mbid=%s", input.Name, mbid))
func (*wikimediaPlugin) GetArtistImages(input metadata.ArtistRequest) (metadata.ArtistImagesResponse, error) {
pdk.Log(pdk.LogDebug, fmt.Sprintf("GetArtistImages: name=%s, mbid=%s", input.Name, input.MBID))
var q string
if mbid != "" {
q = fmt.Sprintf(`SELECT ?img WHERE { ?artist wdt:P434 "%s"; wdt:P18 ?img } LIMIT 1`, mbid)
if input.MBID != "" {
q = fmt.Sprintf(`SELECT ?img WHERE { ?artist wdt:P434 "%s"; wdt:P18 ?img } LIMIT 1`, input.MBID)
} else if input.Name != "" {
escapedName := strings.ReplaceAll(input.Name, "\"", "\\\"")
q = fmt.Sprintf(`SELECT ?img WHERE { ?artist rdfs:label "%s"@en; wdt:P18 ?img } LIMIT 1`, escapedName)
} else {
return metadata.ArtistImagesOutput{}, errors.New("MBID or Name required for Wikidata Image lookup")
return metadata.ArtistImagesResponse{}, errors.New("MBID or Name required for Wikidata Image lookup")
}
result, err := sparqlQuery(wikidataEndpoint, q)
if err != nil {
pdk.Log(pdk.LogInfo, fmt.Sprintf("Image not found for: %s (%s)", input.Name, mbid))
return metadata.ArtistImagesOutput{}, errors.New("image not found")
pdk.Log(pdk.LogInfo, fmt.Sprintf("Image not found for: %s (%s)", input.Name, input.MBID))
return metadata.ArtistImagesResponse{}, errors.New("image not found")
}
if result.Results.Bindings[0].Img != nil {
return metadata.ArtistImagesOutput{
return metadata.ArtistImagesResponse{
Images: []metadata.ImageInfo{{URL: result.Results.Bindings[0].Img.Value, Size: 0}},
}, nil
}
pdk.Log(pdk.LogInfo, fmt.Sprintf("Image not found for: %s (%s)", input.Name, mbid))
return metadata.ArtistImagesOutput{}, errors.New("image not found")
pdk.Log(pdk.LogInfo, fmt.Sprintf("Image not found for: %s (%s)", input.Name, input.MBID))
return metadata.ArtistImagesResponse{}, errors.New("image not found")
}
// Required main function - init() handles registration

View File

@ -9,6 +9,7 @@ import (
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model/id"
"github.com/navidrome/navidrome/plugins/capabilities"
"github.com/navidrome/navidrome/plugins/host"
"github.com/navidrome/navidrome/scheduler"
)
@ -162,18 +163,6 @@ func (s *schedulerServiceImpl) Close() error {
return nil
}
// schedulerCallbackInput is the input format for the nd_scheduler_callback function.
type schedulerCallbackInput struct {
ScheduleID string `json:"scheduleId"`
Payload string `json:"payload"`
IsRecurring bool `json:"isRecurring"`
}
// schedulerCallbackOutput is the output format for the nd_scheduler_callback function.
type schedulerCallbackOutput struct {
Error string `json:"error,omitempty"`
}
// invokeCallback calls the plugin's nd_scheduler_callback function.
func (s *schedulerServiceImpl) invokeCallback(ctx context.Context, scheduleID string) {
log.Debug(ctx, "Scheduler callback invoked", "plugin", s.pluginName, "scheduleID", scheduleID)
@ -206,14 +195,14 @@ func (s *schedulerServiceImpl) invokeCallback(ctx context.Context, scheduleID st
}
// Prepare callback input
input := schedulerCallbackInput{
input := capabilities.SchedulerCallbackRequest{
ScheduleID: scheduleID,
Payload: payload,
IsRecurring: isRecurring,
}
start := time.Now()
result, err := callPluginFunction[schedulerCallbackInput, schedulerCallbackOutput](ctx, instance, FuncSchedulerCallback, input)
result, err := callPluginFunction[capabilities.SchedulerCallbackRequest, capabilities.SchedulerCallbackResponse](ctx, instance, FuncSchedulerCallback, input)
if err != nil {
log.Error(ctx, "Scheduler callback failed", "plugin", s.pluginName, "scheduleID", scheduleID, "duration", time.Since(start), err)
return

View File

@ -14,6 +14,7 @@ import (
"github.com/gorilla/websocket"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model/id"
"github.com/navidrome/navidrome/plugins/capabilities"
"github.com/navidrome/navidrome/plugins/host"
)
@ -317,44 +318,19 @@ func (s *webSocketServiceImpl) readLoop(ctx context.Context, connectionID string
}
}
// Callback input/output types
type onTextMessageInput struct {
ConnectionID string `json:"connectionId"`
Message string `json:"message"`
}
type onBinaryMessageInput struct {
ConnectionID string `json:"connectionId"`
Data string `json:"data"` // base64 encoded
}
type onErrorInput struct {
ConnectionID string `json:"connectionId"`
Error string `json:"error"`
}
type onCloseInput struct {
ConnectionID string `json:"connectionId"`
Code int32 `json:"code"`
Reason string `json:"reason"`
}
type emptyOutput struct{}
func (s *webSocketServiceImpl) invokeOnTextMessage(ctx context.Context, connectionID, message string) {
instance := s.getPluginInstance()
if instance == nil {
return
}
input := onTextMessageInput{
input := capabilities.OnTextMessageRequest{
ConnectionID: connectionID,
Message: message,
}
start := time.Now()
_, err := callPluginFunction[onTextMessageInput, emptyOutput](ctx, instance, FuncWebSocketOnTextMessage, input)
_, err := callPluginFunction[capabilities.OnTextMessageRequest, capabilities.OnTextMessageResponse](ctx, instance, FuncWebSocketOnTextMessage, input)
if err != nil {
// Don't log error if function simply doesn't exist (optional callback)
if !errors.Is(errFunctionNotFound, err) {
@ -369,13 +345,13 @@ func (s *webSocketServiceImpl) invokeOnBinaryMessage(ctx context.Context, connec
return
}
input := onBinaryMessageInput{
input := capabilities.OnBinaryMessageRequest{
ConnectionID: connectionID,
Data: base64.StdEncoding.EncodeToString(data),
}
start := time.Now()
_, err := callPluginFunction[onBinaryMessageInput, emptyOutput](ctx, instance, FuncWebSocketOnBinaryMessage, input)
_, err := callPluginFunction[capabilities.OnBinaryMessageRequest, capabilities.OnBinaryMessageResponse](ctx, instance, FuncWebSocketOnBinaryMessage, input)
if err != nil {
// Don't log error if function simply doesn't exist (optional callback)
if !errors.Is(errFunctionNotFound, err) {
@ -390,13 +366,13 @@ func (s *webSocketServiceImpl) invokeOnError(ctx context.Context, connectionID,
return
}
input := onErrorInput{
input := capabilities.OnErrorRequest{
ConnectionID: connectionID,
Error: errorMsg,
}
start := time.Now()
_, err := callPluginFunction[onErrorInput, emptyOutput](ctx, instance, FuncWebSocketOnError, input)
_, err := callPluginFunction[capabilities.OnErrorRequest, capabilities.OnErrorResponse](ctx, instance, FuncWebSocketOnError, input)
if err != nil {
// Don't log error if function simply doesn't exist (optional callback)
if !errors.Is(errFunctionNotFound, err) {
@ -411,14 +387,14 @@ func (s *webSocketServiceImpl) invokeOnClose(ctx context.Context, connectionID s
return
}
input := onCloseInput{
input := capabilities.OnCloseRequest{
ConnectionID: connectionID,
Code: code,
Reason: reason,
}
start := time.Now()
_, err := callPluginFunction[onCloseInput, emptyOutput](ctx, instance, FuncWebSocketOnClose, input)
_, err := callPluginFunction[capabilities.OnCloseRequest, capabilities.OnCloseResponse](ctx, instance, FuncWebSocketOnClose, input)
if err != nil {
// Don't log error if function simply doesn't exist (optional callback)
if !errors.Is(errFunctionNotFound, err) {

View File

@ -5,6 +5,7 @@ import (
"errors"
"github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/plugins/capabilities"
)
// CapabilityMetadataAgent indicates the plugin can provide artist/album metadata.
@ -53,8 +54,8 @@ func (a *MetadataAgent) AgentName() string {
// GetArtistMBID retrieves the MusicBrainz ID for an artist
func (a *MetadataAgent) GetArtistMBID(ctx context.Context, id string, name string) (string, error) {
input := artistMBIDInput{ID: id, Name: name}
result, err := callPluginFunction[artistMBIDInput, artistMBIDOutput](ctx, a.plugin, FuncGetArtistMBID, input)
input := capabilities.ArtistMBIDRequest{ID: id, Name: name}
result, err := callPluginFunction[capabilities.ArtistMBIDRequest, capabilities.ArtistMBIDResponse](ctx, a.plugin, FuncGetArtistMBID, input)
if err != nil {
return "", errors.Join(agents.ErrNotFound, err)
}
@ -68,8 +69,8 @@ func (a *MetadataAgent) GetArtistMBID(ctx context.Context, id string, name strin
// GetArtistURL retrieves the external URL for an artist
func (a *MetadataAgent) GetArtistURL(ctx context.Context, id, name, mbid string) (string, error) {
input := artistInput{ID: id, Name: name, MBID: mbid}
result, err := callPluginFunction[artistInput, artistURLOutput](ctx, a.plugin, FuncGetArtistURL, input)
input := capabilities.ArtistRequest{ID: id, Name: name, MBID: mbid}
result, err := callPluginFunction[capabilities.ArtistRequest, capabilities.ArtistURLResponse](ctx, a.plugin, FuncGetArtistURL, input)
if err != nil {
return "", errors.Join(agents.ErrNotFound, err)
}
@ -81,8 +82,8 @@ func (a *MetadataAgent) GetArtistURL(ctx context.Context, id, name, mbid string)
// GetArtistBiography retrieves the biography for an artist
func (a *MetadataAgent) GetArtistBiography(ctx context.Context, id, name, mbid string) (string, error) {
input := artistInput{ID: id, Name: name, MBID: mbid}
result, err := callPluginFunction[artistInput, artistBiographyOutput](ctx, a.plugin, FuncGetArtistBiography, input)
input := capabilities.ArtistRequest{ID: id, Name: name, MBID: mbid}
result, err := callPluginFunction[capabilities.ArtistRequest, capabilities.ArtistBiographyResponse](ctx, a.plugin, FuncGetArtistBiography, input)
if err != nil {
return "", errors.Join(agents.ErrNotFound, err)
}
@ -96,8 +97,8 @@ func (a *MetadataAgent) GetArtistBiography(ctx context.Context, id, name, mbid s
// GetSimilarArtists retrieves similar artists
func (a *MetadataAgent) GetSimilarArtists(ctx context.Context, id, name, mbid string, limit int) ([]agents.Artist, error) {
input := similarArtistsInput{ID: id, Name: name, MBID: mbid, Limit: limit}
result, err := callPluginFunction[similarArtistsInput, similarArtistsOutput](ctx, a.plugin, FuncGetSimilarArtists, input)
input := capabilities.SimilarArtistsRequest{ID: id, Name: name, MBID: mbid, Limit: int32(limit)}
result, err := callPluginFunction[capabilities.SimilarArtistsRequest, capabilities.SimilarArtistsResponse](ctx, a.plugin, FuncGetSimilarArtists, input)
if err != nil {
return nil, errors.Join(agents.ErrNotFound, err)
}
@ -107,8 +108,8 @@ func (a *MetadataAgent) GetSimilarArtists(ctx context.Context, id, name, mbid st
}
artists := make([]agents.Artist, len(result.Artists))
for i, a := range result.Artists {
artists[i] = agents.Artist{Name: a.Name, MBID: a.MBID}
for i, ar := range result.Artists {
artists[i] = agents.Artist{Name: ar.Name, MBID: ar.MBID}
}
return artists, nil
@ -116,8 +117,8 @@ func (a *MetadataAgent) GetSimilarArtists(ctx context.Context, id, name, mbid st
// GetArtistImages retrieves images for an artist
func (a *MetadataAgent) GetArtistImages(ctx context.Context, id, name, mbid string) ([]agents.ExternalImage, error) {
input := artistInput{ID: id, Name: name, MBID: mbid}
result, err := callPluginFunction[artistInput, artistImagesOutput](ctx, a.plugin, FuncGetArtistImages, input)
input := capabilities.ArtistRequest{ID: id, Name: name, MBID: mbid}
result, err := callPluginFunction[capabilities.ArtistRequest, capabilities.ArtistImagesResponse](ctx, a.plugin, FuncGetArtistImages, input)
if err != nil {
return nil, errors.Join(agents.ErrNotFound, err)
}
@ -128,7 +129,7 @@ func (a *MetadataAgent) GetArtistImages(ctx context.Context, id, name, mbid stri
images := make([]agents.ExternalImage, len(result.Images))
for i, img := range result.Images {
images[i] = agents.ExternalImage{URL: img.URL, Size: img.Size}
images[i] = agents.ExternalImage{URL: img.URL, Size: int(img.Size)}
}
return images, nil
@ -136,8 +137,8 @@ func (a *MetadataAgent) GetArtistImages(ctx context.Context, id, name, mbid stri
// GetArtistTopSongs retrieves top songs for an artist
func (a *MetadataAgent) GetArtistTopSongs(ctx context.Context, id, artistName, mbid string, count int) ([]agents.Song, error) {
input := topSongsInput{ID: id, Name: artistName, MBID: mbid, Count: count}
result, err := callPluginFunction[topSongsInput, topSongsOutput](ctx, a.plugin, FuncGetArtistTopSongs, input)
input := capabilities.TopSongsRequest{ID: id, Name: artistName, MBID: mbid, Count: int32(count)}
result, err := callPluginFunction[capabilities.TopSongsRequest, capabilities.TopSongsResponse](ctx, a.plugin, FuncGetArtistTopSongs, input)
if err != nil {
return nil, errors.Join(agents.ErrNotFound, err)
}
@ -156,8 +157,8 @@ func (a *MetadataAgent) GetArtistTopSongs(ctx context.Context, id, artistName, m
// GetAlbumInfo retrieves album information
func (a *MetadataAgent) GetAlbumInfo(ctx context.Context, name, artist, mbid string) (*agents.AlbumInfo, error) {
input := albumInput{Name: name, Artist: artist, MBID: mbid}
result, err := callPluginFunction[albumInput, albumInfoOutput](ctx, a.plugin, FuncGetAlbumInfo, input)
input := capabilities.AlbumRequest{Name: name, Artist: artist, MBID: mbid}
result, err := callPluginFunction[capabilities.AlbumRequest, capabilities.AlbumInfoResponse](ctx, a.plugin, FuncGetAlbumInfo, input)
if err != nil {
return nil, errors.Join(agents.ErrNotFound, err)
}
@ -172,8 +173,8 @@ func (a *MetadataAgent) GetAlbumInfo(ctx context.Context, name, artist, mbid str
// GetAlbumImages retrieves images for an album
func (a *MetadataAgent) GetAlbumImages(ctx context.Context, name, artist, mbid string) ([]agents.ExternalImage, error) {
input := albumInput{Name: name, Artist: artist, MBID: mbid}
result, err := callPluginFunction[albumInput, albumImagesOutput](ctx, a.plugin, FuncGetAlbumImages, input)
input := capabilities.AlbumRequest{Name: name, Artist: artist, MBID: mbid}
result, err := callPluginFunction[capabilities.AlbumRequest, capabilities.AlbumImagesResponse](ctx, a.plugin, FuncGetAlbumImages, input)
if err != nil {
return nil, errors.Join(agents.ErrNotFound, err)
}
@ -184,7 +185,7 @@ func (a *MetadataAgent) GetAlbumImages(ctx context.Context, name, artist, mbid s
images := make([]agents.ExternalImage, len(result.Images))
for i, img := range result.Images {
images[i] = agents.ExternalImage{URL: img.URL, Size: img.Size}
images[i] = agents.ExternalImage{URL: img.URL, Size: int(img.Size)}
}
return images, nil

View File

@ -1,100 +0,0 @@
package plugins
// --- Input/Output JSON structures for MetadataAgent plugin calls ---
// artistMBIDInput is the input for GetArtistMBID
type artistMBIDInput struct {
ID string `json:"id"`
Name string `json:"name"`
}
// artistMBIDOutput is the output for GetArtistMBID
type artistMBIDOutput struct {
MBID string `json:"mbid"`
}
// artistInput is the common input for artist-related functions
type artistInput struct {
ID string `json:"id"`
Name string `json:"name"`
MBID string `json:"mbid,omitempty"`
}
// artistURLOutput is the output for GetArtistURL
type artistURLOutput struct {
URL string `json:"url"`
}
// artistBiographyOutput is the output for GetArtistBiography
type artistBiographyOutput struct {
Biography string `json:"biography"`
}
// similarArtistsInput is the input for GetSimilarArtists
type similarArtistsInput struct {
ID string `json:"id"`
Name string `json:"name"`
MBID string `json:"mbid,omitempty"`
Limit int `json:"limit"`
}
// artistRef is a reference to an artist with name and optional MBID
type artistRef struct {
Name string `json:"name"`
MBID string `json:"mbid,omitempty"`
}
// similarArtistsOutput is the output for GetSimilarArtists
type similarArtistsOutput struct {
Artists []artistRef `json:"artists"`
}
// imageInfo represents an image with URL and size
type imageInfo struct {
URL string `json:"url"`
Size int `json:"size"`
}
// artistImagesOutput is the output for GetArtistImages
type artistImagesOutput struct {
Images []imageInfo `json:"images"`
}
// topSongsInput is the input for GetArtistTopSongs
type topSongsInput struct {
ID string `json:"id"`
Name string `json:"name"`
MBID string `json:"mbid,omitempty"`
Count int `json:"count"`
}
// songRef is a reference to a song with name and optional MBID
type songRef struct {
Name string `json:"name"`
MBID string `json:"mbid,omitempty"`
}
// topSongsOutput is the output for GetArtistTopSongs
type topSongsOutput struct {
Songs []songRef `json:"songs"`
}
// albumInput is the common input for album-related functions
type albumInput struct {
Name string `json:"name"`
Artist string `json:"artist"`
MBID string `json:"mbid,omitempty"`
}
// albumInfoOutput is the output for GetAlbumInfo
type albumInfoOutput struct {
Name string `json:"name"`
MBID string `json:"mbid"`
Description string `json:"description"`
URL string `json:"url"`
}
// albumImagesOutput is the output for GetAlbumImages
type albumImagesOutput struct {
Images []imageInfo `json:"images"`
}

View File

@ -11,17 +11,17 @@ import (
pdk "github.com/extism/go-pdk"
)
// OnInitInput is the input provided to the init callback.
// InitRequest is the request provided to the init callback.
// Currently empty, reserved for future use.
type OnInitInput struct {
type InitRequest struct {
}
// OnInitOutput is the output from the init callback.
type OnInitOutput struct {
// InitResponse is the response from the init callback.
type InitResponse struct {
// Error is the error message if initialization failed.
// Empty or null indicates success.
// Empty string indicates success.
// The error is logged but does not prevent the plugin from being loaded.
Error *string `json:"error,omitempty"`
Error string `json:"error,omitempty"`
}
// Lifecycle is the marker interface for lifecycle plugins.
@ -38,10 +38,10 @@ type Lifecycle interface{}
// InitProvider provides the OnInit function.
type InitProvider interface {
OnInit(OnInitInput) (OnInitOutput, error)
OnInit(InitRequest) (InitResponse, error)
} // Internal implementation holders
var (
initImpl func(OnInitInput) (OnInitOutput, error)
initImpl func(InitRequest) (InitResponse, error)
)
// Register registers a lifecycle implementation.
@ -63,7 +63,7 @@ func _NdOnInit() int32 {
return NotImplementedCode
}
var input OnInitInput
var input InitRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1

View File

@ -8,17 +8,17 @@
package lifecycle
// OnInitInput is the input provided to the init callback.
// InitRequest is the request provided to the init callback.
// Currently empty, reserved for future use.
type OnInitInput struct {
type InitRequest struct {
}
// OnInitOutput is the output from the init callback.
type OnInitOutput struct {
// InitResponse is the response from the init callback.
type InitResponse struct {
// Error is the error message if initialization failed.
// Empty or null indicates success.
// Empty string indicates success.
// The error is logged but does not prevent the plugin from being loaded.
Error *string `json:"error,omitempty"`
Error string `json:"error,omitempty"`
}
// Lifecycle is the marker interface for lifecycle plugins.
@ -35,7 +35,7 @@ type Lifecycle interface{}
// InitProvider provides the OnInit function.
type InitProvider interface {
OnInit(OnInitInput) (OnInitOutput, error)
OnInit(InitRequest) (InitResponse, error)
}
// NotImplementedCode is the standard return code for unimplemented functions.

View File

@ -11,22 +11,24 @@ import (
pdk "github.com/extism/go-pdk"
)
// ArtistMBIDInput is the input for GetArtistMBID.
type ArtistMBIDInput struct {
// ArtistRequest is the common request for artist-related functions.
type ArtistRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID string `json:"mbid,omitempty"`
}
// ArtistURLOutput is the output for GetArtistURL.
type ArtistURLOutput struct {
// URL is the external URL for the artist.
URL string `json:"url"`
// ArtistBiographyResponse is the response for GetArtistBiography.
type ArtistBiographyResponse struct {
// Biography is the artist biography text.
Biography string `json:"biography"`
}
// AlbumInfoOutput is the output for GetAlbumInfo.
type AlbumInfoOutput struct {
// AlbumInfoResponse is the response for GetAlbumInfo.
type AlbumInfoResponse struct {
// Name is the album name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the album.
@ -37,10 +39,38 @@ type AlbumInfoOutput struct {
URL string `json:"url"`
}
// AlbumImagesOutput is the output for GetAlbumImages.
type AlbumImagesOutput struct {
// Images is the list of album images.
Images []ImageInfo `json:"images"`
// ArtistMBIDRequest is the request for GetArtistMBID.
type ArtistMBIDRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
}
// TopSongsResponse is the response for GetArtistTopSongs.
type TopSongsResponse struct {
// Songs is the list of top songs.
Songs []SongRef `json:"songs"`
}
// ArtistRef is a reference to an artist with name and optional MBID.
type ArtistRef struct {
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist.
MBID string `json:"mbid,omitempty"`
}
// ArtistMBIDResponse is the response for GetArtistMBID.
type ArtistMBIDResponse struct {
// MBID is the MusicBrainz ID for the artist.
MBID string `json:"mbid"`
}
// SimilarArtistsResponse is the response for GetSimilarArtists.
type SimilarArtistsResponse struct {
// Artists is the list of similar artists.
Artists []ArtistRef `json:"artists"`
}
// SongRef is a reference to a song with name and optional MBID.
@ -48,7 +78,7 @@ type SongRef struct {
// Name is the song name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the song.
MBID *string `json:"mbid,omitempty"`
MBID string `json:"mbid,omitempty"`
}
// ImageInfo represents an image with URL and size.
@ -59,86 +89,56 @@ type ImageInfo struct {
Size int32 `json:"size"`
}
// ArtistBiographyOutput is the output for GetArtistBiography.
type ArtistBiographyOutput struct {
// Biography is the artist biography text.
Biography string `json:"biography"`
// ArtistURLResponse is the response for GetArtistURL.
type ArtistURLResponse struct {
// URL is the external URL for the artist.
URL string `json:"url"`
}
// SimilarArtistsOutput is the output for GetSimilarArtists.
type SimilarArtistsOutput struct {
// Artists is the list of similar artists.
Artists []ArtistRef `json:"artists"`
}
// TopSongsInput is the input for GetArtistTopSongs.
type TopSongsInput struct {
// SimilarArtistsRequest is the request for GetSimilarArtists.
type SimilarArtistsRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID *string `json:"mbid,omitempty"`
// Count is the maximum number of top songs to return.
Count int32 `json:"count"`
}
// TopSongsOutput is the output for GetArtistTopSongs.
type TopSongsOutput struct {
// Songs is the list of top songs.
Songs []SongRef `json:"songs"`
}
// ArtistRef is a reference to an artist with name and optional MBID.
type ArtistRef struct {
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist.
MBID *string `json:"mbid,omitempty"`
}
// ArtistImagesOutput is the output for GetArtistImages.
type ArtistImagesOutput struct {
// Images is the list of artist images.
Images []ImageInfo `json:"images"`
}
// ArtistMBIDOutput is the output for GetArtistMBID.
type ArtistMBIDOutput struct {
// MBID is the MusicBrainz ID for the artist.
MBID string `json:"mbid"`
}
// ArtistInput is the common input for artist-related functions.
type ArtistInput struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID *string `json:"mbid,omitempty"`
}
// SimilarArtistsInput is the input for GetSimilarArtists.
type SimilarArtistsInput struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID *string `json:"mbid,omitempty"`
MBID string `json:"mbid,omitempty"`
// Limit is the maximum number of similar artists to return.
Limit int32 `json:"limit"`
}
// AlbumInput is the common input for album-related functions.
type AlbumInput struct {
// ArtistImagesResponse is the response for GetArtistImages.
type ArtistImagesResponse struct {
// Images is the list of artist images.
Images []ImageInfo `json:"images"`
}
// TopSongsRequest is the request for GetArtistTopSongs.
type TopSongsRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID string `json:"mbid,omitempty"`
// Count is the maximum number of top songs to return.
Count int32 `json:"count"`
}
// AlbumRequest is the common request for album-related functions.
type AlbumRequest struct {
// Name is the album name.
Name string `json:"name"`
// Artist is the album artist name.
Artist string `json:"artist"`
// MBID is the MusicBrainz ID for the album (if known).
MBID *string `json:"mbid,omitempty"`
MBID string `json:"mbid,omitempty"`
}
// AlbumImagesResponse is the response for GetAlbumImages.
type AlbumImagesResponse struct {
// Images is the list of album images.
Images []ImageInfo `json:"images"`
}
// Metadata is the marker interface for metadata plugins.
@ -153,52 +153,52 @@ type Metadata interface{}
// ArtistMBIDProvider provides the GetArtistMBID function.
type ArtistMBIDProvider interface {
GetArtistMBID(ArtistMBIDInput) (ArtistMBIDOutput, error)
GetArtistMBID(ArtistMBIDRequest) (ArtistMBIDResponse, error)
}
// ArtistURLProvider provides the GetArtistURL function.
type ArtistURLProvider interface {
GetArtistURL(ArtistInput) (ArtistURLOutput, error)
GetArtistURL(ArtistRequest) (ArtistURLResponse, error)
}
// ArtistBiographyProvider provides the GetArtistBiography function.
type ArtistBiographyProvider interface {
GetArtistBiography(ArtistInput) (ArtistBiographyOutput, error)
GetArtistBiography(ArtistRequest) (ArtistBiographyResponse, error)
}
// SimilarArtistsProvider provides the GetSimilarArtists function.
type SimilarArtistsProvider interface {
GetSimilarArtists(SimilarArtistsInput) (SimilarArtistsOutput, error)
GetSimilarArtists(SimilarArtistsRequest) (SimilarArtistsResponse, error)
}
// ArtistImagesProvider provides the GetArtistImages function.
type ArtistImagesProvider interface {
GetArtistImages(ArtistInput) (ArtistImagesOutput, error)
GetArtistImages(ArtistRequest) (ArtistImagesResponse, error)
}
// ArtistTopSongsProvider provides the GetArtistTopSongs function.
type ArtistTopSongsProvider interface {
GetArtistTopSongs(TopSongsInput) (TopSongsOutput, error)
GetArtistTopSongs(TopSongsRequest) (TopSongsResponse, error)
}
// AlbumInfoProvider provides the GetAlbumInfo function.
type AlbumInfoProvider interface {
GetAlbumInfo(AlbumInput) (AlbumInfoOutput, error)
GetAlbumInfo(AlbumRequest) (AlbumInfoResponse, error)
}
// AlbumImagesProvider provides the GetAlbumImages function.
type AlbumImagesProvider interface {
GetAlbumImages(AlbumInput) (AlbumImagesOutput, error)
GetAlbumImages(AlbumRequest) (AlbumImagesResponse, error)
} // Internal implementation holders
var (
artistMBIDImpl func(ArtistMBIDInput) (ArtistMBIDOutput, error)
artistURLImpl func(ArtistInput) (ArtistURLOutput, error)
artistBiographyImpl func(ArtistInput) (ArtistBiographyOutput, error)
similarArtistsImpl func(SimilarArtistsInput) (SimilarArtistsOutput, error)
artistImagesImpl func(ArtistInput) (ArtistImagesOutput, error)
artistTopSongsImpl func(TopSongsInput) (TopSongsOutput, error)
albumInfoImpl func(AlbumInput) (AlbumInfoOutput, error)
albumImagesImpl func(AlbumInput) (AlbumImagesOutput, error)
artistMBIDImpl func(ArtistMBIDRequest) (ArtistMBIDResponse, error)
artistURLImpl func(ArtistRequest) (ArtistURLResponse, error)
artistBiographyImpl func(ArtistRequest) (ArtistBiographyResponse, error)
similarArtistsImpl func(SimilarArtistsRequest) (SimilarArtistsResponse, error)
artistImagesImpl func(ArtistRequest) (ArtistImagesResponse, error)
artistTopSongsImpl func(TopSongsRequest) (TopSongsResponse, error)
albumInfoImpl func(AlbumRequest) (AlbumInfoResponse, error)
albumImagesImpl func(AlbumRequest) (AlbumImagesResponse, error)
)
// Register registers a metadata implementation.
@ -241,7 +241,7 @@ func _NdGetArtistMbid() int32 {
return NotImplementedCode
}
var input ArtistMBIDInput
var input ArtistMBIDRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
@ -268,7 +268,7 @@ func _NdGetArtistUrl() int32 {
return NotImplementedCode
}
var input ArtistInput
var input ArtistRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
@ -295,7 +295,7 @@ func _NdGetArtistBiography() int32 {
return NotImplementedCode
}
var input ArtistInput
var input ArtistRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
@ -322,7 +322,7 @@ func _NdGetSimilarArtists() int32 {
return NotImplementedCode
}
var input SimilarArtistsInput
var input SimilarArtistsRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
@ -349,7 +349,7 @@ func _NdGetArtistImages() int32 {
return NotImplementedCode
}
var input ArtistInput
var input ArtistRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
@ -376,7 +376,7 @@ func _NdGetArtistTopSongs() int32 {
return NotImplementedCode
}
var input TopSongsInput
var input TopSongsRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
@ -403,7 +403,7 @@ func _NdGetAlbumInfo() int32 {
return NotImplementedCode
}
var input AlbumInput
var input AlbumRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
@ -430,7 +430,7 @@ func _NdGetAlbumImages() int32 {
return NotImplementedCode
}
var input AlbumInput
var input AlbumRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1

View File

@ -8,22 +8,24 @@
package metadata
// ArtistMBIDInput is the input for GetArtistMBID.
type ArtistMBIDInput struct {
// ArtistRequest is the common request for artist-related functions.
type ArtistRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID string `json:"mbid,omitempty"`
}
// ArtistURLOutput is the output for GetArtistURL.
type ArtistURLOutput struct {
// URL is the external URL for the artist.
URL string `json:"url"`
// ArtistBiographyResponse is the response for GetArtistBiography.
type ArtistBiographyResponse struct {
// Biography is the artist biography text.
Biography string `json:"biography"`
}
// AlbumInfoOutput is the output for GetAlbumInfo.
type AlbumInfoOutput struct {
// AlbumInfoResponse is the response for GetAlbumInfo.
type AlbumInfoResponse struct {
// Name is the album name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the album.
@ -34,10 +36,38 @@ type AlbumInfoOutput struct {
URL string `json:"url"`
}
// AlbumImagesOutput is the output for GetAlbumImages.
type AlbumImagesOutput struct {
// Images is the list of album images.
Images []ImageInfo `json:"images"`
// ArtistMBIDRequest is the request for GetArtistMBID.
type ArtistMBIDRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
}
// TopSongsResponse is the response for GetArtistTopSongs.
type TopSongsResponse struct {
// Songs is the list of top songs.
Songs []SongRef `json:"songs"`
}
// ArtistRef is a reference to an artist with name and optional MBID.
type ArtistRef struct {
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist.
MBID string `json:"mbid,omitempty"`
}
// ArtistMBIDResponse is the response for GetArtistMBID.
type ArtistMBIDResponse struct {
// MBID is the MusicBrainz ID for the artist.
MBID string `json:"mbid"`
}
// SimilarArtistsResponse is the response for GetSimilarArtists.
type SimilarArtistsResponse struct {
// Artists is the list of similar artists.
Artists []ArtistRef `json:"artists"`
}
// SongRef is a reference to a song with name and optional MBID.
@ -45,7 +75,7 @@ type SongRef struct {
// Name is the song name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the song.
MBID *string `json:"mbid,omitempty"`
MBID string `json:"mbid,omitempty"`
}
// ImageInfo represents an image with URL and size.
@ -56,86 +86,56 @@ type ImageInfo struct {
Size int32 `json:"size"`
}
// ArtistBiographyOutput is the output for GetArtistBiography.
type ArtistBiographyOutput struct {
// Biography is the artist biography text.
Biography string `json:"biography"`
// ArtistURLResponse is the response for GetArtistURL.
type ArtistURLResponse struct {
// URL is the external URL for the artist.
URL string `json:"url"`
}
// SimilarArtistsOutput is the output for GetSimilarArtists.
type SimilarArtistsOutput struct {
// Artists is the list of similar artists.
Artists []ArtistRef `json:"artists"`
}
// TopSongsInput is the input for GetArtistTopSongs.
type TopSongsInput struct {
// SimilarArtistsRequest is the request for GetSimilarArtists.
type SimilarArtistsRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID *string `json:"mbid,omitempty"`
// Count is the maximum number of top songs to return.
Count int32 `json:"count"`
}
// TopSongsOutput is the output for GetArtistTopSongs.
type TopSongsOutput struct {
// Songs is the list of top songs.
Songs []SongRef `json:"songs"`
}
// ArtistRef is a reference to an artist with name and optional MBID.
type ArtistRef struct {
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist.
MBID *string `json:"mbid,omitempty"`
}
// ArtistImagesOutput is the output for GetArtistImages.
type ArtistImagesOutput struct {
// Images is the list of artist images.
Images []ImageInfo `json:"images"`
}
// ArtistMBIDOutput is the output for GetArtistMBID.
type ArtistMBIDOutput struct {
// MBID is the MusicBrainz ID for the artist.
MBID string `json:"mbid"`
}
// ArtistInput is the common input for artist-related functions.
type ArtistInput struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID *string `json:"mbid,omitempty"`
}
// SimilarArtistsInput is the input for GetSimilarArtists.
type SimilarArtistsInput struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID *string `json:"mbid,omitempty"`
MBID string `json:"mbid,omitempty"`
// Limit is the maximum number of similar artists to return.
Limit int32 `json:"limit"`
}
// AlbumInput is the common input for album-related functions.
type AlbumInput struct {
// ArtistImagesResponse is the response for GetArtistImages.
type ArtistImagesResponse struct {
// Images is the list of artist images.
Images []ImageInfo `json:"images"`
}
// TopSongsRequest is the request for GetArtistTopSongs.
type TopSongsRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID string `json:"mbid,omitempty"`
// Count is the maximum number of top songs to return.
Count int32 `json:"count"`
}
// AlbumRequest is the common request for album-related functions.
type AlbumRequest struct {
// Name is the album name.
Name string `json:"name"`
// Artist is the album artist name.
Artist string `json:"artist"`
// MBID is the MusicBrainz ID for the album (if known).
MBID *string `json:"mbid,omitempty"`
MBID string `json:"mbid,omitempty"`
}
// AlbumImagesResponse is the response for GetAlbumImages.
type AlbumImagesResponse struct {
// Images is the list of album images.
Images []ImageInfo `json:"images"`
}
// Metadata is the marker interface for metadata plugins.
@ -150,42 +150,42 @@ type Metadata interface{}
// ArtistMBIDProvider provides the GetArtistMBID function.
type ArtistMBIDProvider interface {
GetArtistMBID(ArtistMBIDInput) (ArtistMBIDOutput, error)
GetArtistMBID(ArtistMBIDRequest) (ArtistMBIDResponse, error)
}
// ArtistURLProvider provides the GetArtistURL function.
type ArtistURLProvider interface {
GetArtistURL(ArtistInput) (ArtistURLOutput, error)
GetArtistURL(ArtistRequest) (ArtistURLResponse, error)
}
// ArtistBiographyProvider provides the GetArtistBiography function.
type ArtistBiographyProvider interface {
GetArtistBiography(ArtistInput) (ArtistBiographyOutput, error)
GetArtistBiography(ArtistRequest) (ArtistBiographyResponse, error)
}
// SimilarArtistsProvider provides the GetSimilarArtists function.
type SimilarArtistsProvider interface {
GetSimilarArtists(SimilarArtistsInput) (SimilarArtistsOutput, error)
GetSimilarArtists(SimilarArtistsRequest) (SimilarArtistsResponse, error)
}
// ArtistImagesProvider provides the GetArtistImages function.
type ArtistImagesProvider interface {
GetArtistImages(ArtistInput) (ArtistImagesOutput, error)
GetArtistImages(ArtistRequest) (ArtistImagesResponse, error)
}
// ArtistTopSongsProvider provides the GetArtistTopSongs function.
type ArtistTopSongsProvider interface {
GetArtistTopSongs(TopSongsInput) (TopSongsOutput, error)
GetArtistTopSongs(TopSongsRequest) (TopSongsResponse, error)
}
// AlbumInfoProvider provides the GetAlbumInfo function.
type AlbumInfoProvider interface {
GetAlbumInfo(AlbumInput) (AlbumInfoOutput, error)
GetAlbumInfo(AlbumRequest) (AlbumInfoResponse, error)
}
// AlbumImagesProvider provides the GetAlbumImages function.
type AlbumImagesProvider interface {
GetAlbumImages(AlbumInput) (AlbumImagesOutput, error)
GetAlbumImages(AlbumRequest) (AlbumImagesResponse, error)
}
// NotImplementedCode is the standard return code for unimplemented functions.

View File

@ -11,8 +11,8 @@ import (
pdk "github.com/extism/go-pdk"
)
// SchedulerCallbackInput is the input provided when a scheduled task fires.
type SchedulerCallbackInput struct {
// SchedulerCallbackRequest is the request provided when a scheduled task fires.
type SchedulerCallbackRequest struct {
// ScheduleID is the unique identifier for this scheduled task.
// This is either the ID provided when scheduling, or an auto-generated UUID if none was specified.
ScheduleID string `json:"scheduleId"`
@ -24,12 +24,12 @@ type SchedulerCallbackInput struct {
IsRecurring bool `json:"isRecurring"`
}
// SchedulerCallbackOutput is the output from the scheduler callback.
type SchedulerCallbackOutput struct {
// SchedulerCallbackResponse is the response from the scheduler callback.
type SchedulerCallbackResponse struct {
// Error is the error message if the callback failed to process the scheduled task.
// Empty or null indicates success. The error is logged but does not
// Empty string indicates success. The error is logged but does not
// affect the scheduling system.
Error *string `json:"error,omitempty"`
Error string `json:"error,omitempty"`
}
// Scheduler is the marker interface for scheduler plugins.
@ -42,10 +42,10 @@ type Scheduler interface{}
// SchedulerCallbackProvider provides the OnSchedulerCallback function.
type SchedulerCallbackProvider interface {
OnSchedulerCallback(SchedulerCallbackInput) (SchedulerCallbackOutput, error)
OnSchedulerCallback(SchedulerCallbackRequest) (SchedulerCallbackResponse, error)
} // Internal implementation holders
var (
schedulerCallbackImpl func(SchedulerCallbackInput) (SchedulerCallbackOutput, error)
schedulerCallbackImpl func(SchedulerCallbackRequest) (SchedulerCallbackResponse, error)
)
// Register registers a scheduler implementation.
@ -67,7 +67,7 @@ func _NdSchedulerCallback() int32 {
return NotImplementedCode
}
var input SchedulerCallbackInput
var input SchedulerCallbackRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1

View File

@ -8,8 +8,8 @@
package scheduler
// SchedulerCallbackInput is the input provided when a scheduled task fires.
type SchedulerCallbackInput struct {
// SchedulerCallbackRequest is the request provided when a scheduled task fires.
type SchedulerCallbackRequest struct {
// ScheduleID is the unique identifier for this scheduled task.
// This is either the ID provided when scheduling, or an auto-generated UUID if none was specified.
ScheduleID string `json:"scheduleId"`
@ -21,12 +21,12 @@ type SchedulerCallbackInput struct {
IsRecurring bool `json:"isRecurring"`
}
// SchedulerCallbackOutput is the output from the scheduler callback.
type SchedulerCallbackOutput struct {
// SchedulerCallbackResponse is the response from the scheduler callback.
type SchedulerCallbackResponse struct {
// Error is the error message if the callback failed to process the scheduled task.
// Empty or null indicates success. The error is logged but does not
// Empty string indicates success. The error is logged but does not
// affect the scheduling system.
Error *string `json:"error,omitempty"`
Error string `json:"error,omitempty"`
}
// Scheduler is the marker interface for scheduler plugins.
@ -39,7 +39,7 @@ type Scheduler interface{}
// SchedulerCallbackProvider provides the OnSchedulerCallback function.
type SchedulerCallbackProvider interface {
OnSchedulerCallback(SchedulerCallbackInput) (SchedulerCallbackOutput, error)
OnSchedulerCallback(SchedulerCallbackRequest) (SchedulerCallbackResponse, error)
}
// NotImplementedCode is the standard return code for unimplemented functions.

View File

@ -25,8 +25,22 @@ const (
ScrobblerErrorUnrecoverable ScrobblerErrorType = "unrecoverable"
)
// NowPlayingInput is the input for now playing notification.
type NowPlayingInput struct {
// IsAuthorizedRequest is the request for authorization check.
type IsAuthorizedRequest struct {
// UserID is the internal Navidrome user ID.
UserID string `json:"userId"`
// Username is the username of the user.
Username string `json:"username"`
}
// IsAuthorizedResponse is the response for authorization check.
type IsAuthorizedResponse struct {
// Authorized indicates whether the user is authorized to scrobble.
Authorized bool `json:"authorized"`
}
// NowPlayingRequest is the request for now playing notification.
type NowPlayingRequest struct {
// UserID is the internal Navidrome user ID.
UserID string `json:"userId"`
// Username is the username of the user.
@ -37,16 +51,16 @@ type NowPlayingInput struct {
Position int32 `json:"position"`
}
// ScrobblerOutput is the output for scrobbler operations.
type ScrobblerOutput struct {
// ScrobblerResponse is the response for scrobbler operations.
type ScrobblerResponse struct {
// Error is the error message if the operation failed.
Error *string `json:"error,omitempty"`
Error string `json:"error,omitempty"`
// ErrorType indicates how Navidrome should handle the error.
ErrorType *ScrobblerErrorType `json:"errorType,omitempty"`
ErrorType ScrobblerErrorType `json:"errorType,omitempty"`
}
// ScrobbleInput is the input for submitting a scrobble.
type ScrobbleInput struct {
// ScrobbleRequest is the request for submitting a scrobble.
type ScrobbleRequest struct {
// UserID is the internal Navidrome user ID.
UserID string `json:"userId"`
// Username is the username of the user.
@ -70,37 +84,23 @@ type TrackInfo struct {
// AlbumArtist is the album artist.
AlbumArtist string `json:"albumArtist"`
// Duration is the track duration in seconds.
Duration float64 `json:"duration"`
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"`
MBZRecordingID string `json:"mbzRecordingId,omitempty"`
// MBZAlbumID is the MusicBrainz album/release ID.
MBZAlbumID *string `json:"mbzAlbumId,omitempty"`
MBZAlbumID string `json:"mbzAlbumId,omitempty"`
// MBZArtistID is the MusicBrainz artist ID.
MBZArtistID *string `json:"mbzArtistId,omitempty"`
MBZArtistID string `json:"mbzArtistId,omitempty"`
// MBZReleaseGroupID is the MusicBrainz release group ID.
MBZReleaseGroupID *string `json:"mbzReleaseGroupId,omitempty"`
MBZReleaseGroupID string `json:"mbzReleaseGroupId,omitempty"`
// MBZAlbumArtistID is the MusicBrainz album artist ID.
MBZAlbumArtistID *string `json:"mbzAlbumArtistId,omitempty"`
MBZAlbumArtistID string `json:"mbzAlbumArtistId,omitempty"`
// MBZReleaseTrackID is the MusicBrainz release track ID.
MBZReleaseTrackID *string `json:"mbzReleaseTrackId,omitempty"`
}
// AuthInput is the input for authorization check.
type AuthInput struct {
// UserID is the internal Navidrome user ID.
UserID string `json:"userId"`
// Username is the username of the user.
Username string `json:"username"`
}
// AuthOutput is the output for authorization check.
type AuthOutput struct {
// Authorized indicates whether the user is authorized to scrobble.
Authorized bool `json:"authorized"`
MBZReleaseTrackID string `json:"mbzReleaseTrackId,omitempty"`
}
// Scrobbler requires all methods to be implemented.
@ -112,16 +112,16 @@ type AuthOutput struct {
// all three functions: IsAuthorized, NowPlaying, and Scrobble.
type Scrobbler interface {
// IsAuthorized - IsAuthorized checks if a user is authorized to scrobble to this service.
IsAuthorized(AuthInput) (AuthOutput, error)
IsAuthorized(IsAuthorizedRequest) (IsAuthorizedResponse, error)
// NowPlaying - NowPlaying sends a now playing notification to the scrobbling service.
NowPlaying(NowPlayingInput) (ScrobblerOutput, error)
NowPlaying(NowPlayingRequest) (ScrobblerResponse, error)
// Scrobble - Scrobble submits a completed scrobble to the scrobbling service.
Scrobble(ScrobbleInput) (ScrobblerOutput, error)
Scrobble(ScrobbleRequest) (ScrobblerResponse, error)
} // Internal implementation holders
var (
isAuthorizedImpl func(AuthInput) (AuthOutput, error)
nowPlayingImpl func(NowPlayingInput) (ScrobblerOutput, error)
scrobbleImpl func(ScrobbleInput) (ScrobblerOutput, error)
isAuthorizedImpl func(IsAuthorizedRequest) (IsAuthorizedResponse, error)
nowPlayingImpl func(NowPlayingRequest) (ScrobblerResponse, error)
scrobbleImpl func(ScrobbleRequest) (ScrobblerResponse, error)
)
// Register registers a scrobbler implementation.
@ -143,7 +143,7 @@ func _NdScrobblerIsAuthorized() int32 {
return NotImplementedCode
}
var input AuthInput
var input IsAuthorizedRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
@ -170,7 +170,7 @@ func _NdScrobblerNowPlaying() int32 {
return NotImplementedCode
}
var input NowPlayingInput
var input NowPlayingRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
@ -197,7 +197,7 @@ func _NdScrobblerScrobble() int32 {
return NotImplementedCode
}
var input ScrobbleInput
var input ScrobbleRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1

View File

@ -22,8 +22,22 @@ const (
ScrobblerErrorUnrecoverable ScrobblerErrorType = "unrecoverable"
)
// NowPlayingInput is the input for now playing notification.
type NowPlayingInput struct {
// IsAuthorizedRequest is the request for authorization check.
type IsAuthorizedRequest struct {
// UserID is the internal Navidrome user ID.
UserID string `json:"userId"`
// Username is the username of the user.
Username string `json:"username"`
}
// IsAuthorizedResponse is the response for authorization check.
type IsAuthorizedResponse struct {
// Authorized indicates whether the user is authorized to scrobble.
Authorized bool `json:"authorized"`
}
// NowPlayingRequest is the request for now playing notification.
type NowPlayingRequest struct {
// UserID is the internal Navidrome user ID.
UserID string `json:"userId"`
// Username is the username of the user.
@ -34,16 +48,16 @@ type NowPlayingInput struct {
Position int32 `json:"position"`
}
// ScrobblerOutput is the output for scrobbler operations.
type ScrobblerOutput struct {
// ScrobblerResponse is the response for scrobbler operations.
type ScrobblerResponse struct {
// Error is the error message if the operation failed.
Error *string `json:"error,omitempty"`
Error string `json:"error,omitempty"`
// ErrorType indicates how Navidrome should handle the error.
ErrorType *ScrobblerErrorType `json:"errorType,omitempty"`
ErrorType ScrobblerErrorType `json:"errorType,omitempty"`
}
// ScrobbleInput is the input for submitting a scrobble.
type ScrobbleInput struct {
// ScrobbleRequest is the request for submitting a scrobble.
type ScrobbleRequest struct {
// UserID is the internal Navidrome user ID.
UserID string `json:"userId"`
// Username is the username of the user.
@ -67,37 +81,23 @@ type TrackInfo struct {
// AlbumArtist is the album artist.
AlbumArtist string `json:"albumArtist"`
// Duration is the track duration in seconds.
Duration float64 `json:"duration"`
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"`
MBZRecordingID string `json:"mbzRecordingId,omitempty"`
// MBZAlbumID is the MusicBrainz album/release ID.
MBZAlbumID *string `json:"mbzAlbumId,omitempty"`
MBZAlbumID string `json:"mbzAlbumId,omitempty"`
// MBZArtistID is the MusicBrainz artist ID.
MBZArtistID *string `json:"mbzArtistId,omitempty"`
MBZArtistID string `json:"mbzArtistId,omitempty"`
// MBZReleaseGroupID is the MusicBrainz release group ID.
MBZReleaseGroupID *string `json:"mbzReleaseGroupId,omitempty"`
MBZReleaseGroupID string `json:"mbzReleaseGroupId,omitempty"`
// MBZAlbumArtistID is the MusicBrainz album artist ID.
MBZAlbumArtistID *string `json:"mbzAlbumArtistId,omitempty"`
MBZAlbumArtistID string `json:"mbzAlbumArtistId,omitempty"`
// MBZReleaseTrackID is the MusicBrainz release track ID.
MBZReleaseTrackID *string `json:"mbzReleaseTrackId,omitempty"`
}
// AuthInput is the input for authorization check.
type AuthInput struct {
// UserID is the internal Navidrome user ID.
UserID string `json:"userId"`
// Username is the username of the user.
Username string `json:"username"`
}
// AuthOutput is the output for authorization check.
type AuthOutput struct {
// Authorized indicates whether the user is authorized to scrobble.
Authorized bool `json:"authorized"`
MBZReleaseTrackID string `json:"mbzReleaseTrackId,omitempty"`
}
// Scrobbler requires all methods to be implemented.
@ -109,11 +109,11 @@ type AuthOutput struct {
// all three functions: IsAuthorized, NowPlaying, and Scrobble.
type Scrobbler interface {
// IsAuthorized - IsAuthorized checks if a user is authorized to scrobble to this service.
IsAuthorized(AuthInput) (AuthOutput, error)
IsAuthorized(IsAuthorizedRequest) (IsAuthorizedResponse, error)
// NowPlaying - NowPlaying sends a now playing notification to the scrobbling service.
NowPlaying(NowPlayingInput) (ScrobblerOutput, error)
NowPlaying(NowPlayingRequest) (ScrobblerResponse, error)
// Scrobble - Scrobble submits a completed scrobble to the scrobbling service.
Scrobble(ScrobbleInput) (ScrobblerOutput, error)
Scrobble(ScrobbleRequest) (ScrobblerResponse, error)
}
// NotImplementedCode is the standard return code for unimplemented functions.

View File

@ -11,23 +11,15 @@ import (
pdk "github.com/extism/go-pdk"
)
// OnErrorInput is the input provided when an error occurs on a WebSocket connection.
type OnErrorInput struct {
// ConnectionID is the unique identifier for the WebSocket connection where the error occurred.
ConnectionID string `json:"connectionId"`
// Error is the error message describing what went wrong.
Error string `json:"error"`
}
// OnErrorOutput is the output from the error handler.
type OnErrorOutput struct {
// OnErrorResponse is the response from the error handler.
type OnErrorResponse struct {
// Error is the error message if the callback failed.
// Empty or null indicates success.
Error *string `json:"error,omitempty"`
// Empty string indicates success.
Error string `json:"error,omitempty"`
}
// OnCloseInput is the input provided when a WebSocket connection is closed.
type OnCloseInput struct {
// OnCloseRequest is the request provided when a WebSocket connection is closed.
type OnCloseRequest struct {
// ConnectionID is the unique identifier for the WebSocket connection that was closed.
ConnectionID string `json:"connectionId"`
// Code is the WebSocket close status code (e.g., 1000 for normal closure,
@ -37,41 +29,49 @@ type OnCloseInput struct {
Reason string `json:"reason"`
}
// OnCloseOutput is the output from the close handler.
type OnCloseOutput struct {
// OnCloseResponse is the response from the close handler.
type OnCloseResponse struct {
// Error is the error message if the callback failed.
// Empty or null indicates success.
Error *string `json:"error,omitempty"`
// Empty string indicates success.
Error string `json:"error,omitempty"`
}
// OnTextMessageInput is the input provided when a text message is received.
type OnTextMessageInput struct {
// OnTextMessageRequest is the request provided when a text message is received.
type OnTextMessageRequest struct {
// ConnectionID is the unique identifier for the WebSocket connection that received the message.
ConnectionID string `json:"connectionId"`
// Message is the text message content received from the WebSocket.
Message string `json:"message"`
}
// OnTextMessageOutput is the output from the text message handler.
type OnTextMessageOutput struct {
// OnTextMessageResponse is the response from the text message handler.
type OnTextMessageResponse struct {
// Error is the error message if the callback failed.
// Empty or null indicates success.
Error *string `json:"error,omitempty"`
// Empty string indicates success.
Error string `json:"error,omitempty"`
}
// OnBinaryMessageInput is the input provided when a binary message is received.
type OnBinaryMessageInput struct {
// OnBinaryMessageRequest is the request provided when a binary message is received.
type OnBinaryMessageRequest struct {
// ConnectionID is the unique identifier for the WebSocket connection that received the message.
ConnectionID string `json:"connectionId"`
// Data is the binary data received from the WebSocket, encoded as base64.
Data string `json:"data"`
}
// OnBinaryMessageOutput is the output from the binary message handler.
type OnBinaryMessageOutput struct {
// OnBinaryMessageResponse is the response from the binary message handler.
type OnBinaryMessageResponse struct {
// Error is the error message if the callback failed.
// Empty or null indicates success.
Error *string `json:"error,omitempty"`
// Empty string indicates success.
Error string `json:"error,omitempty"`
}
// OnErrorRequest is the request provided when an error occurs on a WebSocket connection.
type OnErrorRequest struct {
// ConnectionID is the unique identifier for the WebSocket connection where the error occurred.
ConnectionID string `json:"connectionId"`
// Error is the error message describing what went wrong.
Error string `json:"error"`
}
// WebSocket is the marker interface for websocket plugins.
@ -85,28 +85,28 @@ type WebSocket interface{}
// TextMessageProvider provides the OnTextMessage function.
type TextMessageProvider interface {
OnTextMessage(OnTextMessageInput) (OnTextMessageOutput, error)
OnTextMessage(OnTextMessageRequest) (OnTextMessageResponse, error)
}
// BinaryMessageProvider provides the OnBinaryMessage function.
type BinaryMessageProvider interface {
OnBinaryMessage(OnBinaryMessageInput) (OnBinaryMessageOutput, error)
OnBinaryMessage(OnBinaryMessageRequest) (OnBinaryMessageResponse, error)
}
// ErrorProvider provides the OnError function.
type ErrorProvider interface {
OnError(OnErrorInput) (OnErrorOutput, error)
OnError(OnErrorRequest) (OnErrorResponse, error)
}
// CloseProvider provides the OnClose function.
type CloseProvider interface {
OnClose(OnCloseInput) (OnCloseOutput, error)
OnClose(OnCloseRequest) (OnCloseResponse, error)
} // Internal implementation holders
var (
textMessageImpl func(OnTextMessageInput) (OnTextMessageOutput, error)
binaryMessageImpl func(OnBinaryMessageInput) (OnBinaryMessageOutput, error)
errorImpl func(OnErrorInput) (OnErrorOutput, error)
closeImpl func(OnCloseInput) (OnCloseOutput, error)
textMessageImpl func(OnTextMessageRequest) (OnTextMessageResponse, error)
binaryMessageImpl func(OnBinaryMessageRequest) (OnBinaryMessageResponse, error)
errorImpl func(OnErrorRequest) (OnErrorResponse, error)
closeImpl func(OnCloseRequest) (OnCloseResponse, error)
)
// Register registers a websocket implementation.
@ -137,7 +137,7 @@ func _NdWebsocketOnTextMessage() int32 {
return NotImplementedCode
}
var input OnTextMessageInput
var input OnTextMessageRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
@ -164,7 +164,7 @@ func _NdWebsocketOnBinaryMessage() int32 {
return NotImplementedCode
}
var input OnBinaryMessageInput
var input OnBinaryMessageRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
@ -191,7 +191,7 @@ func _NdWebsocketOnError() int32 {
return NotImplementedCode
}
var input OnErrorInput
var input OnErrorRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
@ -218,7 +218,7 @@ func _NdWebsocketOnClose() int32 {
return NotImplementedCode
}
var input OnCloseInput
var input OnCloseRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1

View File

@ -8,23 +8,15 @@
package websocket
// OnErrorInput is the input provided when an error occurs on a WebSocket connection.
type OnErrorInput struct {
// ConnectionID is the unique identifier for the WebSocket connection where the error occurred.
ConnectionID string `json:"connectionId"`
// Error is the error message describing what went wrong.
Error string `json:"error"`
}
// OnErrorOutput is the output from the error handler.
type OnErrorOutput struct {
// OnErrorResponse is the response from the error handler.
type OnErrorResponse struct {
// Error is the error message if the callback failed.
// Empty or null indicates success.
Error *string `json:"error,omitempty"`
// Empty string indicates success.
Error string `json:"error,omitempty"`
}
// OnCloseInput is the input provided when a WebSocket connection is closed.
type OnCloseInput struct {
// OnCloseRequest is the request provided when a WebSocket connection is closed.
type OnCloseRequest struct {
// ConnectionID is the unique identifier for the WebSocket connection that was closed.
ConnectionID string `json:"connectionId"`
// Code is the WebSocket close status code (e.g., 1000 for normal closure,
@ -34,41 +26,49 @@ type OnCloseInput struct {
Reason string `json:"reason"`
}
// OnCloseOutput is the output from the close handler.
type OnCloseOutput struct {
// OnCloseResponse is the response from the close handler.
type OnCloseResponse struct {
// Error is the error message if the callback failed.
// Empty or null indicates success.
Error *string `json:"error,omitempty"`
// Empty string indicates success.
Error string `json:"error,omitempty"`
}
// OnTextMessageInput is the input provided when a text message is received.
type OnTextMessageInput struct {
// OnTextMessageRequest is the request provided when a text message is received.
type OnTextMessageRequest struct {
// ConnectionID is the unique identifier for the WebSocket connection that received the message.
ConnectionID string `json:"connectionId"`
// Message is the text message content received from the WebSocket.
Message string `json:"message"`
}
// OnTextMessageOutput is the output from the text message handler.
type OnTextMessageOutput struct {
// OnTextMessageResponse is the response from the text message handler.
type OnTextMessageResponse struct {
// Error is the error message if the callback failed.
// Empty or null indicates success.
Error *string `json:"error,omitempty"`
// Empty string indicates success.
Error string `json:"error,omitempty"`
}
// OnBinaryMessageInput is the input provided when a binary message is received.
type OnBinaryMessageInput struct {
// OnBinaryMessageRequest is the request provided when a binary message is received.
type OnBinaryMessageRequest struct {
// ConnectionID is the unique identifier for the WebSocket connection that received the message.
ConnectionID string `json:"connectionId"`
// Data is the binary data received from the WebSocket, encoded as base64.
Data string `json:"data"`
}
// OnBinaryMessageOutput is the output from the binary message handler.
type OnBinaryMessageOutput struct {
// OnBinaryMessageResponse is the response from the binary message handler.
type OnBinaryMessageResponse struct {
// Error is the error message if the callback failed.
// Empty or null indicates success.
Error *string `json:"error,omitempty"`
// Empty string indicates success.
Error string `json:"error,omitempty"`
}
// OnErrorRequest is the request provided when an error occurs on a WebSocket connection.
type OnErrorRequest struct {
// ConnectionID is the unique identifier for the WebSocket connection where the error occurred.
ConnectionID string `json:"connectionId"`
// Error is the error message describing what went wrong.
Error string `json:"error"`
}
// WebSocket is the marker interface for websocket plugins.
@ -82,22 +82,22 @@ type WebSocket interface{}
// TextMessageProvider provides the OnTextMessage function.
type TextMessageProvider interface {
OnTextMessage(OnTextMessageInput) (OnTextMessageOutput, error)
OnTextMessage(OnTextMessageRequest) (OnTextMessageResponse, error)
}
// BinaryMessageProvider provides the OnBinaryMessage function.
type BinaryMessageProvider interface {
OnBinaryMessage(OnBinaryMessageInput) (OnBinaryMessageOutput, error)
OnBinaryMessage(OnBinaryMessageRequest) (OnBinaryMessageResponse, error)
}
// ErrorProvider provides the OnError function.
type ErrorProvider interface {
OnError(OnErrorInput) (OnErrorOutput, error)
OnError(OnErrorRequest) (OnErrorResponse, error)
}
// CloseProvider provides the OnClose function.
type CloseProvider interface {
OnClose(OnCloseInput) (OnCloseOutput, error)
OnClose(OnCloseRequest) (OnCloseResponse, error)
}
// NotImplementedCode is the standard return code for unimplemented functions.

View File

@ -7,6 +7,7 @@ import (
"github.com/navidrome/navidrome/core/scrobbler"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/plugins/capabilities"
)
// CapabilityScrobbler indicates the plugin can receive scrobble events.
@ -39,12 +40,12 @@ type ScrobblerPlugin struct {
// IsAuthorized checks if the user is authorized with this scrobbler
func (s *ScrobblerPlugin) IsAuthorized(ctx context.Context, userId string) bool {
username := getUsernameFromContext(ctx)
input := scrobblerAuthInput{
input := capabilities.IsAuthorizedRequest{
UserID: userId,
Username: username,
}
result, err := callPluginFunction[scrobblerAuthInput, scrobblerAuthOutput](ctx, s.plugin, FuncScrobblerIsAuthorized, input)
result, err := callPluginFunction[capabilities.IsAuthorizedRequest, capabilities.IsAuthorizedResponse](ctx, s.plugin, FuncScrobblerIsAuthorized, input)
if err != nil {
return false
}
@ -55,14 +56,14 @@ func (s *ScrobblerPlugin) IsAuthorized(ctx context.Context, userId string) bool
// NowPlaying sends a now playing notification to the scrobbler
func (s *ScrobblerPlugin) NowPlaying(ctx context.Context, userId string, track *model.MediaFile, position int) error {
username := getUsernameFromContext(ctx)
input := scrobblerNowPlayingInput{
input := capabilities.NowPlayingRequest{
UserID: userId,
Username: username,
Track: mediaFileToTrackInfo(track),
Position: position,
Position: int32(position),
}
result, err := callPluginFunction[scrobblerNowPlayingInput, scrobblerOutput](ctx, s.plugin, FuncScrobblerNowPlaying, input)
result, err := callPluginFunction[capabilities.NowPlayingRequest, capabilities.ScrobblerResponse](ctx, s.plugin, FuncScrobblerNowPlaying, input)
if err != nil {
return err
}
@ -73,14 +74,14 @@ func (s *ScrobblerPlugin) NowPlaying(ctx context.Context, userId string, track *
// Scrobble submits a scrobble to the scrobbler
func (s *ScrobblerPlugin) Scrobble(ctx context.Context, userId string, sc scrobbler.Scrobble) error {
username := getUsernameFromContext(ctx)
input := scrobblerScrobbleInput{
input := capabilities.ScrobbleRequest{
UserID: userId,
Username: username,
Track: mediaFileToTrackInfo(&sc.MediaFile),
Timestamp: sc.TimeStamp.Unix(),
}
result, err := callPluginFunction[scrobblerScrobbleInput, scrobblerOutput](ctx, s.plugin, FuncScrobblerScrobble, input)
result, err := callPluginFunction[capabilities.ScrobbleRequest, capabilities.ScrobblerResponse](ctx, s.plugin, FuncScrobblerScrobble, input)
if err != nil {
return err
}
@ -96,42 +97,42 @@ func getUsernameFromContext(ctx context.Context) string {
return ""
}
// mediaFileToTrackInfo converts a model.MediaFile to scrobblerTrackInfo
func mediaFileToTrackInfo(mf *model.MediaFile) scrobblerTrackInfo {
return scrobblerTrackInfo{
// mediaFileToTrackInfo converts a model.MediaFile to capabilities.TrackInfo
func mediaFileToTrackInfo(mf *model.MediaFile) capabilities.TrackInfo {
return capabilities.TrackInfo{
ID: mf.ID,
Title: mf.Title,
Album: mf.Album,
Artist: mf.Artist,
AlbumArtist: mf.AlbumArtist,
Duration: mf.Duration,
TrackNumber: mf.TrackNumber,
DiscNumber: mf.DiscNumber,
MbzRecordingID: mf.MbzRecordingID,
MbzAlbumID: mf.MbzAlbumID,
MbzArtistID: mf.MbzArtistID,
MbzReleaseGroupID: mf.MbzReleaseGroupID,
MbzAlbumArtistID: mf.MbzAlbumArtistID,
MbzReleaseTrackID: mf.MbzReleaseTrackID,
TrackNumber: int32(mf.TrackNumber),
DiscNumber: int32(mf.DiscNumber),
MBZRecordingID: mf.MbzRecordingID,
MBZAlbumID: mf.MbzAlbumID,
MBZArtistID: mf.MbzArtistID,
MBZReleaseGroupID: mf.MbzReleaseGroupID,
MBZAlbumArtistID: mf.MbzAlbumArtistID,
MBZReleaseTrackID: mf.MbzReleaseTrackID,
}
}
// mapScrobblerError converts the plugin output error to a scrobbler error
func mapScrobblerError(output scrobblerOutput) error {
func mapScrobblerError(output capabilities.ScrobblerResponse) error {
switch output.ErrorType {
case scrobblerErrorNone, "":
case capabilities.ScrobblerErrorNone, "":
return nil
case scrobblerErrorNotAuthorized:
case capabilities.ScrobblerErrorNotAuthorized:
if output.Error != "" {
return fmt.Errorf("%w: %s", scrobbler.ErrNotAuthorized, output.Error)
}
return scrobbler.ErrNotAuthorized
case scrobblerErrorRetryLater:
case capabilities.ScrobblerErrorRetryLater:
if output.Error != "" {
return fmt.Errorf("%w: %s", scrobbler.ErrRetryLater, output.Error)
}
return scrobbler.ErrRetryLater
case scrobblerErrorUnrecoverable:
case capabilities.ScrobblerErrorUnrecoverable:
if output.Error != "" {
return fmt.Errorf("%w: %s", scrobbler.ErrUnrecoverable, output.Error)
}

View File

@ -9,6 +9,7 @@ import (
"github.com/navidrome/navidrome/core/scrobbler"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/plugins/capabilities"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@ -170,42 +171,42 @@ var _ = Describe("ScrobblerPlugin", Ordered, func() {
var _ = Describe("mapScrobblerError", func() {
It("returns nil for empty error type", func() {
output := scrobblerOutput{ErrorType: ""}
output := capabilities.ScrobblerResponse{ErrorType: ""}
Expect(mapScrobblerError(output)).ToNot(HaveOccurred())
})
It("returns nil for 'none' error type", func() {
output := scrobblerOutput{ErrorType: "none"}
output := capabilities.ScrobblerResponse{ErrorType: capabilities.ScrobblerErrorNone}
Expect(mapScrobblerError(output)).ToNot(HaveOccurred())
})
It("returns ErrNotAuthorized for 'not_authorized' error type", func() {
output := scrobblerOutput{ErrorType: "not_authorized"}
output := capabilities.ScrobblerResponse{ErrorType: capabilities.ScrobblerErrorNotAuthorized}
err := mapScrobblerError(output)
Expect(err).To(MatchError(scrobbler.ErrNotAuthorized))
})
It("returns ErrNotAuthorized with message", func() {
output := scrobblerOutput{ErrorType: "not_authorized", Error: "user not linked"}
output := capabilities.ScrobblerResponse{ErrorType: capabilities.ScrobblerErrorNotAuthorized, Error: "user not linked"}
err := mapScrobblerError(output)
Expect(err).To(MatchError(ContainSubstring("not authorized")))
Expect(err).To(MatchError(ContainSubstring("user not linked")))
})
It("returns ErrRetryLater for 'retry_later' error type", func() {
output := scrobblerOutput{ErrorType: "retry_later"}
output := capabilities.ScrobblerResponse{ErrorType: capabilities.ScrobblerErrorRetryLater}
err := mapScrobblerError(output)
Expect(err).To(MatchError(scrobbler.ErrRetryLater))
})
It("returns ErrUnrecoverable for 'unrecoverable' error type", func() {
output := scrobblerOutput{ErrorType: "unrecoverable"}
output := capabilities.ScrobblerResponse{ErrorType: capabilities.ScrobblerErrorUnrecoverable}
err := mapScrobblerError(output)
Expect(err).To(MatchError(scrobbler.ErrUnrecoverable))
})
It("returns error for unknown error type", func() {
output := scrobblerOutput{ErrorType: "unknown"}
output := capabilities.ScrobblerResponse{ErrorType: "unknown"}
err := mapScrobblerError(output)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("unknown error type"))

View File

@ -1,62 +0,0 @@
package plugins
// --- Input/Output JSON structures for Scrobbler plugin calls ---
// scrobblerAuthInput is the input for IsAuthorized
type scrobblerAuthInput struct {
UserID string `json:"userId"`
Username string `json:"username"`
}
// scrobblerAuthOutput is the output for IsAuthorized
type scrobblerAuthOutput struct {
Authorized bool `json:"authorized"`
}
// scrobblerTrackInfo contains track metadata for scrobbling
type scrobblerTrackInfo struct {
ID string `json:"id"`
Title string `json:"title"`
Album string `json:"album"`
Artist string `json:"artist"`
AlbumArtist string `json:"albumArtist"`
Duration float32 `json:"duration"`
TrackNumber int `json:"trackNumber"`
DiscNumber int `json:"discNumber"`
MbzRecordingID string `json:"mbzRecordingId,omitempty"`
MbzAlbumID string `json:"mbzAlbumId,omitempty"`
MbzArtistID string `json:"mbzArtistId,omitempty"`
MbzReleaseGroupID string `json:"mbzReleaseGroupId,omitempty"`
MbzAlbumArtistID string `json:"mbzAlbumArtistId,omitempty"`
MbzReleaseTrackID string `json:"mbzReleaseTrackId,omitempty"`
}
// scrobblerNowPlayingInput is the input for NowPlaying
type scrobblerNowPlayingInput struct {
UserID string `json:"userId"`
Username string `json:"username"`
Track scrobblerTrackInfo `json:"track"`
Position int `json:"position"`
}
// scrobblerScrobbleInput is the input for Scrobble
type scrobblerScrobbleInput struct {
UserID string `json:"userId"`
Username string `json:"username"`
Track scrobblerTrackInfo `json:"track"`
Timestamp int64 `json:"timestamp"`
}
// scrobblerOutput is the output for NowPlaying and Scrobble
type scrobblerOutput struct {
Error string `json:"error,omitempty"`
ErrorType string `json:"errorType,omitempty"` // "none", "notAuthorized", "retryLater", "unrecoverable"
}
// scrobbler error type constants
const (
scrobblerErrorNone = "none"
scrobblerErrorNotAuthorized = "not_authorized"
scrobblerErrorRetryLater = "retry_later"
scrobblerErrorUnrecoverable = "unrecoverable"
)