add: last.fm status integration

This commit is contained in:
trisua 2025-04-26 19:23:30 -04:00
parent 3e2bdceb99
commit 0765156697
17 changed files with 397 additions and 3 deletions

View file

@ -37,4 +37,29 @@ config.connections.spotify_client_id %}
`<b>${message}.</b> You can now close this tab.`;
}, 150);
</script>
{% elif connection_type == "LastFm" and user and user.connections.LastFm and
config.connections.last_fm_key %}
<script>
setTimeout(async () => {
const token = new URLSearchParams(window.location.search).get("token");
const api_key = "{{ config.connections.last_fm_key }}";
if (!token) {
alert("Connection failed (did not get token from Last.fm)");
return;
}
const res = await trigger("last_fm::get_session", [token]);
const { message } = await trigger("connections::push_con_data", [
"LastFm",
{
session_token: res.session.key,
name: res.session.name,
},
]);
document.getElementById("status").innerHTML =
`<b>${message}.</b> You can now close this tab.`;
}, 1000);
</script>
{% endif %} {% endblock %}

View file

@ -847,4 +847,38 @@ state.data %}
</div>
</div>
</div>
{% endif %} {%- endmacro %} {% macro last_fm_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 "last_fm" }}
</div>
</div>
<div class="card secondary flex gap-2">
<img
src="{{ state.external_urls.track_img }}"
alt="Track cover"
loading="lazy"
class="avatar"
style="--size: {{ size }}"
/>
<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
>
</div>
</div>
</div>
{% endif %} {%- endmacro %}

View file

@ -109,6 +109,8 @@
<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]) }}
{% elif profile.connections.LastFm and profile.connections.LastFm[0].data.name and profile.connections.LastFm[0].show_on_profile %}
{{ components::last_fm_playing(state=profile.connections.LastFm[1]) }}
{% endif %}
</div>

View file

@ -515,13 +515,23 @@
{{ icon "spotify" }}
<span>Spotify</span>
</button>
{% endif %} {% if config.connections.last_fm_key and not
user.connections.LastFm %}
<button
class="quaternary"
onclick="trigger('last_fm::create_connection', ['{{ config.connections.last_fm_key }}'])"
>
{{ icon "last_fm" }}
<span>Last.fm</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 %}
{% if key == "Spotify" %} {{ icon "spotify" }} {% elif key ==
"LastFm" %} {{ icon "last_fm" }} {% endif %}
<b>
{% if value[0].data.name %} {{ value[0].data.name }} {% else

View file

@ -381,6 +381,37 @@ macros -%}
}
}, 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 @@
<svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 284.498"><path fill="#D0232B" d="M225.779 253.403l-18.823-51.007s-30.491 34.026-76.188 34.026c-40.526 0-69.224-35.199-69.224-91.497 0-72.096 36.378-97.891 72.097-97.891 51.584 0 67.997 33.411 82.111 76.205l18.823 58.593c18.813 56.894 54.021 102.609 155.399 102.609 72.701 0 122.026-22.309 122.026-80.914 0-47.49-27.007-72.097-77.418-83.898l-37.494-8.191c-25.807-5.887-33.411-16.412-33.411-34.028 0-19.908 15.808-31.713 41.615-31.713 28.186 0 43.39 10.592 45.687 35.813l58.602-7.003C504.877 21.695 468.496 0 408.693 0c-52.811 0-104.404 19.908-104.404 83.907 0 39.904 19.391 65.085 68.005 76.802l39.904 9.4c29.877 7.013 39.894 19.391 39.894 36.391 0 21.695-21.099 30.494-60.993 30.494-59.21 0-83.908-31.108-97.904-73.893l-19.391-58.593C249.297 28.211 209.998.091 132.014.091 45.791.091 0 54.593 0 147.296c0 89.096 45.697 137.202 127.914 137.202 66.201 0 97.892-31.107 97.892-31.107l-.027.009v.003z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -578,3 +578,109 @@
},
);
})();
(() => {
const self = reg_ns("last_fm");
self.define("api", async (_, method, data) => {
return JSON.parse(
(
await (
await fetch(
"/api/v1/auth/user/connections/last_fm/api_proxy",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
method,
data,
}),
},
)
).json()
).payload,
);
});
self.define("create_connection", (_, client_id) => {
fetch("/api/v1/auth/user/connections/last_fm", {
method: "POST",
})
.then((res) => res.json())
.then(async (_) => {
const params = new URLSearchParams();
params.append("api_key", client_id);
params.append(
"cb",
`${window.location.origin}/auth/connections_link/LastFm`,
);
window.open(`https://last.fm/api/auth?${params.toString()}`);
window.location.reload();
});
});
self.define("get_session", async ({ $ }, token) => {
return await $.api("auth.getSession", {
token,
});
});
self.define("get_playing", async ({ $ }, user, session_token) => {
// <https://lastfm-docs.github.io/api-docs/user/getRecentTracks/>
return (
await $.api("user.getRecentTracks", {
user,
sk: session_token,
limit: "1",
extended: "1",
})
).recenttracks.track[0];
});
self.define("publish_playing", async (_, playing) => {
if (!playing || !playing["@attr"] || !playing["@attr"].nowplaying) {
return await trigger("connections::push_con_state", [
"LastFm",
{
external_urls: {},
data: {},
},
]);
}
if (
window.localStorage.getItem("atto:connections.last_fm/name") ===
playing.name
) {
// item already pushed to connection, no need right now
return;
}
window.localStorage.setItem(
"atto:connections.last_fm/name",
playing.name,
);
return await trigger("connections::push_con_state", [
"LastFm",
{
external_urls: {
track: playing.url,
artist: playing.artist.url,
track_img: playing.image[2]["#text"],
},
data: {
id: playing.mbid,
// track
track: playing.name,
artist: playing.artist.name,
album: playing.album["#text"],
},
},
]);
});
})();