use reqwest::StatusCode; use super::deserialize as de; use std::sync::Arc; use std::collections::BTreeMap; use super::font::FontQuery; use super::config::STATE; pub mod model { use std::sync::Arc; use std::collections::BTreeMap; /// The theme representation of a user. #[derive(serde::Serialize, Debug)] pub struct User { /// Their username. pub name: Arc, /// Their "Display Name". pub realname: Arc, /// Total scrobbles. pub scrobble_count: u64, /// Number of artists in library. pub artist_count: u64, /// Number of tracks in library. pub track_count: u64, /// Number of albums in library. pub album_count: u64, /// Link to user's profile picture. pub image_url: Arc, /// Link to user's profile. pub url: Arc } /// The theme representation of an artist #[derive(serde::Serialize, Debug)] pub struct Artist { /// The artist's name. pub name: Arc, /// A link to their current image. pub image_url: Arc, /// A link to their last.fm page. pub url: Arc } /// The theme representation of a user's most recently scrobbled track. #[derive(serde::Serialize, Debug)] pub struct Scrobble { /// The name of the track. pub name: Arc, /// The name of its album. pub album: Arc, /// The artist who made it. pub artist: Artist, /// A link to the track image. pub image_url: Arc, /// True if the user is currently scrobbling it, false if it's just the most recently played track. pub now_playing: bool, /// A link to the track's last.fm page. pub url: Arc, /// True if the user has loved the track. pub loved: bool } /// The user-specified font request parameters #[derive(serde::Serialize, Debug)] #[serde(untagged)] pub enum Font { /// A font that requires additional CSS to load properly. External { css: Arc, name: Arc }, /// A font that is w3c standard, or widely installed. Name { name: Arc }, } /// The context passed in to all themes. /// /// Serialized as untagged, so themes should check if `error` is set and decide whether to show an error state or try rendering user info. #[derive(serde::Serialize, Debug)] #[serde(untagged)] pub enum Data { /// Contains text explaining a potential error. Error { error: &'static str }, /// Contains data about a user and what they're listening to. Data { user: User, scrobble: Scrobble, font: Option, query: BTreeMap, } } } #[derive(Debug)] pub struct ResponseCtx(pub model::Data, pub StatusCode); impl ResponseCtx { pub async fn create(api_result: Result, (StatusCode, &'static str)>, font_query: Option, query: BTreeMap) -> ResponseCtx { match api_result { Ok(a) => { let (user, track) = a.as_ref(); ResponseCtx(model::Data::Data { user: model::User { name: user.name.clone(), realname: user.realname.clone(), scrobble_count: user.playcount, artist_count: user.artist_count, track_count: user.track_count, album_count: user.track_count, image_url: user.images.iter().max_by(|a, b| a.size.cmp(&b.size)).map(|a| a.url.clone()).unwrap_or_else(|| "".into()), url: user.url.clone() }, scrobble: model::Scrobble { name: track.name.clone(), album: track.album.name.clone(), artist: model::Artist { name: track.artist.name.clone(), image_url: track.artist.images.iter().max_by(|a, b| a.size.cmp(&b.size)).map(|a| a.url.clone()).unwrap_or_else(|| "".into()), url: track.artist.url.clone().unwrap_or_else(|| "".into()) }, image_url: track.images.iter().max_by(|a, b| a.size.cmp(&b.size)).map(|a| a.url.clone()).unwrap_or_else(|| "".into()), now_playing: track.attr.nowplaying, url: track.url.clone(), loved: track.loved.unwrap_or(false) }, font: match font_query { Some(FontQuery { google_font: Some(f), .. }) if STATE.has_google_api_key() => { let css = match STATE.get_fontinfo(&f.to_string()).await { Ok(css) => css, Err((status, error)) => { return ResponseCtx(model::Data::Error {error}, status); } }; Some(model::Font::External { css, name: f }) }, Some(FontQuery { include_font: Some(f), .. }) => Some( model::Font::External { css: format!( "@font-face {{ font-family: 'included_font'; src: url('{}'); }}", f.replace("\\", "\\\\") .replace("'", "\\'")).into(), name: "included_font".into() }), Some(FontQuery { font: Some(s), .. }) => Some(model::Font::Name { name: s }), _ => None, }, query }, StatusCode::OK) }, Err((status, error)) => { ResponseCtx(model::Data::Error {error}, status) } } } }