From 9edafd94b00a73662d51824dbcba0a018e2140cf Mon Sep 17 00:00:00 2001 From: alyx Date: Thu, 10 Aug 2023 20:45:01 -0400 Subject: Create plain theme, do some escaping, fix some busted envvars, document themes. --- src/config.rs | 21 +++++++++++++++------ src/ctx.rs | 12 ++++++++---- src/main.rs | 26 ++++++++++++++++---------- src/themes/plain.hbs | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 20 deletions(-) create mode 100644 src/themes/plain.hbs (limited to 'src') diff --git a/src/config.rs b/src/config.rs index aad298f..ab92108 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,17 +11,17 @@ use super::deserialize::{GetRecentTracks, GetUserInfo, Track, User}; use reqwest::{Client, StatusCode}; use dotenv::var; use tokio::sync::RwLock; -use handlebars::Handlebars; +use handlebars::{Handlebars, handlebars_helper}; use duration_str as ds; -static INTERNAL_THEMES: &[(&'static str, &'static str)] = &[("plain", "")]; +static INTERNAL_THEMES: &[(&'static str, &'static str)] = &[("plain", include_str!("themes/plain.hbs"))]; pub static STATE: LazyLock> = LazyLock::new(|| { State::new() }); fn getter(username: &String) -> Pin, (StatusCode, &'static str)>> + Send + Sync)>> { - let username = username.clone(); + let username = urlencoding::encode(username.as_ref()).to_string(); Box::pin(async move { let userreq = STATE.http.get(format!("https://ws.audioscrobbler.com/2.0/?method=user.getInfo&format=json&user={username}&api_key={}", STATE.api_key)) .send().await @@ -84,6 +84,15 @@ impl State { handlebars: { let mut hb = Handlebars::new(); + + handlebars_helper!(html_escape: |s: String| htmlize::escape_text(s)); + handlebars_helper!(html_attr_escape: |s: String| htmlize::escape_attribute(s)); + handlebars_helper!(url_encode: |s: String| urlencoding::encode(&s)); + + hb.register_helper("html-escape", Box::new(html_escape)); + hb.register_helper("html-attr-escape", Box::new(html_attr_escape)); + hb.register_helper("url-encode", Box::new(html_attr_escape)); + for (key, fulltext) in INTERNAL_THEMES { log::info!(target: "lfm::config::theme", "Registering internal theme `{key}`"); hb.register_template_string(key, fulltext).unwrap(); @@ -92,7 +101,7 @@ impl State { if let Ok(themes_dir) = var("LFME_THEME_DIR") { log::info!(target: "lfm::config::theme", "Registering theme dir `{themes_dir}`"); - hb.register_templates_directory(&var("LFME_THEME_EXT").unwrap_or_else(|_| "hbs".into()), themes_dir).unwrap(); + hb.register_templates_directory(&var("LFME_THEME_EXT").unwrap_or_else(|_| ".hbs".into()), themes_dir).unwrap(); } hb @@ -118,8 +127,8 @@ impl State { } } }, - default_refresh: default_refresh + Duration::from_secs(5), - whitelist_refresh: whitelist_refresh + Duration::from_secs(5) + default_refresh: default_refresh + Duration::from_secs(1), + whitelist_refresh: whitelist_refresh + Duration::from_secs(1) }) } diff --git a/src/ctx.rs b/src/ctx.rs index 6beb634..63b54ff 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -1,9 +1,11 @@ use reqwest::StatusCode; use super::deserialize as de; use std::sync::Arc; +use std::collections::BTreeMap; pub mod model { use std::sync::Arc; + use std::collections::BTreeMap; /// The theme representation of a user. #[derive(serde::Serialize, Debug)] @@ -73,14 +75,15 @@ pub mod model { /// 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 } + Data { user: User, scrobble: Scrobble, query: BTreeMap } } } #[derive(Debug)] pub struct ResponseCtx(pub model::Data, pub StatusCode); -impl From, (StatusCode, &'static str)>> for ResponseCtx { - fn from(v: Result, (StatusCode, &'static str)>) -> ResponseCtx { +impl From<(Result, (StatusCode, &'static str)>, BTreeMap)> for ResponseCtx { + fn from(v: (Result, (StatusCode, &'static str)>, BTreeMap)) -> ResponseCtx { + let (v, q) = v; match v { Ok(a) => { let (user, track) = a.as_ref(); @@ -111,7 +114,8 @@ impl From, (StatusCode, &'static str)>> for Re now_playing: track.attr.nowplaying, url: track.url.clone(), loved: track.loved.unwrap_or(false) - } + }, + query: q }, StatusCode::OK) }, Err((status, error)) => { diff --git a/src/main.rs b/src/main.rs index 729482f..b787049 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use dotenv::var; use log::LevelFilter; +use std::collections::BTreeMap; use std::fs::File; use std::sync::Arc; use lfm_embed::{STATE, ResponseCtx}; @@ -10,7 +11,9 @@ use warp::Filter; #[derive(serde::Deserialize, Debug)] struct UserQuery { #[serde(default)] - theme: Option> + theme: Option>, + #[serde(flatten)] + rest: BTreeMap } #[tokio::main] @@ -35,17 +38,20 @@ async fn main() { let user = warp::path!("user" / String) .and(warp::query::()) .then(|s, q: UserQuery| async move { - log::info!(target: "lfm::server::user", "Handling request for user `{s}` with {q:?}"); + log::debug!(target: "lfm::server::user", "Handling request for user `{s}` with {q:?}"); let (ctx, dur) = STATE.get_userinfo(&s).await; - let ResponseCtx(data, status) = ctx.into(); - - let theme = q.theme.unwrap_or_else(|| STATE.default_theme()); + let ResponseCtx(mut data, status) = (ctx, q.rest).into(); + + let theme = q.theme.filter(|a| STATE.handlebars().has_template(&a)).unwrap_or_else(|| STATE.default_theme()); + log::debug!(target: "lfm::server::user", "Using theme {theme}"); warp::reply::with_header( - warp::reply::with_status( - warp::reply::html( - STATE.handlebars().render(&theme, &data).unwrap() - ), status - ), "Refresh", dur.as_secs() + warp::reply::with_header( + warp::reply::with_status( + warp::reply::html( + STATE.handlebars().render(&theme, &data).unwrap() + ), status + ), "Refresh", dur.as_secs() + ), "X-Selected-Theme", theme.as_ref() ) }); diff --git a/src/themes/plain.hbs b/src/themes/plain.hbs new file mode 100644 index 0000000..cbe8948 --- /dev/null +++ b/src/themes/plain.hbs @@ -0,0 +1,34 @@ + + + + + {{#if error}}Error!{{else}}@{{user.name}}'s Last.fm Stats{{/if}} + + + + {{#if error}} +

{{error}}

+ {{else}} + +

@{{user.name}}{{#if scrobble.now_playing}} + is scrobbling{{else}}'s last scrobble was + {{/if}} +

+ {{scrobble.name}} +

+ {{#if scrobble.album}}from {{scrobble.album}}{{/if}} +

+ by {{scrobble.artist.name}}. +

+ {{/if}} + + \ No newline at end of file -- cgit v1.2.3-54-g00ecf