129 lines
3.7 KiB
TypeScript
129 lines
3.7 KiB
TypeScript
interface Track {
|
|
artists: string[];
|
|
title: string;
|
|
album: { albumtitle: string };
|
|
}
|
|
|
|
interface Scrobble {
|
|
time: number;
|
|
track: Track;
|
|
}
|
|
|
|
interface LastFMAlbumInfo {
|
|
album: {
|
|
image: { "#text": string; size: string }[];
|
|
};
|
|
}
|
|
|
|
const LASTFM_API_KEY = "596e4b8f593080597d5637be3173e670"; // Replace with your API key
|
|
|
|
async function fetchAlbumCover(
|
|
artist: string,
|
|
album: string,
|
|
): Promise<string | null> {
|
|
const encodedArtist = encodeURIComponent(artist);
|
|
const encodedAlbum = encodeURIComponent(album);
|
|
const url = `https://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=${LASTFM_API_KEY}&artist=${encodedArtist}&album=${encodedAlbum}&format=json`;
|
|
|
|
try {
|
|
const response = await fetch(url);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data: LastFMAlbumInfo = await response.json();
|
|
|
|
if (data?.album?.image) {
|
|
const largeImage = data.album.image.find(
|
|
(image) => image.size === "large",
|
|
);
|
|
return largeImage ? largeImage["#text"] : null;
|
|
}
|
|
|
|
return null;
|
|
} catch (error) {
|
|
console.error("Error fetching album cover:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function fetchAndDisplayLastTrack() {
|
|
const container = document.getElementById("last-track-widget");
|
|
|
|
if (!container) {
|
|
console.error("Container element not found!");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
let res: Response;
|
|
try {
|
|
res = await fetch("https://z0x.ca/music");
|
|
if (!res.ok) throw new Error("WAN fetch failed");
|
|
} catch (e) {
|
|
res = await fetch("https://z0x.home.arpa/music");
|
|
}
|
|
|
|
if (!res.ok) {
|
|
throw new Error(`HTTP error! status: ${res.status}`);
|
|
}
|
|
|
|
const data: { status: string; list: Scrobble[] } = await res.json();
|
|
|
|
let lastTrack: Track | null = null;
|
|
if (data?.status === "ok" && data.list?.length) {
|
|
lastTrack = data.list.sort(
|
|
(a: Scrobble, b: Scrobble) => b.time - a.time,
|
|
)[0].track;
|
|
}
|
|
|
|
if (lastTrack) {
|
|
const albumCover = await fetchAlbumCover(
|
|
lastTrack.artists[0],
|
|
lastTrack.album.albumtitle,
|
|
);
|
|
|
|
let imageElement = "";
|
|
if (albumCover) {
|
|
imageElement = `<img src="${albumCover}" alt="Album Cover" class="mb-2 rounded shadow-md w-32 h-32 object-cover">`;
|
|
} else {
|
|
imageElement = ""; // Do not display anything
|
|
}
|
|
|
|
container.innerHTML = `
|
|
${imageElement}
|
|
<span class="mb-2 flex gap-2">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 10v3"/><path d="M6 6v11"/><path d="M10 3v18"/><path d="M14 8v7"/><path d="M18 5v13"/><path d="M22 10v3"/></svg>
|
|
<span class="text-sm text-primary">
|
|
Last played..
|
|
</span>
|
|
</span>
|
|
<span class="text-md mb-2 font-bold leading-none">
|
|
${lastTrack.title}
|
|
</span>
|
|
<span class="text-xs text-muted-foreground">
|
|
<span class="font-semibold text-secondary-foreground">
|
|
by
|
|
</span>
|
|
${lastTrack.artists[0]}
|
|
</span>
|
|
<span class="text-xs text-muted-foreground">
|
|
<span class="font-semibold text-secondary-foreground">
|
|
on
|
|
</span>
|
|
${lastTrack.album.albumtitle}
|
|
</span>
|
|
`;
|
|
} else {
|
|
container.innerHTML = "<p>No tracks found.</p>";
|
|
}
|
|
} catch (e) {
|
|
console.error("Fetch error:", e);
|
|
container.innerHTML = "<p>Error loading tracks.</p>";
|
|
} finally {
|
|
container.classList.remove("opacity-0");
|
|
container.classList.add("opacity-100");
|
|
}
|
|
}
|
|
|
|
fetchAndDisplayLastTrack();
|