From f0f6b7391a80ec1563f1c509b129d8ff2304893e Mon Sep 17 00:00:00 2001 From: synzr Date: Wed, 3 Dec 2025 17:43:21 +0500 Subject: [PATCH 1/4] feat(api/music): initial implementation --- .env.example | 2 + package.json | 4 ++ pnpm-lock.yaml | 26 +++++++++++++ src/lib/server/clients/last-fm.ts | 64 +++++++++++++++++++++++++++++++ src/routes/api/music/+server.ts | 11 ++++++ 5 files changed, 107 insertions(+) create mode 100644 .env.example create mode 100644 src/lib/server/clients/last-fm.ts create mode 100644 src/routes/api/music/+server.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..95a3ce8 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +LASTFM_API_KEY= +LASTFM_USER= diff --git a/package.json b/package.json index 04fa0d8..d507528 100644 --- a/package.json +++ b/package.json @@ -33,5 +33,9 @@ "typescript-eslint": "^8.47.0", "vite": "^7.2.2", "vite-plugin-devtools-json": "^1.0.0" + }, + "dependencies": { + "lastfm-ts-api": "^2.2.0", + "node-cache": "^5.1.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a146fb..3546fe7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,13 @@ settings: importers: .: + dependencies: + lastfm-ts-api: + specifier: ^2.2.0 + version: 2.2.0 + node-cache: + specifier: ^5.1.2 + version: 5.1.2 devDependencies: '@eslint/compat': specifier: ^1.4.0 @@ -679,6 +686,10 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -917,6 +928,9 @@ packages: known-css-properties@0.37.0: resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} + lastfm-ts-api@2.2.0: + resolution: {integrity: sha512-RGceA84ImKtPtFKZaAE9FbTI9hchjxAwi3ZJAr5nlrfVjvSQvpoEFtamrgJY+JYY9PdJusHil8eki00ll4ILZg==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1034,6 +1048,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-cache@5.1.2: + resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} + engines: {node: '>= 8.0.0'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1801,6 +1819,8 @@ snapshots: dependencies: clsx: 2.1.1 + clone@2.1.2: {} + clsx@2.1.1: {} color-convert@2.0.1: @@ -2047,6 +2067,8 @@ snapshots: known-css-properties@0.37.0: {} + lastfm-ts-api@2.2.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -2133,6 +2155,10 @@ snapshots: natural-compare@1.4.0: {} + node-cache@5.1.2: + dependencies: + clone: 2.1.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 diff --git a/src/lib/server/clients/last-fm.ts b/src/lib/server/clients/last-fm.ts new file mode 100644 index 0000000..b33505b --- /dev/null +++ b/src/lib/server/clients/last-fm.ts @@ -0,0 +1,64 @@ +import { LASTFM_API_KEY, LASTFM_USER } from "$env/static/private"; +import { LastFMUser } from "lastfm-ts-api"; + +/** + * Last.fm API client. + */ +const client = new LastFMUser(LASTFM_API_KEY); + +/** + * Recent track. + */ +export interface RecentTrack { + /** + * Song title. + */ + title: string; + + /** + * Song artist. + */ + artist: string; + + /** + * Album title. + */ + album: string; + + /** + * Cover URL. + */ + cover: string; + + /** + * Song URL. + */ + url: string; +} + +/** + * Get the most recent track from a listening history. + * @returns Recent track. + */ +export async function getRecentTrack(): Promise { + // NOTE: fetch the recent tracks + const { + recenttracks: { + track: tracks, + }, + } = await client.getRecentTracks({ + user: LASTFM_USER, + }); + + // NOTE: get the first track and return it + const track = tracks.shift()!; + return { + title: track.name, + artist: track.artist["#text"], + album: track.album["#text"], + + // NOTE: the most HQ cover is always last in an array + cover: track.image.pop()!["#text"], + url: track.url, + }; +} diff --git a/src/routes/api/music/+server.ts b/src/routes/api/music/+server.ts new file mode 100644 index 0000000..d8235c5 --- /dev/null +++ b/src/routes/api/music/+server.ts @@ -0,0 +1,11 @@ +import { getRecentTrack } from '$lib/server/clients/last-fm'; +import type { RequestHandler } from './$types'; + +export const GET: RequestHandler = async () => { + return Response.json( + await getRecentTrack(), + { + headers: { 'cache-control': 'maxage=60' }, + }, + ); +}; -- 2.49.1 From 7e854d2b23a85f72bce9180feb5e0a2efe730753 Mon Sep 17 00:00:00 2001 From: synzr Date: Wed, 3 Dec 2025 18:05:22 +0500 Subject: [PATCH 2/4] feat(components/tiles/music): music tile --- src/lib/assets/icons/last-fm.png | Bin 0 -> 34907 bytes src/lib/assets/icons/last-fm.webp | Bin 0 -> 2122 bytes .../components/tiles/music/MusicTile.svelte | 40 ++++++++++++++++++ .../tiles/pages/TileTextPage.svelte | 6 +-- src/routes/(tiles)/+page.server.ts | 7 +++ src/routes/(tiles)/+page.svelte | 18 +++----- 6 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 src/lib/assets/icons/last-fm.png create mode 100644 src/lib/assets/icons/last-fm.webp create mode 100644 src/lib/components/tiles/music/MusicTile.svelte create mode 100644 src/routes/(tiles)/+page.server.ts diff --git a/src/lib/assets/icons/last-fm.png b/src/lib/assets/icons/last-fm.png new file mode 100644 index 0000000000000000000000000000000000000000..e6d66a725d7bfca79592a994ae151218a1085129 GIT binary patch literal 34907 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 z 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 literal 0 HcmV?d00001 diff --git a/src/lib/components/tiles/music/MusicTile.svelte b/src/lib/components/tiles/music/MusicTile.svelte new file mode 100644 index 0000000..e3f577d --- /dev/null +++ b/src/lib/components/tiles/music/MusicTile.svelte @@ -0,0 +1,40 @@ + + + + + + {#if track} + + + {:else} + + {/if} + diff --git a/src/lib/components/tiles/pages/TileTextPage.svelte b/src/lib/components/tiles/pages/TileTextPage.svelte index 6228d45..6e6b298 100644 --- a/src/lib/components/tiles/pages/TileTextPage.svelte +++ b/src/lib/components/tiles/pages/TileTextPage.svelte @@ -20,14 +20,14 @@ Text page for a tile. Must be in a tile to display correctly.
-

{title}

+

{title}

{#if subtitle} -

{subtitle}

+

{subtitle}

{/if} {#if text} -

{text}

+

{text}

{/if}
diff --git a/src/routes/(tiles)/+page.server.ts b/src/routes/(tiles)/+page.server.ts new file mode 100644 index 0000000..5aa31e1 --- /dev/null +++ b/src/routes/(tiles)/+page.server.ts @@ -0,0 +1,7 @@ +import type { PageServerLoad } from './$types'; + +import { getRecentTrack } from '$lib/server/clients/last-fm'; + +export const load = (async () => { + return { track: await getRecentTrack() }; +}) satisfies PageServerLoad; diff --git a/src/routes/(tiles)/+page.svelte b/src/routes/(tiles)/+page.svelte index 3c121b1..6b2d053 100644 --- a/src/routes/(tiles)/+page.svelte +++ b/src/routes/(tiles)/+page.svelte @@ -1,4 +1,6 @@ - @@ -23,16 +28,7 @@ - - - - + Date: Thu, 4 Dec 2025 11:17:34 +0500 Subject: [PATCH 3/4] feat(components/tiles/music): client-side update every minute --- .../components/tiles/music/MusicTile.svelte | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/lib/components/tiles/music/MusicTile.svelte b/src/lib/components/tiles/music/MusicTile.svelte index e3f577d..47a0c89 100644 --- a/src/lib/components/tiles/music/MusicTile.svelte +++ b/src/lib/components/tiles/music/MusicTile.svelte @@ -6,6 +6,8 @@ Music tile. - + {#if track} Date: Thu, 4 Dec 2025 11:20:03 +0500 Subject: [PATCH 4/4] fix(components/tiles/common): now links are actually used --- src/lib/components/tiles/common/Tile.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/tiles/common/Tile.svelte b/src/lib/components/tiles/common/Tile.svelte index d506a3b..2708ee6 100644 --- a/src/lib/components/tiles/common/Tile.svelte +++ b/src/lib/components/tiles/common/Tile.svelte @@ -266,7 +266,7 @@ Metro-like tile. Must be in a group to display correctly. grid-column-start: {column}; " onmousemove={active ? updateDynamicMouseValues : null} - href="#h" + href={active ? link : "#"} >