use std::sync::Arc; use std::collections::BTreeMap; use reqwest::StatusCode; use super::deserialize as de; use super::config::CONFIG; use crate::cache::font::FontQuery; 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 }, } #[derive(serde::Serialize, Debug)] pub struct Data { pub user: User, pub scrobble: Scrobble, pub font: Option, pub query: BTreeMap, } /// 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 Root { /// Contains text explaining a potential error. Error { error: &'static str }, /// Contains data about a user and what they're listening to. Data { #[serde(flatten)] data: Box } } impl From for Root { fn from(v: Data) -> Self { Self::Data { data: Box::new(v) } } } } pub use model::Root as Ctx; #[derive(Debug)] pub struct ResponseCtx(pub model::Root, 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, trackstub) = a.as_ref(); ResponseCtx((model::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.title.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()).filter(|s| !s.is_empty()).unwrap_or_else(|| "https://lastfm.freetls.fastly.net/i/u/128s/4128a6eb29f94943c9d206c08e625904.jpg".into()), now_playing: trackstub.attr.nowplaying, url: track.url.clone(), loved: track.loved.unwrap_or(false) }, font: match font_query { Some(FontQuery { google_font: Some(f), .. }) if CONFIG.has_google_api_key() => { let css = match crate::cache::font::get_fontinfo(&f.to_string()).await { Ok(css) => css, Err((status, error)) => { return ResponseCtx(model::Root::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 }).into(), StatusCode::OK) }, Err((status, error)) => { ResponseCtx(model::Root::Error {error}, status) } } } }