quintodrome/plugins/pdk/rust/nd-pdk-capabilities/src/scrobbler.rs
Deluan Quintão fda35dd8ce
feat(plugins): add similar songs retrieval functions and improve duration consistency (#4933)
* feat: add duration filtering for similar songs matching

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

* test: refactor expectations for similar songs in provider matching tests

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

* feat(plugins): add functions to retrieve similar songs by track, album, and artist

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

* fix(plugins): support uint32 in ndpgen

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

* fix(plugins): update duration field to use seconds as float instead of milliseconds as uint32

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

* fix: add helper functions for Rust's skip_serializing_if with numeric types

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

* feat(provider): enhance track matching logic to fallback to title match when duration-filtered tracks fail

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-01-26 18:28:41 -05:00

193 lines
7.4 KiB
Rust

// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains export wrappers for the Scrobbler capability.
// It is intended for use in Navidrome plugins built with extism-pdk.
use serde::{Deserialize, Serialize};
// Helper functions for skip_serializing_if with numeric types
#[allow(dead_code)]
fn is_zero_i32(value: &i32) -> bool { *value == 0 }
#[allow(dead_code)]
fn is_zero_u32(value: &u32) -> bool { *value == 0 }
#[allow(dead_code)]
fn is_zero_i64(value: &i64) -> bool { *value == 0 }
#[allow(dead_code)]
fn is_zero_u64(value: &u64) -> bool { *value == 0 }
#[allow(dead_code)]
fn is_zero_f32(value: &f32) -> bool { *value == 0.0 }
#[allow(dead_code)]
fn is_zero_f64(value: &f64) -> bool { *value == 0.0 }
/// ScrobblerError represents an error type for scrobbling operations.
pub type ScrobblerError = &'static str;
/// ScrobblerErrorNotAuthorized indicates the user is not authorized.
pub const SCROBBLER_ERROR_NOT_AUTHORIZED: ScrobblerError = "scrobbler(not_authorized)";
/// ScrobblerErrorRetryLater indicates the operation should be retried later.
pub const SCROBBLER_ERROR_RETRY_LATER: ScrobblerError = "scrobbler(retry_later)";
/// ScrobblerErrorUnrecoverable indicates an unrecoverable error.
pub const SCROBBLER_ERROR_UNRECOVERABLE: ScrobblerError = "scrobbler(unrecoverable)";
/// ArtistRef is a reference to an artist with name and optional MBID.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArtistRef {
/// ID is the internal Navidrome artist ID (if known).
#[serde(default, skip_serializing_if = "String::is_empty")]
pub id: String,
/// Name is the artist name.
#[serde(default)]
pub name: String,
/// MBID is the MusicBrainz ID for the artist.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbid: String,
}
/// IsAuthorizedRequest is the request for authorization check.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IsAuthorizedRequest {
/// Username is the username of the user.
#[serde(default)]
pub username: String,
}
/// NowPlayingRequest is the request for now playing notification.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NowPlayingRequest {
/// Username is the username of the user.
#[serde(default)]
pub username: String,
/// Track is the track currently playing.
#[serde(default)]
pub track: TrackInfo,
/// Position is the current playback position in seconds.
#[serde(default)]
pub position: i32,
}
/// ScrobbleRequest is the request for submitting a scrobble.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ScrobbleRequest {
/// Username is the username of the user.
#[serde(default)]
pub username: String,
/// Track is the track that was played.
#[serde(default)]
pub track: TrackInfo,
/// Timestamp is the Unix timestamp when the track started playing.
#[serde(default)]
pub timestamp: i64,
}
/// TrackInfo contains track metadata for scrobbling.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TrackInfo {
/// ID is the internal Navidrome track ID.
#[serde(default)]
pub id: String,
/// Title is the track title.
#[serde(default)]
pub title: String,
/// Album is the album name.
#[serde(default)]
pub album: String,
/// Artist is the formatted artist name for display (e.g., "Artist1 • Artist2").
#[serde(default)]
pub artist: String,
/// AlbumArtist is the formatted album artist name for display.
#[serde(default)]
pub album_artist: String,
/// Artists is the list of track artists.
#[serde(default)]
pub artists: Vec<ArtistRef>,
/// AlbumArtists is the list of album artists.
#[serde(default)]
pub album_artists: Vec<ArtistRef>,
/// Duration is the track duration in seconds.
#[serde(default)]
pub duration: f32,
/// TrackNumber is the track number on the album.
#[serde(default)]
pub track_number: i32,
/// DiscNumber is the disc number.
#[serde(default)]
pub disc_number: i32,
/// MBZRecordingID is the MusicBrainz recording ID.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbz_recording_id: String,
/// MBZAlbumID is the MusicBrainz album/release ID.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbz_album_id: String,
/// MBZReleaseGroupID is the MusicBrainz release group ID.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbz_release_group_id: String,
/// MBZReleaseTrackID is the MusicBrainz release track ID.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbz_release_track_id: String,
}
/// Error represents an error from a capability method.
#[derive(Debug)]
pub struct Error {
pub message: String,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for Error {}
impl Error {
pub fn new(message: impl Into<String>) -> Self {
Self { message: message.into() }
}
}
/// Scrobbler requires all methods to be implemented.
/// Scrobbler provides scrobbling functionality to external services.
/// This capability allows plugins to submit listening history to services like Last.fm,
/// ListenBrainz, or custom scrobbling backends.
///
/// All methods are required - plugins implementing this capability must provide
/// all three functions: IsAuthorized, NowPlaying, and Scrobble.
pub trait Scrobbler {
/// IsAuthorized - IsAuthorized checks if a user is authorized to scrobble to this service.
fn is_authorized(&self, req: IsAuthorizedRequest) -> Result<bool, Error>;
/// NowPlaying - NowPlaying sends a now playing notification to the scrobbling service.
fn now_playing(&self, req: NowPlayingRequest) -> Result<(), Error>;
/// Scrobble - Scrobble submits a completed scrobble to the scrobbling service.
fn scrobble(&self, req: ScrobbleRequest) -> Result<(), Error>;
}
/// Register all exports for the Scrobbler capability.
/// This macro generates the WASM export functions for all trait methods.
#[macro_export]
macro_rules! register_scrobbler {
($plugin_type:ty) => {
#[extism_pdk::plugin_fn]
pub fn nd_scrobbler_is_authorized(
req: extism_pdk::Json<$crate::scrobbler::IsAuthorizedRequest>
) -> extism_pdk::FnResult<extism_pdk::Json<bool>> {
let plugin = <$plugin_type>::default();
let result = $crate::scrobbler::Scrobbler::is_authorized(&plugin, req.into_inner())?;
Ok(extism_pdk::Json(result))
}
#[extism_pdk::plugin_fn]
pub fn nd_scrobbler_now_playing(
req: extism_pdk::Json<$crate::scrobbler::NowPlayingRequest>
) -> extism_pdk::FnResult<()> {
let plugin = <$plugin_type>::default();
$crate::scrobbler::Scrobbler::now_playing(&plugin, req.into_inner())?;
Ok(())
}
#[extism_pdk::plugin_fn]
pub fn nd_scrobbler_scrobble(
req: extism_pdk::Json<$crate::scrobbler::ScrobbleRequest>
) -> extism_pdk::FnResult<()> {
let plugin = <$plugin_type>::default();
$crate::scrobbler::Scrobbler::scrobble(&plugin, req.into_inner())?;
Ok(())
}
};
}