add: profile connections, spotify connection

This commit is contained in:
trisua 2025-04-26 16:27:18 -04:00
parent a5c2356940
commit 33ba576d4a
31 changed files with 931 additions and 19 deletions

View file

@ -0,0 +1,40 @@
{% extends "auth/base.html" %} {% block head %}
<title>Connection</title>
{% endblock %} {% block title %}Connection{% endblock %} {% block content %}
<div class="w-full flex-col gap-2" id="status"><b>Working...</b></div>
{% if connection_type == "Spotify" and user and user.connections.Spotify and
config.connections.spotify_client_id %}
<script>
setTimeout(async () => {
const code = new URLSearchParams(window.location.search).get("code");
const client_id = "{{ config.connections.spotify_client_id }}";
const verifier = "{{ user.connections.Spotify[0].data.verifier }}";
if (!code) {
alert("Connection failed (did not get code from Spotify)");
return;
}
const [token, refresh_token, expires_in] = await trigger(
"spotify::get_token",
[client_id, verifier, code],
);
const profile = await trigger("spotify::profile", [token]);
const { message } = await trigger("connections::push_con_data", [
"Spotify",
{
token,
refresh_token,
expires_in: expires_in.toString(),
name: profile.display_name,
},
]);
document.getElementById("status").innerHTML =
`<b>${message}.</b> You can now close this tab.`;
}, 150);
</script>
{% endif %} {% endblock %}

View file

@ -801,4 +801,50 @@ secondary=false, show_community=true) -%}
</div>
</div>
</div>
{%- endmacro %}
{%- endmacro %} {% macro spotify_playing(state, size="60px") -%} {% if state and
state.data %}
<div class="card-nest">
<div class="card flex items-center justify-between gap-2 small">
<div class="flex items-center gap-2">
<b>Listening on</b>
{{ icon "spotify" }}
</div>
<span class="fade date short">{{ state.data.timestamp }}</span>
</div>
<div class="card secondary flex gap-2">
<a href="{{ state.external_urls.album }}">
<img
src="{{ state.external_urls.album_img }}"
alt="Album cover"
loading="lazy"
class="avatar"
style="--size: {{ size }}"
/>
</a>
<div class="flex flex-col">
<h5 class="w-full">
<a href="{{ state.external_urls.track }}" class="flush"
>{{ state.data.track }}</a
>
</h5>
<span class="fade"
><a href="{{ state.external_urls.artist }}" class="flush"
>{{ state.data.artist }}</a
></span
>
<span
hook="spotify_time_text"
hook-arg:updated="{{ state.data.timestamp }}"
hook-arg:progress="{{ state.data.progress_ms }}"
hook-arg:duration="{{ state.data.duration_ms }}"
hook-arg:display="full"
></span>
</div>
</div>
</div>
{% endif %} {%- endmacro %}

View file

@ -105,6 +105,13 @@
</div>
<div class="card flex flex-col gap-2">
<!-- prettier-ignore -->
<div style="display: contents;">
{% if profile.connections.Spotify and profile.connections.Spotify[0].data.name and profile.connections.Spotify[0].show_on_profile %}
{{ components::spotify_playing(state=profile.connections.Spotify[1]) }}
{% endif %}
</div>
<div class="w-full flex justify-between items-center">
<span class="notification chip">ID</span>
<button

View file

@ -29,6 +29,11 @@
{{ icon "cookie" }}
<span>{{ text "settings:tab.sessions" }}</span>
</a>
<a data-tab-button="connections" href="#/connections">
{{ icon "cable" }}
<span>{{ text "settings:tab.connections" }}</span>
</a>
</div>
<div class="w-full flex flex-col gap-2" data-tab="account">
@ -496,6 +501,46 @@
</button>
</div>
<div
class="card w-full tertiary hidden flex flex-col gap-2"
data-tab="connections"
>
<div class="card w-full flex flex-wrap gap-2">
{% if config.connections.spotify_client_id and not
user.connections.Spotify %}
<button
class="quaternary"
onclick="trigger('spotify::create_connection', ['{{ config.connections.spotify_client_id }}'])"
>
{{ icon "spotify" }}
<span>Spotify</span>
</button>
{% endif %}
</div>
{% for key, value in user.connections %}
<div class="card-nest">
<div class="card small flex items-center gap-2">
{% if key == "Spotify" %} {{ icon "spotify" }} {% endif %}
<b>
{% if value[0].data.name %} {{ value[0].data.name }} {% else
%} {{ key }} {% endif %}
</b>
</div>
<div class="card flex items-center gap-2">
<button
class="quaternary red small"
onclick="trigger('connections::delete', ['{{ key }}'])"
>
{{ text "general:action.delete" }}
</button>
</div>
</div>
{% endfor %}
</div>
<!-- prettier-ignore -->
<script type="application/json" id="settings_json">{{ user_settings_serde|safe }}</script>

View file

@ -9,7 +9,7 @@ macros -%}
<meta
http-equiv="content-security-policy"
content="default-src 'self' blob:; 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: *"
content="default-src 'self' blob: *.spotify.com; 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" />
@ -111,6 +111,7 @@ macros -%}
atto["hooks::check_reactions"]();
atto["hooks::tabs"]();
atto["hooks::partial_embeds"]();
atto["hooks::spotify_time_text"](); // spotify durations
if (document.getElementById("tokens")) {
trigger("me::render_token_picker", [
@ -325,6 +326,61 @@ macros -%}
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>
{% endif %}
</body>
</html>