add: rewrite root html

This commit is contained in:
trisua 2025-05-31 10:17:49 -04:00
parent 78d0766345
commit 350e47f4b7
7 changed files with 405 additions and 442 deletions

4
Cargo.lock generated
View file

@ -339,9 +339,9 @@ dependencies = [
[[package]] [[package]]
name = "bberry" name = "bberry"
version = "0.1.1" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9413ba3ccb3dfc908093b3e2cdb7b39acc2127fe0b29268df4708cfa06c6ce30" checksum = "d73b8674872fdd2eb09463fc9e3d15c4510a3abd8a1f8ef0a9e918065913aed5"
[[package]] [[package]]
name = "bit_field" name = "bit_field"

View file

@ -48,4 +48,4 @@ async-stripe = { version = "0.41.0", features = [
] } ] }
emojis = "0.6.4" emojis = "0.6.4"
webp = "0.3.0" webp = "0.3.0"
bberry = "0.1.1" bberry = "0.1.2"

View file

@ -36,7 +36,7 @@ pub const ME_JS: &str = include_str!("./public/js/me.js");
pub const STREAMS_JS: &str = include_str!("./public/js/streams.js"); pub const STREAMS_JS: &str = include_str!("./public/js/streams.js");
// html // html
pub const ROOT: &str = include_str!("./public/html/root.html"); pub const ROOT: &str = include_str!("./public/html/root.lisp");
pub const MACROS: &str = include_str!("./public/html/macros.html"); pub const MACROS: &str = include_str!("./public/html/macros.html");
pub const COMPONENTS: &str = include_str!("./public/html/components.html"); pub const COMPONENTS: &str = include_str!("./public/html/components.html");
@ -140,7 +140,13 @@ pub(crate) async fn pull_icon(icon: &str, icons_dir: &str) {
} }
println!("download icon: {icon}"); println!("download icon: {icon}");
let svg = reqwest::get(icon_url).await.unwrap().text().await.unwrap(); let svg = reqwest::get(icon_url)
.await
.unwrap()
.text()
.await
.unwrap()
.replace("\n", "");
write(&file_path, &svg).unwrap(); write(&file_path, &svg).unwrap();
writer.insert(icon.to_string(), svg); writer.insert(icon.to_string(), svg);
@ -245,7 +251,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD {
// ... // ...
let html_path = PathBufD::current().join(&config.dirs.templates); let html_path = PathBufD::current().join(&config.dirs.templates);
write_template!(html_path->"root.html"(crate::assets::ROOT) --config=config); write_template!(html_path->"root.html"(crate::assets::ROOT) --config=config --lisp);
write_template!(html_path->"macros.html"(crate::assets::MACROS) --config=config); write_template!(html_path->"macros.html"(crate::assets::MACROS) --config=config);
write_template!(html_path->"components.html"(crate::assets::COMPONENTS) --config=config); write_template!(html_path->"components.html"(crate::assets::COMPONENTS) --config=config);

View file

@ -3,24 +3,24 @@
(text "{% endblock %} {% block body %} {{ macros::nav() }}") (text "{% endblock %} {% block body %} {{ macros::nav() }}")
(main (main
(: "class" "flex flex-col gap-2") ("class" "flex flex-col gap-2")
(div (div
(: "class" "card-nest") ("class" "card-nest")
(div (div
(: "class" "card") ("class" "card")
(b (text "Error 😦"))) (b (text "Error 😦")))
(div (div
(: "class" "card flex flex-col gap-4") ("class" "card flex flex-col gap-4")
(span (text "{{ error_text }}")) (span (text "{{ error_text }}"))
(div (div
(: "class" "w-full flex gap-2") ("class" "w-full flex gap-2")
(a (a
(: "class" "button primary") (: "href" "/") ("class" "button primary") ("href" "/")
(text "Home")) (text "Home"))
(a (a
(: "class" "button secondary") (: "href" "javascript:history.back()") ("class" "button secondary") ("href" "javascript:history.back()")
(text "Back")))))) (text "Back"))))))
(text "{% endblock %}") (text "{% endblock %}")

View file

@ -3,18 +3,18 @@
(text "{% endblock %} {% block body %} {{ macros::nav() }}") (text "{% endblock %} {% block body %} {{ macros::nav() }}")
(main (main
(: "class" "flex flex-col gap-2") ("class" "flex flex-col gap-2")
(div (div
(: "class" "card-nest") ("class" "card-nest")
(div (div
(: "class" "card small flex items-center justify-between gap-2") ("class" "card small flex items-center justify-between gap-2")
(span (span
(: "class" "flex items-center gap-2") ("class" "flex items-center gap-2")
(text "{{ icon scroll-text }}") (text "{{ icon scroll-text }}")
(span (text "{{ file_name }}")))) (span (text "{{ file_name }}"))))
(div (div
(: "class" "card") ("class" "card")
(span (text "{{ file|markdown|safe }}"))))) (span (text "{{ file|markdown|safe }}")))))
(text "{% endblock %}") (text "{% endblock %}")

View file

@ -1,424 +0,0 @@
{%- import "components.html" as components -%} {%- import "macros.html" as
macros -%}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta
http-equiv="content-security-policy"
content="default-src 'self' blob: *.spotify.com musicbrainz.org; img-src * data:; media-src *; font-src *; style-src 'unsafe-inline' 'self' blob: *; script-src 'self' 'unsafe-inline' blob: *; object-src 'self' blob: *; upgrade-insecure-requests; connect-src * localhost; frame-src 'self' blob: data: *"
/>
<link rel="icon" href="/public/favicon.svg" />
<link rel="stylesheet" href="/css/style.css" />
{% if user -%}
<script>
window.localStorage.setItem(
"tetratto:theme",
"{{ user.settings.theme_preference }}",
);
</script>
{%- endif %}
<script src="/js/loader.js"></script>
<script defer async src="/js/atto.js"></script>
<script>
globalThis.ns_verbose = false;
globalThis.ns_config = {
root: "/js/",
verbose: globalThis.ns_verbose,
version: "cache-breaker-{{ random_cache_breaker }}",
};
globalThis._app_base = {
name: "tetratto",
ns_store: {},
classes: {},
};
globalThis.no_policy = false;
</script>
<meta name="theme-color" content="{{ config.color }}" />
<meta name="description" content="{{ config.description }}" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="{{ config.name }}" />
<meta name="turbo-prefetch" content="false" />
<meta name="turbo-refresh-method" content="morph" />
<meta name="turbo-refresh-scroll" content="preserve" />
<script
src="https://unpkg.com/@hotwired/turbo@8.0.5/dist/turbo.es2017-esm.js"
type="module"
async
defer
></script>
{% block head %}{% endblock %}
</head>
<body>
<div id="toast_zone"></div>
<div id="page">
<!-- prettier-ignore -->
{% if user and user.id == 0 -%}
<article>
<main>
<div class="card-nest">
<div class="card small flex items-center gap-2 red">
{{ icon "frown" }}
<span
>{{ text "general:label.account_banned" }}</span
>
</div>
<div class="card">
<span
>{{ text "general:label.account_banned_body"
}}</span
>
</div>
</div>
</main>
</article>
{% else %} {% block body %}{% endblock %} {%- endif %}
<!-- html_footer_goes_here -->
</div>
<script data-turbo-permanent="true" id="init-script">
document.documentElement.addEventListener("turbo:load", () => {
const atto = ns("atto");
atto.disconnect_observers();
atto.remove_false_options();
atto.clean_date_codes();
atto.link_filter();
atto["hooks::scroll"](document.body, document.documentElement);
atto["hooks::dropdown.init"](window);
atto["hooks::character_counter.init"]();
atto["hooks::long_text.init"]();
atto["hooks::alt"]();
atto["hooks::online_indicator"]();
atto["hooks::ips"]();
atto["hooks::check_reactions"]();
atto["hooks::tabs"]();
atto["hooks::spotify_time_text"](); // spotify durations
atto["hooks::verify_emoji"]();
if (document.getElementById("tokens")) {
trigger("me::render_token_picker", [
document.getElementById("tokens"),
]);
}
setTimeout(() => {
trigger("me::notifications_stream");
}, 250);
});
</script>
{% if user -%}
<script data-turbo-permanent="true" id="update-seen-script">
document.documentElement.addEventListener("turbo:load", () => {
trigger("me::seen");
trigger("streams::user", ["{{ user.id }}"]);
if (!window.location.pathname.startsWith("/chats/")) {
if (window.socket) {
window.socket.send("Close");
window.socket = undefined;
console.log("socket disconnect");
}
}
});
</script>
{%- endif %}
<!-- dialogs -->
<dialog id="link_filter">
<div class="inner flex flex-col gap-2">
<p>Pressing continue will bring you to the following URL:</p>
<pre><code id="link_filter_url"></code></pre>
<p>Are sure you want to go there?</p>
<hr class="margin" />
<div class="flex gap-2">
<a
class="button primary"
id="link_filter_continue"
rel="noopener noreferrer"
target="_blank"
onclick="document.getElementById('link_filter').close()"
>
{{ icon "external-link" }}
<span>{{ text "dialog:action.continue" }}</span>
</a>
<button
class="secondary"
type="button"
onclick="document.getElementById('link_filter').close()"
>
{{ icon "x" }}
<span>{{ text "dialog:action.cancel" }}</span>
</button>
</div>
</div>
</dialog>
<dialog id="web_api_prompt">
<div class="inner flex flex-col gap-2">
<form
class="flex gap-2 flex-col"
onsubmit="event.preventDefault()"
>
<label for="prompt" id="web_api_prompt:msg"></label>
<input id="prompt" name="prompt" />
<div class="flex justify-between">
<div></div>
<div class="flex gap-2">
<button
class="primary bold circle"
onclick="globalThis.web_api_prompt_submit(document.getElementById('prompt').value); document.getElementById('prompt').value = ''"
type="button"
>
{{ icon "check" }} {{ text "dialog:action.okay"
}}
</button>
<button
class="bold red camo"
onclick="globalThis.web_api_prompt_submit('')"
type="button"
>
{{ icon "x" }} {{ text "dialog:action.cancel" }}
</button>
</div>
</div>
</form>
</div>
</dialog>
<dialog id="web_api_prompt_long">
<div class="inner flex flex-col gap-2">
<form
class="flex gap-2 flex-col"
onsubmit="event.preventDefault()"
>
<label
for="prompt_long"
id="web_api_prompt_long:msg"
></label>
<textarea id="prompt_long" name="prompt_long"></textarea>
<div class="flex justify-between">
<div></div>
<div class="flex gap-2">
<button
class="primary bold circle"
onclick="globalThis.web_api_prompt_long_submit(document.getElementById('prompt_long').value); document.getElementById('prompt_long').value = ''"
type="button"
>
{{ icon "check" }} {{ text "dialog:action.okay"
}}
</button>
<button
class="bold red camo"
onclick="globalThis.web_api_prompt_long_submit('')"
type="button"
>
{{ icon "x" }} {{ text "dialog:action.cancel" }}
</button>
</div>
</div>
</form>
</div>
</dialog>
<dialog id="web_api_confirm">
<div class="inner flex flex-col gap-2">
<form
class="flex gap-2 flex-col"
onsubmit="event.preventDefault()"
>
<label id="web_api_confirm:msg"></label>
<div class="flex justify-between">
<div></div>
<div class="flex gap-2">
<button
class="primary bold circle"
onclick="globalThis.web_api_confirm_submit(true)"
type="button"
>
{{ icon "check" }} {{ text "dialog:action.yes"
}}
</button>
<button
class="bold red camo"
onclick="globalThis.web_api_confirm_submit(false)"
type="button"
>
{{ icon "x" }} {{ text "dialog:action.no" }}
</button>
</div>
</div>
</form>
</div>
</dialog>
<div class="lightbox hidden" id="lightbox">
<button
class="lightbox_exit small square quaternary red"
onclick="trigger('ui::lightbox_close')"
>
{{ icon "x" }}
</button>
<a href="" id="lightbox_img_a" target="_blank">
<img src="" alt="Image" id="lightbox_img" />
</a>
</div>
{% if user -%}
<dialog id="tokens_dialog">
<div class="inner flex flex-col gap-2">
<form
class="flex gap-2 flex-col"
onsubmit="event.preventDefault()"
>
<div id="tokens" style="display: contents"></div>
<a href="/auth/login" class="button" data-turbo="false">
{{ icon "plus" }}
<span>{{ text "general:action.add_account" }}</span>
</a>
<div class="flex justify-between">
<div></div>
<div class="flex gap-2">
<button
class="quaternary"
onclick="document.getElementById('tokens_dialog').close()"
type="button"
>
{{ icon "check" }}
<span>{{ text "dialog:action.okay" }}</span>
</button>
</div>
</div>
</form>
</div>
</dialog>
{%- endif %} {% if user and use_user_theme -%} {{
components::theme(user=user,
theme_preference=user.settings.theme_preference) }}
<script>
setTimeout(() => {
trigger("atto::use_theme_preference");
}, 150);
</script>
{%- endif %} {% if user and user.connections.Spotify and
config.connections.spotify_client_id and
user.connections.Spotify[0].data.token and
user.connections.Spotify[0].data.refresh_token %}
<script>
setTimeout(async () => {
if (window.spotify_init) {
return;
}
window.spotify_init = true;
const client_id = "{{ config.connections.spotify_client_id }}";
let token = "{{ user.connections.Spotify[0].data.token }}";
let refresh_token =
"{{ user.connections.Spotify[0].data.refresh_token }}";
if (token) {
// we already have a token
const pull_playing = async () => {
const playing = await trigger("spotify::get_playing", [
token,
]);
if (playing.error) {
// refresh token
const [new_token, new_refresh_token, expires_in] =
await trigger("spotify::refresh_token", [
client_id,
refresh_token,
]);
await trigger("connections::push_con_data", [
"Spotify",
{
token: new_token,
expires_in: expires_in.toString(),
name: profile.display_name,
},
]);
token = new_token;
refresh_token = new_refresh_token;
return;
}
await trigger("spotify::publish_playing", [playing]);
};
await pull_playing();
setInterval(pull_playing, 30_000);
} else {
window.spotify_needs_token = true;
}
}, 150);
</script>
{% elif user and user.connections.LastFm and
config.connections.last_fm_key and
user.connections.LastFm[0].data.session_token %}
<script>
setTimeout(async () => {
if (window.last_fm_init) {
return;
}
window.last_fm_init = true;
const user = "{{ user.connections.LastFm[0].data.name }}";
const session_token =
"{{ user.connections.LastFm[0].data.session_token }}";
if (session_token) {
// we already have a token
const pull_playing = async () => {
const playing = await trigger("last_fm::get_playing", [
user,
session_token,
]);
await trigger("last_fm::publish_playing", [playing]);
};
await pull_playing();
setInterval(pull_playing, 30_000);
} else {
window.last_fm_needs_token = true;
}
}, 150);
</script>
{%- endif %}
</body>
</html>

View file

@ -0,0 +1,381 @@
(text "{%- import \"components.html\" as components -%} {%- import \"macros.html\" as macros -%}")
(text "<!doctype html>")
(html
("lang" "en")
(head
(meta ("charset" "UTF-8"))
(meta ("name" "viewport") ("content" "width=device-width, initial-scale=1.0"))
(meta ("http-equiv" "X-UA-Compatible") ("content" "ie=edge"))
(meta ("http-equiv" "content-security-policy") ("content" "default-src 'self' blob: *.spotify.com musicbrainz.org; img-src * data:; media-src *; font-src *; style-src 'unsafe-inline' 'self' blob: *; script-src 'self' 'unsafe-inline' blob: *; object-src 'self' blob: *; upgrade-insecure-requests; connect-src * localhost; frame-src 'self' blob: data: *"))
(link ("rel" "icon") ("href" "/public/favicon.svg"))
(link ("rel" "stylesheet") ("href" "/css/style.css"))
(text "{% if user -%}
<script>
window.localStorage.setItem(
\"tetratto:theme\",
\"{{ user.settings.theme_preference }}\",
);
</script>
{%- endif %}")
(text "<script>
globalThis.ns_verbose = false;
globalThis.ns_config = {
root: \"/js/\",
verbose: globalThis.ns_verbose,
version: \"cache-breaker-{{ random_cache_breaker }}\",
};
globalThis._app_base = {
name: \"tetratto\",
ns_store: {},
classes: {},
};
globalThis.no_policy = false;
</script>")
(script ("src" "/js/loader.js" ))
(script ("src" "/js/atto.js" ))
(meta ("name" "theme-color") ("content" "{{ config.color }}"))
(meta ("name" "description") ("content" "{{ config.description }}"))
(meta ("property" "og:type") ("content" "website"))
(meta ("property" "og:site_name") ("content" "{{ config.color }}"))
(meta ("name" "turbo-prefetch") ("content" "false"))
(meta ("name" "turbo-refresh-method") ("content" "morph"))
(meta ("name" "turbo-refresh-scroll") ("content" "preserve"))
(script ("src" "https://unpkg.com/@hotwired/turbo@8.0.5/dist/turbo.es2017-esm.js") ("type" "module") ("async" "") ("defer" ""))
(text "{% block head %}{% endblock %}"))
(body
(div ("id" "toast_zone"))
(div
("id" "page")
(text "{% if user and user.id == 0 -%}")
; account banned message
(article
(main
(div
("class" "card-nest")
(div
("class" "card small flex items-center gap-2 red")
(text "{{ icon \"frown\" }}")
(span (text "{{ text \"general:label.account_banned\" }}")))
(div
("class" "card")
(span (text "{{ text \"general:label.account_banned_body\" }}"))))))
; if we aren't banned, just show the page body
(text "{% else %} {% block body %}{% endblock %} {%- endif %}")
(text "<!-- html_footer_goes_here -->"))
; random js
(text "<script data-turbo-permanent=\"true\" id=\"init-script\">
document.documentElement.addEventListener(\"turbo:load\", () => {
const atto = ns(\"atto\");
atto.disconnect_observers();
atto.remove_false_options();
atto.clean_date_codes();
atto.link_filter();
atto[\"hooks::scroll\"](document.body, document.documentElement);
atto[\"hooks::dropdown.init\"](window);
atto[\"hooks::character_counter.init\"]();
atto[\"hooks::long_text.init\"]();
atto[\"hooks::alt\"]();
atto[\"hooks::online_indicator\"]();
atto[\"hooks::ips\"]();
atto[\"hooks::check_reactions\"]();
atto[\"hooks::tabs\"]();
atto[\"hooks::spotify_time_text\"](); // spotify durations
atto[\"hooks::verify_emoji\"]();
if (document.getElementById(\"tokens\")) {
trigger(\"me::render_token_picker\", [
document.getElementById(\"tokens\"),
]);
}
setTimeout(() => {
trigger(\"me::notifications_stream\");
}, 250);
});
</script>")
(text "{% if user -%}
<script data-turbo-permanent=\"true\" id=\"update-seen-script\">
document.documentElement.addEventListener(\"turbo:load\", () => {
trigger(\"me::seen\");
trigger(\"streams::user\", [\"{{ user.id }}\"]);
if (!window.location.pathname.startsWith(\"/chats/\")) {
if (window.socket) {
window.socket.send(\"Close\");
window.socket = undefined;
console.log(\"socket disconnect\");
}
}
});
</script>
{%- endif %}")
; dialogs
(dialog
("id" "link_filter")
(div
("class" "inner flex flex-col gap-2")
; warning stuff
(p (text "Pressing continue will bring you to the following URL:"))
(pre (code ("id" "link_filter_url")))
(p (text "Are sure you want to go there?"))
(hr ("class" "margin"))
(div
("class" "flex gap-2")
(a
("class" "button primary")
("id" "link_filter_continue")
("rel" "noopener noreferrer")
("target" "_blank")
("onclick", "document.getElementById('link_filter').close()")
(text "{{ icon \"external-link\" }}")
(span (text "{{ text \"dialog:action.continue\" }}")))
(button
("class" "secondary")
("type" "button")
("onclick", "document.getElementById('link_filter').close()")
(text "{{ icon \"x\" }}" )
(span (text "{{ text \"dialog:action.cancel\" }}"))))))
(dialog
("id" "web_api_prompt")
(div
("class" "inner flex flex-col gap-2")
(form
("class" "flex gap-2 flex-col")
("onsubmit" "event.preventDefault()")
(label ("for" "prompt") ("id" "web_api_prompt:msg"))
(input ("id" "prompt") ("name" "prompt"))
(div
("class" "flex justify-between")
(div null?)
(div
("class" "flex gap-2")
(button
("class" "primary bold circle")
("onclick", "globalThis.web_api_prompt_submit(document.getElementById('prompt').value); document.getElementById('prompt').value = ''")
("type" "button")
(text "{{ icon \"check\" }}")
(text "{{ text \"dialog:action.okay\" }}"))
(button
("class" "bold red camo")
("onclick", "globalThis.web_api_prompt_submit('')")
("type" "button")
(text "{{ icon \"x\" }}")
(text "{{ text \"dialog:action.cancel\" }}")))))))
(dialog
("id" "web_api_prompt_long")
(div
("class" "inner flex flex-col gap-2")
(form
("class" "flex gap-2 flex-col")
("onsubmit" "event.preventDefault()")
(label ("for" "prompt_long") ("id" "web_api_prompt_long:msg"))
(input ("id" "prompt_long") ("name" "prompt_long"))
(div
("class" "flex justify-between")
(div null?)
(div
("class" "flex gap-2")
(button
("class" "primary bold circle")
("onclick", "globalThis.web_api_prompt_long_submit(document.getElementById('prompt_long').value); document.getElementById('prompt_long').value = ''")
("type" "button")
(text "{{ icon \"check\" }}")
(text "{{ text \"dialog:action.okay\" }}"))
(button
("class" "bold red camo")
("onclick", "globalThis.web_api_prompt_long_submit('')")
("type" "button")
(text "{{ icon \"x\" }}")
(text "{{ text \"dialog:action.cancel\" }}")))))))
(dialog
("id" "web_api_confirm")
(div
("class" "inner flex flex-col gap-2")
(form
("class" "flex gap-2 flex-col")
("onsubmit" "event.preventDefault()")
(span ("id" "web_api_confirm:msg"))
(div
("class" "flex justify-between")
(div null?)
(div
("class" "flex gap-2")
(button
("class" "primary bold circle")
("onclick", "globalThis.web_api_confirm_submit(true)")
("type" "button")
(text "{{ icon \"check\" }}")
(text "{{ text \"dialog:action.yes\" }}"))
(button
("class" "bold red camo")
("onclick", "globalThis.web_api_confirm_submit(false)")
("type" "button")
(text "{{ icon \"x\" }}")
(text "{{ text \"dialog:action.no\" }}")))))))
(div
("class" "lightbox hidden")
("id" "lightbox")
(button
("class" "lightbox_exit small square quaternary red")
("onclick" "trigger('ui::lightbox_close')")
(text "{{ icon \"x\" }}"))
(a
("href" "")
("id" "lightbox_img_a")
("target" "_blank")))
; tokens dialog
(text "{% if user -%}")
(dialog
("id" "tokens_dialog")
(div
("class" "inner flex flex-col gap-2")
(form
("class" "flex gap-2 flex-col")
("onsubmit" "event.preventDefault()")
(div ("id" "tokens") ("style" "display: contents"))
(a
("href" "/auth/login")
("class" "button")
("data-turbo", "false")
(text "{{ icon \"plus\" }}")
(span (text "{{ text \"general:action.add_account\" }}")))
(div
("class" "flex justify-between")
(div null?)
(div
("class" "flex gap-2")
(button
("class" "quaternary")
("onclick" "document.getElementById('tokens_dialog').close()")
("type" "button")
(text "{{ icon \"check \" }}")
(span "{{ text \"dialog:action.okay\" }}")))))))
; user scripts
(text "{%- endif %} {% if user and use_user_theme -%} {{ components::theme(user=user, theme_preference=user.settings.theme_preference) }}
<script>
setTimeout(() => {
trigger(\"atto::use_theme_preference\");
}, 150);
</script>
{%- endif %} {% if user and user.connections.Spotify and config.connections.spotify_client_id and user.connections.Spotify[0].data.token and user.connections.Spotify[0].data.refresh_token %}
<script>
setTimeout(async () => {
if (window.spotify_init) {
return;
}
window.spotify_init = true;
const client_id = \"{{ config.connections.spotify_client_id }}\";
let token = \"{{ user.connections.Spotify[0].data.token }}\";
let refresh_token =
\"{{ user.connections.Spotify[0].data.refresh_token }}\";
if (token) {
// we already have a token
const pull_playing = async () => {
const playing = await trigger(\"spotify::get_playing\", [
token,
]);
if (playing.error) {
// refresh token
const [new_token, new_refresh_token, expires_in] =
await trigger(\"spotify::refresh_token\", [
client_id,
refresh_token,
]);
await trigger(\"connections::push_con_data\", [
\"Spotify\",
{
token: new_token,
expires_in: expires_in.toString(),
name: profile.display_name,
},
]);
token = new_token;
refresh_token = new_refresh_token;
return;
}
await trigger(\"spotify::publish_playing\", [playing]);
};
await pull_playing();
setInterval(pull_playing, 30_000);
} else {
window.spotify_needs_token = true;
}
}, 150);
</script>
{% elif user and user.connections.LastFm and config.connections.last_fm_key and user.connections.LastFm[0].data.session_token %}
<script>
setTimeout(async () => {
if (window.last_fm_init) {
return;
}
window.last_fm_init = true;
const user = \"{{ user.connections.LastFm[0].data.name }}\";
const session_token =
\"{{ user.connections.LastFm[0].data.session_token }}\";
if (session_token) {
// we already have a token
const pull_playing = async () => {
const playing = await trigger(\"last_fm::get_playing\", [
user,
session_token,
]);
await trigger(\"last_fm::publish_playing\", [playing]);
};
await pull_playing();
setInterval(pull_playing, 30_000);
} else {
window.last_fm_needs_token = true;
}
}, 150);
</script>
{%- endif %}")))