diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.rs | 21 | ||||
| -rw-r--r-- | src/ctx.rs | 12 | ||||
| -rw-r--r-- | src/main.rs | 26 | ||||
| -rw-r--r-- | src/themes/plain.hbs | 34 | 
4 files changed, 73 insertions, 20 deletions
| 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<Arc<State>> = LazyLock::new(|| {      State::new()  });  fn getter(username: &String) -> Pin<Box<(dyn Future<Output = Result<Arc<(User, Track)>, (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)          })      } @@ -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<String, String> }      }  }  #[derive(Debug)]  pub struct ResponseCtx(pub model::Data, pub StatusCode); -impl From<Result<Arc<(de::User, de::Track)>, (StatusCode, &'static str)>> for ResponseCtx { -    fn from(v: Result<Arc<(de::User, de::Track)>, (StatusCode, &'static str)>) -> ResponseCtx { +impl From<(Result<Arc<(de::User, de::Track)>, (StatusCode, &'static str)>, BTreeMap<String, String>)> for ResponseCtx { +    fn from(v: (Result<Arc<(de::User, de::Track)>, (StatusCode, &'static str)>, BTreeMap<String, String>)) -> ResponseCtx { +        let (v, q) = v;          match v {              Ok(a) => {                  let (user, track) = a.as_ref(); @@ -111,7 +114,8 @@ impl From<Result<Arc<(de::User, de::Track)>, (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<Arc<str>> +    theme: Option<Arc<str>>, +    #[serde(flatten)] +    rest: BTreeMap<String, String>  }  #[tokio::main] @@ -35,17 +38,20 @@ async fn main() {      let user = warp::path!("user" / String)          .and(warp::query::<UserQuery>())          .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 @@ +<!doctype html> +<html lang="en" style="font-size: 0.5cm; margin: 0.5rem; overflow: hidden;"> +  <head> +    <meta charset="UTF-8"/> +    <title>{{#if error}}Error!{{else}}@{{user.name}}'s Last.fm Stats{{/if}}</title> +    <style> +      {{#if (eq query.dark null)}} +        :root { --b: black; color: black; backgrond-color: white; } +      {{else}} +        :root { --b: white; color: white; background-color: black; } +        a:visited { color: pink } +        a { color: cyan; } +      {{/if}} +      p { margin: 0px; padding: 0px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } +    </style> +  </head> +  <body> +    {{#if error}} +      <p style="white-space: unset;">{{error}}</p> +    {{else}} +      <a style="float: left;" target="_blank" href="{{scrobble.url}}"><img src="{{scrobble.image_url}}" style="height: 4.0rem; border: solid var(--b) 0.2rem; margin: 0.1rem;" /></a> +      <p><a target="_blank" href="{{user.url}}">@{{user.name}}</a>{{#if scrobble.now_playing}} +        is scrobbling{{else}}'s last scrobble was +      {{/if}} +      </p><p> +        <i><b><a target="_blank" href="{{scrobble.url}}">{{scrobble.name}}</a></b></i> +      </p><p> +        {{#if scrobble.album}}from <i><b>{{scrobble.album}}</b></i>{{/if}} +      </p><p> +        by <b><a target="_blank" href="{{scrobble.artist.url}}">{{scrobble.artist.name}}</a></b>. +      </p> +    {{/if}} +  </body> +</html>
\ No newline at end of file | 
