From 31842b2c3d308742565a51c7a63fd94ce4932894 Mon Sep 17 00:00:00 2001 From: Deluan Date: Tue, 30 Dec 2025 08:59:12 -0500 Subject: [PATCH] refactor(plugins): consistent naming/types across PDK Signed-off-by: Deluan --- plugins/capabilities/doc.go | 3 + plugins/capabilities/lifecycle.go | 14 +- plugins/capabilities/lifecycle.yaml | 15 +- plugins/capabilities/metadata_agent.go | 80 +++---- plugins/capabilities/metadata_agent.yaml | 118 +++++----- plugins/capabilities/scheduler_callback.go | 14 +- plugins/capabilities/scheduler_callback.yaml | 15 +- plugins/capabilities/scrobbler.go | 44 ++-- plugins/capabilities/scrobbler.yaml | 40 ++-- plugins/capabilities/websocket_callback.go | 56 ++--- plugins/capabilities/websocket_callback.yaml | 60 +++--- plugins/capability_lifecycle.go | 11 +- plugins/examples/crypto-ticker/main.go | 32 +-- .../examples/discord-rich-presence/main.go | 66 +++--- plugins/examples/minimal/main.go | 4 +- plugins/examples/wikimedia/main.go | 59 +++-- plugins/host_scheduler.go | 17 +- plugins/host_websocket.go | 42 +--- plugins/metadata_agent.go | 41 ++-- plugins/metadata_types.go | 100 --------- plugins/pdk/go/lifecycle/lifecycle.go | 18 +- plugins/pdk/go/lifecycle/lifecycle_stub.go | 14 +- plugins/pdk/go/metadata/metadata.go | 202 +++++++++--------- plugins/pdk/go/metadata/metadata_stub.go | 170 +++++++-------- plugins/pdk/go/scheduler/scheduler.go | 18 +- plugins/pdk/go/scheduler/scheduler_stub.go | 14 +- plugins/pdk/go/scrobbler/scrobbler.go | 76 +++---- plugins/pdk/go/scrobbler/scrobbler_stub.go | 64 +++--- plugins/pdk/go/websocket/websocket.go | 84 ++++---- plugins/pdk/go/websocket/websocket_stub.go | 68 +++--- plugins/scrobbler_adapter.go | 47 ++-- plugins/scrobbler_adapter_test.go | 15 +- plugins/scrobbler_types.go | 62 ------ 33 files changed, 727 insertions(+), 956 deletions(-) delete mode 100644 plugins/metadata_types.go delete mode 100644 plugins/scrobbler_types.go diff --git a/plugins/capabilities/doc.go b/plugins/capabilities/doc.go index 228eca20c..798d86cab 100644 --- a/plugins/capabilities/doc.go +++ b/plugins/capabilities/doc.go @@ -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 diff --git a/plugins/capabilities/lifecycle.go b/plugins/capabilities/lifecycle.go index 5cbf0b542..7c12260b4 100644 --- a/plugins/capabilities/lifecycle.go +++ b/plugins/capabilities/lifecycle.go @@ -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"` } diff --git a/plugins/capabilities/lifecycle.yaml b/plugins/capabilities/lifecycle.yaml index 0ac0da1e9..933107f84 100644 --- a/plugins/capabilities/lifecycle.yaml +++ b/plugins/capabilities/lifecycle.yaml @@ -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 diff --git a/plugins/capabilities/metadata_agent.go b/plugins/capabilities/metadata_agent.go index bda349cf1..0658740ab 100644 --- a/plugins/capabilities/metadata_agent.go +++ b/plugins/capabilities/metadata_agent.go @@ -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"` } diff --git a/plugins/capabilities/metadata_agent.yaml b/plugins/capabilities/metadata_agent.yaml index 31a61a3ab..79da13e93 100644 --- a/plugins/capabilities/metadata_agent.yaml +++ b/plugins/capabilities/metadata_agent.yaml @@ -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: diff --git a/plugins/capabilities/scheduler_callback.go b/plugins/capabilities/scheduler_callback.go index 78c178c92..b722a2fab 100644 --- a/plugins/capabilities/scheduler_callback.go +++ b/plugins/capabilities/scheduler_callback.go @@ -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"` } diff --git a/plugins/capabilities/scheduler_callback.yaml b/plugins/capabilities/scheduler_callback.yaml index d818739f2..6f1eb836a 100644 --- a/plugins/capabilities/scheduler_callback.yaml +++ b/plugins/capabilities/scheduler_callback.yaml @@ -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 diff --git a/plugins/capabilities/scrobbler.go b/plugins/capabilities/scrobbler.go index 17663a301..5c6b686bd 100644 --- a/plugins/capabilities/scrobbler.go +++ b/plugins/capabilities/scrobbler.go @@ -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"` } diff --git a/plugins/capabilities/scrobbler.yaml b/plugins/capabilities/scrobbler.yaml index 6fd088d47..ab0e2f1f3 100644 --- a/plugins/capabilities/scrobbler.yaml +++ b/plugins/capabilities/scrobbler.yaml @@ -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 diff --git a/plugins/capabilities/websocket_callback.go b/plugins/capabilities/websocket_callback.go index f9cd6333f..7f04e74d3 100644 --- a/plugins/capabilities/websocket_callback.go +++ b/plugins/capabilities/websocket_callback.go @@ -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"` } diff --git a/plugins/capabilities/websocket_callback.yaml b/plugins/capabilities/websocket_callback.yaml index b8d740a4a..9121e17cf 100644 --- a/plugins/capabilities/websocket_callback.yaml +++ b/plugins/capabilities/websocket_callback.yaml @@ -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. diff --git a/plugins/capability_lifecycle.go b/plugins/capability_lifecycle.go index 1ab0b268d..0d42cab4f 100644 --- a/plugins/capability_lifecycle.go +++ b/plugins/capability_lifecycle.go @@ -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 diff --git a/plugins/examples/crypto-ticker/main.go b/plugins/examples/crypto-ticker/main.go index 2b29350c2..52f8d821d 100755 --- a/plugins/examples/crypto-ticker/main.go +++ b/plugins/examples/crypto-ticker/main.go @@ -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 diff --git a/plugins/examples/discord-rich-presence/main.go b/plugins/examples/discord-rich-presence/main.go index 39690c84b..dd1bcfa1a 100644 --- a/plugins/examples/discord-rich-presence/main.go +++ b/plugins/examples/discord-rich-presence/main.go @@ -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() {} diff --git a/plugins/examples/minimal/main.go b/plugins/examples/minimal/main.go index 91872afd8..81cd55ef7 100644 --- a/plugins/examples/minimal/main.go +++ b/plugins/examples/minimal/main.go @@ -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 } diff --git a/plugins/examples/wikimedia/main.go b/plugins/examples/wikimedia/main.go index 25b95581a..7b8aa426f 100644 --- a/plugins/examples/wikimedia/main.go +++ b/plugins/examples/wikimedia/main.go @@ -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 diff --git a/plugins/host_scheduler.go b/plugins/host_scheduler.go index 7ecb026fc..340a36987 100644 --- a/plugins/host_scheduler.go +++ b/plugins/host_scheduler.go @@ -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 diff --git a/plugins/host_websocket.go b/plugins/host_websocket.go index c456176c8..d89211735 100644 --- a/plugins/host_websocket.go +++ b/plugins/host_websocket.go @@ -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) { diff --git a/plugins/metadata_agent.go b/plugins/metadata_agent.go index 84bf32067..c0fedd160 100644 --- a/plugins/metadata_agent.go +++ b/plugins/metadata_agent.go @@ -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 diff --git a/plugins/metadata_types.go b/plugins/metadata_types.go deleted file mode 100644 index b69681fac..000000000 --- a/plugins/metadata_types.go +++ /dev/null @@ -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"` -} diff --git a/plugins/pdk/go/lifecycle/lifecycle.go b/plugins/pdk/go/lifecycle/lifecycle.go index 519cf35b9..a6fdcb6c7 100644 --- a/plugins/pdk/go/lifecycle/lifecycle.go +++ b/plugins/pdk/go/lifecycle/lifecycle.go @@ -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 diff --git a/plugins/pdk/go/lifecycle/lifecycle_stub.go b/plugins/pdk/go/lifecycle/lifecycle_stub.go index 97c111964..30c2b6c54 100644 --- a/plugins/pdk/go/lifecycle/lifecycle_stub.go +++ b/plugins/pdk/go/lifecycle/lifecycle_stub.go @@ -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. diff --git a/plugins/pdk/go/metadata/metadata.go b/plugins/pdk/go/metadata/metadata.go index 69c6522ce..f9efd7a3d 100644 --- a/plugins/pdk/go/metadata/metadata.go +++ b/plugins/pdk/go/metadata/metadata.go @@ -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 diff --git a/plugins/pdk/go/metadata/metadata_stub.go b/plugins/pdk/go/metadata/metadata_stub.go index 4d6a2ea4f..8dc348c38 100644 --- a/plugins/pdk/go/metadata/metadata_stub.go +++ b/plugins/pdk/go/metadata/metadata_stub.go @@ -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. diff --git a/plugins/pdk/go/scheduler/scheduler.go b/plugins/pdk/go/scheduler/scheduler.go index b1e8bf9c0..6d0a44a66 100644 --- a/plugins/pdk/go/scheduler/scheduler.go +++ b/plugins/pdk/go/scheduler/scheduler.go @@ -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 diff --git a/plugins/pdk/go/scheduler/scheduler_stub.go b/plugins/pdk/go/scheduler/scheduler_stub.go index e87d9862a..013540e6d 100644 --- a/plugins/pdk/go/scheduler/scheduler_stub.go +++ b/plugins/pdk/go/scheduler/scheduler_stub.go @@ -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. diff --git a/plugins/pdk/go/scrobbler/scrobbler.go b/plugins/pdk/go/scrobbler/scrobbler.go index 96d97b797..8b04e2bdf 100644 --- a/plugins/pdk/go/scrobbler/scrobbler.go +++ b/plugins/pdk/go/scrobbler/scrobbler.go @@ -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 diff --git a/plugins/pdk/go/scrobbler/scrobbler_stub.go b/plugins/pdk/go/scrobbler/scrobbler_stub.go index 02d8ea16b..e5b92961b 100644 --- a/plugins/pdk/go/scrobbler/scrobbler_stub.go +++ b/plugins/pdk/go/scrobbler/scrobbler_stub.go @@ -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. diff --git a/plugins/pdk/go/websocket/websocket.go b/plugins/pdk/go/websocket/websocket.go index 0c7050644..b17fa3f42 100644 --- a/plugins/pdk/go/websocket/websocket.go +++ b/plugins/pdk/go/websocket/websocket.go @@ -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 diff --git a/plugins/pdk/go/websocket/websocket_stub.go b/plugins/pdk/go/websocket/websocket_stub.go index c0bc5a943..987c2f5d2 100644 --- a/plugins/pdk/go/websocket/websocket_stub.go +++ b/plugins/pdk/go/websocket/websocket_stub.go @@ -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. diff --git a/plugins/scrobbler_adapter.go b/plugins/scrobbler_adapter.go index bc5603c12..37aa21d7c 100644 --- a/plugins/scrobbler_adapter.go +++ b/plugins/scrobbler_adapter.go @@ -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) } diff --git a/plugins/scrobbler_adapter_test.go b/plugins/scrobbler_adapter_test.go index 6dbee2b38..f1dc4bcd4 100644 --- a/plugins/scrobbler_adapter_test.go +++ b/plugins/scrobbler_adapter_test.go @@ -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")) diff --git a/plugins/scrobbler_types.go b/plugins/scrobbler_types.go deleted file mode 100644 index e9afdabf3..000000000 --- a/plugins/scrobbler_types.go +++ /dev/null @@ -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" -)