use serde::{Deserialize, Serialize};
#[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 }
#[deprecated(note = "use nd_pdk::types::ArtistRef")]
pub type ArtistRef = nd_pdk_types::ArtistRef;
pub type ScrobblerError = &'static str;
pub const SCROBBLER_ERROR_NOT_AUTHORIZED: ScrobblerError = "scrobbler(not_authorized)";
pub const SCROBBLER_ERROR_RETRY_LATER: ScrobblerError = "scrobbler(retry_later)";
pub const SCROBBLER_ERROR_UNRECOVERABLE: ScrobblerError = "scrobbler(unrecoverable)";
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IsAuthorizedRequest {
#[serde(default)]
pub username: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NowPlayingRequest {
#[serde(default)]
pub username: String,
#[serde(default)]
pub track: TrackInfo,
#[serde(default)]
pub position: i32,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaybackReportRequest {
#[serde(default)]
pub username: String,
#[serde(default)]
pub track: TrackInfo,
#[serde(default)]
pub state: String,
#[serde(default)]
pub position_ms: i64,
#[serde(default)]
pub playback_rate: f64,
#[serde(default)]
pub player_id: String,
#[serde(default)]
pub player_name: String,
#[serde(default)]
pub timestamp: i64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ScrobbleRequest {
#[serde(default)]
pub username: String,
#[serde(default)]
pub track: TrackInfo,
#[serde(default)]
pub timestamp: i64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TrackInfo {
#[serde(default)]
pub id: String,
#[serde(default)]
pub title: String,
#[serde(default)]
pub album: String,
#[serde(default)]
pub artist: String,
#[serde(default)]
pub album_artist: String,
#[serde(default)]
pub artists: Vec<nd_pdk_types::ArtistRef>,
#[serde(default)]
pub album_artists: Vec<nd_pdk_types::ArtistRef>,
#[serde(default)]
pub duration: f32,
#[serde(default)]
pub track_number: i32,
#[serde(default)]
pub disc_number: i32,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbz_recording_id: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbz_album_id: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbz_release_group_id: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbz_release_track_id: String,
#[serde(default, skip_serializing_if = "is_zero_i32")]
pub library_id: i32,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub path: String,
}
#[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() }
}
}
pub trait Scrobbler {
fn is_authorized(&self, req: IsAuthorizedRequest) -> Result<bool, Error>;
fn now_playing(&self, req: NowPlayingRequest) -> Result<(), Error>;
fn scrobble(&self, req: ScrobbleRequest) -> Result<(), Error>;
fn playback_report(&self, req: PlaybackReportRequest) -> Result<(), Error>;
}
#[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(())
}
#[extism_pdk::plugin_fn]
pub fn nd_scrobbler_playback_report(
req: extism_pdk::Json<$crate::scrobbler::PlaybackReportRequest>
) -> extism_pdk::FnResult<()> {
let plugin = <$plugin_type>::default();
$crate::scrobbler::Scrobbler::playback_report(&plugin, req.into_inner())?;
Ok(())
}
};
}