aboutsummaryrefslogblamecommitdiffstats
path: root/README.md
blob: 4d5ae8f4f0722d403122962a5857d9af94058519 (plain) (tree)
1
2
3
4
5
6
7
8
9



                                                                                                               
                                                                          


                                                                                                                  
 
       




















                                                                                                                                                                    
                                        

              

                                                                                                                                 
                                                                                                                                       


                                                                                         
               



                                                                          

              


                                                                                                                



                                    
                                                                                                                     











                                                                                                                       
                                             
                                                     
 
                                       
 



                                                                                                                                                            
                                    


                                                                             
                                              






                                                                                                                                                              
                                            


                                                          
         
 

                                                                                                                                                       


                                                                                                                              

                                                             



















                                                                                                          
                                      

                                                  

                                                                                                                   








                                                                                                                                                                              
                        
      







                                                                      
 















                                                                                                          








                                                                                                          































































                                                                                                       








                                                                            





























                                                                                                                                                                                                     


                                                                                                                                   
 
                                                                                                             

       
      












                                                                          
# `lfm_embed`
A simple webserver for rendering a last.fm embed.

More specifically, this displays a simple webpage with info about your current or most recent last.fm scrobble.
Its intended use is to be put in an iframe on a personal site or whatever.
While it is self-hostable, there is an official public instance at [scrobble.observer](https://scrobble.observer).

Release builds available [here](https://aleteoryx.me/downloads2/lfm_embed).

# Usage

`lfm_embed` serves one thing:
Requests to `/user/<last.fm username>` will generate a page displaying your current or last last.fm scrobble.
The look and feel of this is down to the configured theme for your request.

As the user, you may select a theme supported by the server by passing `?theme=` query string.
Check with your host for a list of supported themes.

Individual themes may also support parameters, via additional query parameters.

## Builtin Themes

### `plain`

A minimal theme, displaying just the username, album art, and track info.
It is the default fallback for an unset or unknown theme.

![A screenshot of the plain theme, displaying album art, and the text "@vvinrg is scrobbling Sweet Tooth from Sleepyhead by Cavetown".](screenshots/plain-light.png)

#### Parameters

- `?dark`: toggle the theme's dark mode.

# Self-Hosting
`lfm_embed` is, as it stands, designed to use a reverse proxy.
A future release may include HTTPS support, but currently, it will only listen on `localhost`, prohibiting it from public access.
Once configured, a request of the form `http://localhost:9999/user/<last.fm username>` will render out an embed with the default theme.

As it stands, there are no plans to support displaying users with private listen history.

# Configuration
Configuration should be done via the environment.
`lfm_embed` also supports reading from a standard `.env` file.
The following are the environment variables which `lfm_embed` understands.

## Core Config

### `LMFE_API_KEY` (Required)
Your last.fm API key. You'll need to create one [here](https://www.last.fm/api/account/create) for self-hosting.

### `LFME_PORT` (Default: `9999`)
The port to serve on locally.

### `LFME_NO_REFRESH` (Default: `0`)
If set to `1`, disable outputting of the HTTP `Refresh: <cache refresh>` header, used to provide live status updates.

## Logging

### `LFME_LOG_LEVEL` (Default: `"warn"`)
The loglevel. This is actually parsed as an [`env_logger`](https://docs.rs/env_logger/latest/env_logger) filter string.
Read the docs for more info.

### `LFME_LOG_FILE`
If set, logs will be written to the specified file. Otherwise, logs are written to `stderr`.

## User Perms

### `LFME_WHITELIST_MODE` (Default: `"open"`)
The following(case-insensitive) values are supported:

- `open`: Allow requests for all users.

- `exclusive`: Only allow requests for users in `LFME_WHITELIST`, returning HTTP 403 for all others. `LFME_WHITELIST` _must_ be set if this mode is enabled.

If the user requested has their listen history private, a 403 will be returned.

### `LFME_WHITELIST` (Default: `""`)
This is expected to be a sequence of comma-separated usernames.
Leading/trailing whitespace will be stripped, and unicode is fully supported.

### `LFME_WHITELIST_REFRESH` (Default: `"1m"`)
The amount of time to cache whitelisted user info for.
It is interpreted as a sequence of `<num><suffix>` values, where `num` is a positive integer,
and `suffix` is one of `y`,`mon`,`w`,`d`,`h`,`m`,`s`, `ms`, `µs`, or `ns`, each of which
corresponds to a self-explanatory unit of time.
For most practical applications, one should only use `m` and `s`, as caching your current listen for more than that time has the potential to omit most songs.
Parsing is delegated to the [`duration_str`](https://docs.rs/duration-str/latest/duration_str/) crate, and further info may be found there.

### `LFME_DEFAULT_REFRESH` (Default: `"5m"`)
The amount of time to cache non-whitelisted user info for.
See `LFME_WHITELIST_REFRESH` for more info.

## Themes

### `LFME_THEME_DIR`
If set, must be a valid path to a directory containing [Handlebars](https://handlebarsjs.com/guide/#language-features) files, ending in LFME_THEME_EXT.
They will be registered as themes on top of the builtin ones, with each theme's name being their filename minus the extension.
Same-named themes will override builtin ones.

See [Theming](#theming) for details on writing custom themes.

Theme names are the same as their path, minus the extension. Given an extension of .hbs, a directory like:
```
themes/
  mytheme.hbs
  myothertheme.hbs
  myunrelatedfile.css
  alices-themes/
    mytheme.hbs
    mysuperawesometheme.hbs
```
results in the following themes:
```
mytheme
myothertheme
alices-themes/mytheme
alices-themes/mysuperawesometheme
```

By default, these are loaded and compiled once, at startup.

### `LFME_THEME_EXT` (Default: `.hbs`)
The file extension for themes in `LFME_THEME_DIR`.

Note: This behaves more like a suffix than a file extension. You must include the leading dot for a file extension.

### `LFME_THEME_DEV` (Default: `0`)
If set to `1`, existing themes will be reloaded on edit.

Note: Even with this mode, adding a new theme requires a full reload.
Themes are only enumerated once, at startup. (This is a limitation of the [`handlebars`](https://docs.rs/handlebars/latest/handlebars) implementation in use, and may change.)

### `LFME_THEME_DEFAULT` (Default: `"plain"`)
The theme to use when no query string is present.

## Example Configuration
```ini
LFME_API_KEY=0123456789abcdef0123456789abcdef
LFME_LOG_LEVEL=error
LFME_PORT=3000

LFME_WHITELIST_REFRESH=30s
LFME_WHITELIST_MODE=exclusive
LFME_WHITELIST=a_precious_basket_case, realRiversCuomo, Pixiesfan12345
```

# Theming {#theming}

Custom themes are, as stated above, [Handlebars](https://handlebarsjs.com/guide/#language-features) files.
They are expected to produce a complete HTML doc, which should contain info about a user's scrobbles.

See `src/themes` for examples of implementation.

## Writing a Theme

Themes should have, roughly, the structure below:

```html
<!DOCTYPE html>
<html lang="en"> <!-- Or really whatever you want, but I speak English. -->
    <head>
        <meta charset="UTF-8">
        <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}}
            <!-- theme contents here -->
        {{/if}}
    </body>
</html>
```

You will be passed one of 2 context objects, depending on if there was an error processing the request.
If there was an error, the object will just be `{ error: String }`, otherwise it will be the following:

```js
{
    // The user the request was made on the behalf of.
    user: {
        // Their username.
        name: String,
        // Their "Display Name".
        realname: String,
        // Link to user's profile picture.
        image_url: String,
        // Link to user's profile.
        url: String,
        
        // Total scrobbles.
        scrobble_count: Number,
        // Number of artists in library.
        artist_count: Number,
        // Number of tracks in library.
        track_count: Number,
        // Number of albums in library.
        album_count: Number,
        
        // True if user subscribes to last.fm pro.
        pro_subscriber: Boolean
    },
    
    // The user's most current, or most scrobble.
    scrobble: {
        // The name of the track.
        name: String,
        // The name of its album.
        album: String,
        // The artist who made it.
        artist: {
            // Their name.
            name: String,
            // Link to their profile image.
            image_url: String,
            // Link to the artist's last.fm page.
            url: String
        },
        // A link to the track image.
        image_url: String,
        // A link to the track's last.fm page.
        url: String,

        // True if the user has loved the track.
        loved: Boolean
        // True if the user is currently scrobbling it, false if it's just
        // 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
    // control over it. Use the provided `html_escape`,
    // `html_attr_escape`, and `uri_encode` helpers when inlining
    // any contained text.
    query: Object
}
```

In addition, the following [helpers](https://handlebarsjs.com/guide/expressions.html#helpers) are provided, on top of the handlebars [builtins](https://handlebarsjs.com/guide/builtin-helpers.html):

- `(eq Object Object) => Boolean`: Check equality between its args.

- `(ne Object Object) => Boolean`: Check inequality between its args.

- `(gt Number|String Number|String) => Boolean`: Check if the first arg is greater than the second.

- `(gte Number|String Number|String) => Boolean`: Check if the first arg is greater than or equal to the second.

- `(lt Number|String Number|String) => Boolean`: Check if the first arg is less than the second.

- `(lte Number|String Number|String) => Boolean`: Check if the first arg is less than or equal to the second.

- `(and Boolean Boolean) => Boolean`: Boolean AND gate.

- `(or Boolean Boolean) => Boolean`: Boolean OR gate.

- `(not Boolean) => Boolean`: Boolean NOT gate.

- `(uri_encode String) => String`: URI-encode input text, making the output suitable to be included as part of a link or other URL.

# Contributing

[E-Mail me](mailto:alyx@aleteoryx.me) if you'd like to help out, submit a custom theme, or request a feature.

# Legal
```txt
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
```