diff options
| -rw-r--r-- | README.md | 20 | ||||
| -rw-r--r-- | src/config.rs | 2 | ||||
| -rw-r--r-- | src/ctx.rs | 21 | ||||
| -rw-r--r-- | src/font.rs | 10 | ||||
| -rw-r--r-- | src/lib.rs | 1 | ||||
| -rw-r--r-- | src/main.rs | 6 | ||||
| -rw-r--r-- | src/themes/plain.hbs | 11 | 
7 files changed, 61 insertions, 10 deletions
| @@ -160,7 +160,15 @@ Themes should have, roughly, the structure below:  <html lang="en"> <!-- Or really whatever you want, but I speak English. -->      <head>          <meta charset="UTF-8"> -        <style> /* styles */ </style> +        <style> +          {{#if font.css}} +            {{{font.css}}} +          {{/if}} +          {{#if (or font.name font.css)}} +            * { font-family: '{{#if font.name}}{{font.name}}{{/if}}{{#if font.url}}included_font{{/if}}' } +          {{/if}} +          /* actual styles */ +        </style>      </head>      <body>          {{#if error}}<p>{{error}}</p>{{else}} @@ -225,7 +233,15 @@ If there was an error, the object will just be `{ error: String }`, otherwise it          // the most recently played track.          now_playing: Boolean,      }, -     + +    // Custom font info. +    font: { +        // Set if the theme should replace the custom font with something. +        name: String? +        // Will contain CSS which includes a custom font as 'included_font'. +        css: String? +    } +      // A set of extraneous query parameters.      //      // This should be considered UNTRUSTED, as the requester has full diff --git a/src/config.rs b/src/config.rs index 3b36ad2..4c5d4b7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -62,7 +62,7 @@ pub struct State {      http: Client,      default_refresh: Duration, -    whitelist_refresh: Duration +    whitelist_refresh: Duration,  }  impl State { @@ -2,6 +2,7 @@ use reqwest::StatusCode;  use super::deserialize as de;  use std::sync::Arc;  use std::collections::BTreeMap; +use super::font::FontQuery;  pub mod model {      use std::sync::Arc; @@ -66,6 +67,13 @@ pub mod model {          pub loved: bool      } +    #[derive(serde::Serialize, Debug)] +    #[serde(untagged)] +    pub enum Font { +        Url { css: Arc<str> }, +        Name { name: Arc<str> }, +    } +      /// 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. @@ -75,15 +83,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, query: BTreeMap<String, String> } +        Data { user: User, scrobble: Scrobble, font: Option<Font>, 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)>, 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; +impl From<(Result<Arc<(de::User, de::Track)>, (StatusCode, &'static str)>, Option<FontQuery>, BTreeMap<String, String>)> for ResponseCtx { +    fn from(v: (Result<Arc<(de::User, de::Track)>, (StatusCode, &'static str)>, Option<FontQuery>, BTreeMap<String, String>)) -> ResponseCtx { +        let (v, f, q) = v;          match v {              Ok(a) => {                  let (user, track) = a.as_ref(); @@ -115,6 +123,11 @@ impl From<(Result<Arc<(de::User, de::Track)>, (StatusCode, &'static str)>, BTree                          url: track.url.clone(),                          loved: track.loved.unwrap_or(false)                      }, +                    font: match f { +                        Some(FontQuery { font: Some(s), include_font: None }) => Some(model::Font::Name { name: s }), +                        Some(FontQuery { include_font: Some(s), .. }) => Some(model::Font::Url { css: format!("@font-face {{ font-family: 'included_font'; src: url('{}'); }}", s.replace("\\", "\\\\").replace("'", "\\'")).into() }), +                        _ => None, +                    },                      query: q                  }, StatusCode::OK)              }, diff --git a/src/font.rs b/src/font.rs new file mode 100644 index 0000000..f9f4f4d --- /dev/null +++ b/src/font.rs @@ -0,0 +1,10 @@ +use std::sync::Arc; + +#[derive(serde::Deserialize, Debug, Default)] +#[serde(default)] +#[serde(rename = "kebab-case")] +pub struct FontQuery { +  pub font: Option<Arc<str>>, +  pub include_font: Option<Arc<str>>, +//  pub small_font: Option<()> +} @@ -4,6 +4,7 @@ pub mod deserialize;  pub mod cache;  pub mod config;  pub mod ctx; +pub mod font;  pub use config::STATE;  pub use ctx::ResponseCtx; diff --git a/src/main.rs b/src/main.rs index b787049..f8b0e7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use std::collections::BTreeMap;  use std::fs::File;  use std::sync::Arc;  use lfm_embed::{STATE, ResponseCtx}; +use lfm_embed::font::FontQuery;  use warp::Filter;  #[derive(serde::Deserialize, Debug)] @@ -13,6 +14,9 @@ struct UserQuery {      #[serde(default)]      theme: Option<Arc<str>>,      #[serde(flatten)] +    #[serde(default)] +    font: Option<FontQuery>, +    #[serde(flatten)]      rest: BTreeMap<String, String>  } @@ -40,7 +44,7 @@ async fn main() {          .then(|s, q: UserQuery| async move {              log::debug!(target: "lfm::server::user", "Handling request for user `{s}` with {q:?}");              let (ctx, dur) = STATE.get_userinfo(&s).await; -            let ResponseCtx(mut data, status) = (ctx, q.rest).into(); +            let ResponseCtx(mut data, status) = (ctx, q.font, 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}"); diff --git a/src/themes/plain.hbs b/src/themes/plain.hbs index fe33d28..bbd0589 100644 --- a/src/themes/plain.hbs +++ b/src/themes/plain.hbs @@ -1,5 +1,5 @@  <!doctype html> -<html lang="en" style="font-size: 0.5cm; margin: 0.5rem; overflow: hidden;"> +<html lang="en" style="margin: 20px; overflow: hidden;">    <head>      <meta charset="UTF-8"/>      <title>{{#if error}}Error!{{else}}@{{user.name}}'s Last.fm Stats{{/if}}</title> @@ -11,6 +11,13 @@          a:visited { color: pink }          a { color: cyan; }        {{/if}} +      * { font-size: {{#if font}}17px{{else}}20px{{/if}}; } +      {{#if font.css}} +        {{{font.css}}} +      {{/if}} +      {{#if (or font.name font.css)}} +        * { font-family: '{{#if font.name}}{{font.name}}{{/if}}{{#if font.url}}included_font{{/if}}' } +      {{/if}}        p { margin: 0px; padding: 0px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }      </style>    </head> @@ -18,7 +25,7 @@      {{#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> +      <a style="float: left;" target="_blank" href="{{scrobble.url}}"><img src="{{scrobble.image_url}}" style="height: 80px; border: solid var(--b) 4px; margin: 2px;" /></a>        <p><a target="_blank" href="{{user.url}}">@{{user.name}}</a>{{#if scrobble.now_playing}}          is scrobbling{{else}}'s last scrobble was        {{/if}} | 
