From 28fa4c0042d8061cee71dda28c007981f203dd71 Mon Sep 17 00:00:00 2001 From: Tsiry Sandratraina Date: Mon, 2 Feb 2026 00:18:15 +0300 Subject: [PATCH] Add scrobble embed and embed improvements Add ScrobbleEmbedPage and getScrobble xrpc, and register a new /embed/:did/scrobble/:rkey route to render scrobbles with profile data. Update Scrobble type (cover?, date) and change song/artist/album routes to use :did/:rkey params. Add various Tailwind utility classes and tweak the embed index layout to include a centered input. Add placeholder xrpc stubs for album/artist/song. --- apps/embed/public/styles.css | 51 ++++++++++++++++ apps/embed/src/embeds/AlbumEmbedPage.tsx | 2 +- apps/embed/src/embeds/ArtistEmbedPage.tsx | 2 +- apps/embed/src/embeds/NowPlayingEmbedPage.tsx | 2 +- apps/embed/src/embeds/ScrobbleEmbedPage.tsx | 58 +++++++++++++++++++ apps/embed/src/embeds/SongEmbedPage.tsx | 2 +- apps/embed/src/index.tsx | 39 ++++++++++--- apps/embed/src/types/scrobble.ts | 2 + apps/embed/src/xrpc/getAlbum.ts | 0 apps/embed/src/xrpc/getArtist.ts | 0 apps/embed/src/xrpc/getScrobble.ts | 17 ++++++ apps/embed/src/xrpc/getSong.ts | 0 12 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 apps/embed/src/embeds/ScrobbleEmbedPage.tsx create mode 100644 apps/embed/src/xrpc/getAlbum.ts create mode 100644 apps/embed/src/xrpc/getArtist.ts create mode 100644 apps/embed/src/xrpc/getScrobble.ts create mode 100644 apps/embed/src/xrpc/getSong.ts diff --git a/apps/embed/public/styles.css b/apps/embed/public/styles.css index dd253500..deb8378b 100644 --- a/apps/embed/public/styles.css +++ b/apps/embed/public/styles.css @@ -1349,6 +1349,9 @@ .m-\[20px\] { margin: 20px; } +.m-auto { + margin: auto; +} .filter { display: flex; flex-wrap: wrap; @@ -1464,9 +1467,15 @@ .mt-\[-6px\] { margin-top: -6px; } +.mt-\[5px\] { + margin-top: 5px; +} .mt-\[10px\] { margin-top: 10px; } +.mt-\[20px\] { + margin-top: 20px; +} .mr-\[5px\] { margin-right: 5px; } @@ -1482,6 +1491,12 @@ .mr-\[25px\] { margin-right: 25px; } +.mb-\[5px\] { + margin-bottom: 5px; +} +.mb-\[10px\] { + margin-bottom: 10px; +} .mb-\[15px\] { margin-bottom: 15px; } @@ -1727,6 +1742,9 @@ .max-h-\[18px\] { max-height: 18px; } +.max-h-\[20-px\] { + max-height: 20-px; +} .max-h-\[20px\] { max-height: 20px; } @@ -1748,6 +1766,12 @@ .max-h-\[100px\] { max-height: 100px; } +.max-h-\[200px\] { + max-height: 200px; +} +.max-h-\[250px\] { + max-height: 250px; +} .min-h-screen { min-height: 100vh; } @@ -1779,12 +1803,21 @@ outline-style: none; } } +.w-1\/2 { + width: calc(1/2 * 100%); +} +.w-1\/3 { + width: calc(1/3 * 100%); +} .w-\[30px\] { width: 30px; } .w-\[60px\] { width: 60px; } +.w-fit { + width: fit-content; +} .w-full { width: 100%; } @@ -1812,6 +1845,12 @@ .max-w-\[100px\] { max-width: 100px; } +.max-w-\[200px\] { + max-width: 200px; +} +.max-w-\[250px\] { + max-width: 250px; +} .max-w-full { max-width: 100%; } @@ -1912,6 +1951,9 @@ .rounded-\[5px\] { border-radius: 5px; } +.rounded-\[8px\] { + border-radius: 8px; +} .rounded-box { border-radius: var(--radius-box); } @@ -2341,6 +2383,9 @@ .\!bg-base-100 { background-color: var(--color-base-100) !important; } +.bg-\[\#00fff3\] { + background-color: #00fff3; +} .bg-\[var\(--color-avatar-background\)\] { background-color: var(--color-avatar-background); } @@ -2366,6 +2411,9 @@ .pr-\[15px\] { padding-right: 15px; } +.text-center { + text-align: center; +} .align-bottom { vertical-align: bottom; } @@ -2402,6 +2450,9 @@ .whitespace-nowrap { white-space: nowrap; } +.text-\[\#ff2876\] { + color: #ff2876; +} .text-\[var\(--color-genre\)\] { color: var(--color-genre); } diff --git a/apps/embed/src/embeds/AlbumEmbedPage.tsx b/apps/embed/src/embeds/AlbumEmbedPage.tsx index c966ed52..19f5da83 100644 --- a/apps/embed/src/embeds/AlbumEmbedPage.tsx +++ b/apps/embed/src/embeds/AlbumEmbedPage.tsx @@ -1,3 +1,3 @@ export function AlbumEmbedPage() { - return <>; + return <>Album; } diff --git a/apps/embed/src/embeds/ArtistEmbedPage.tsx b/apps/embed/src/embeds/ArtistEmbedPage.tsx index 03cb1ba4..961885c6 100644 --- a/apps/embed/src/embeds/ArtistEmbedPage.tsx +++ b/apps/embed/src/embeds/ArtistEmbedPage.tsx @@ -1,3 +1,3 @@ export function ArtistEmbedPage() { - return <>; + return <>Artist; } diff --git a/apps/embed/src/embeds/NowPlayingEmbedPage.tsx b/apps/embed/src/embeds/NowPlayingEmbedPage.tsx index 5991e02a..c3b84644 100644 --- a/apps/embed/src/embeds/NowPlayingEmbedPage.tsx +++ b/apps/embed/src/embeds/NowPlayingEmbedPage.tsx @@ -1,3 +1,3 @@ export function NowPlayingEmbedPage() { - return <>; + return <>Now Playing; } diff --git a/apps/embed/src/embeds/ScrobbleEmbedPage.tsx b/apps/embed/src/embeds/ScrobbleEmbedPage.tsx new file mode 100644 index 00000000..8ec8255c --- /dev/null +++ b/apps/embed/src/embeds/ScrobbleEmbedPage.tsx @@ -0,0 +1,58 @@ +import dayjs from "dayjs"; +import type { Profile } from "../types/profile"; +import type { Scrobble } from "../types/scrobble"; + +export type ScrobbleEmbedPageProps = { + profile: Profile; + scrobble: Scrobble; +}; + +export function ScrobbleEmbedPage(props: ScrobbleEmbedPageProps) { + console.log("ScrobbleEmbedPage props:", props); + return ( +
+
+ + + + +
{props.scrobble.title}
+
+
+ {props.scrobble.artist} +
+
+ + + + + + @{props.profile.handle} + +
+
played this song
+
+ {dayjs(props.scrobble.date).format("MMM D, YYYY [at] h:mm A")} +
+
+
+ ); +} diff --git a/apps/embed/src/embeds/SongEmbedPage.tsx b/apps/embed/src/embeds/SongEmbedPage.tsx index 3de59adb..1b21f414 100644 --- a/apps/embed/src/embeds/SongEmbedPage.tsx +++ b/apps/embed/src/embeds/SongEmbedPage.tsx @@ -1,3 +1,3 @@ export function SongEmbedPage() { - return <>; + return <>Song; } diff --git a/apps/embed/src/index.tsx b/apps/embed/src/index.tsx index 07db1d11..5f30be0a 100644 --- a/apps/embed/src/index.tsx +++ b/apps/embed/src/index.tsx @@ -21,6 +21,8 @@ import getTopTracks from "./xrpc/getTopTracks"; import getRecentScrobbles from "./xrpc/getRecentScrobbles"; import chalk from "chalk"; import { logger } from "hono/logger"; +import { ScrobbleEmbedPage } from "./embeds/ScrobbleEmbedPage"; +import getScrobble from "./xrpc/getScrobble"; const app = new Hono(); @@ -65,15 +67,15 @@ app.get("/embed/u/:handle/top/tracks", async (c) => { return c.render(); }); -app.get("/embed/song/:id", (c) => { +app.get("/embed/:did/song/:rkey", (c) => { return c.render(); }); -app.get("/embed/artist/:id", (c) => { +app.get("/embed/:did/artist/:rkey", (c) => { return c.render(); }); -app.get("/embed/album/:id", (c) => { +app.get("/embed/:did/album/:rkey", (c) => { return c.render(); }); @@ -137,16 +139,35 @@ app.get("/embed/u/:handle/summary", (c) => { return c.render(); }); +app.get("/embed/:did/scrobble/:rkey", async (c) => { + const did = c.req.param("did"); + const rkey = c.req.param("rkey"); + const uri = `at://${did}/app.rocksky.scrobble/${rkey}`; + const [{ profile, ok: profileOk }, { scrobble, ok: scrobbleOk }] = + await Promise.all([getProfile(did), getScrobble(uri)]); + + if (!scrobbleOk || !profileOk || !scrobble) { + return c.text("Scrobble not found", 404); + } + + return c.render(); +}); + app.get("/", (c) => { return c.render( -
-
-

+
+
+

Embed a Rocksky Scrobble

-

- This is server-side rendered with Tailwind CSS -

+
+ +
, ); diff --git a/apps/embed/src/types/scrobble.ts b/apps/embed/src/types/scrobble.ts index 95b8b776..7d930f2b 100644 --- a/apps/embed/src/types/scrobble.ts +++ b/apps/embed/src/types/scrobble.ts @@ -5,6 +5,7 @@ export type Scrobble = { artist: string; albumArtist: string; albumArt: string; + cover?: string; album: string; handle: string; did: string; @@ -13,4 +14,5 @@ export type Scrobble = { artistUri: string; albumUri: string; createdAt: string; + date: string; }; diff --git a/apps/embed/src/xrpc/getAlbum.ts b/apps/embed/src/xrpc/getAlbum.ts new file mode 100644 index 00000000..e69de29b diff --git a/apps/embed/src/xrpc/getArtist.ts b/apps/embed/src/xrpc/getArtist.ts new file mode 100644 index 00000000..e69de29b diff --git a/apps/embed/src/xrpc/getScrobble.ts b/apps/embed/src/xrpc/getScrobble.ts new file mode 100644 index 00000000..64561665 --- /dev/null +++ b/apps/embed/src/xrpc/getScrobble.ts @@ -0,0 +1,17 @@ +import { ROCKSKY_API_URL } from "../consts"; +import type { Scrobble } from "../types/scrobble"; + +export default async function getScrobble(uri: string) { + const url = new URL( + `${ROCKSKY_API_URL}/xrpc/app.rocksky.scrobble.getScrobble`, + ); + url.searchParams.append("uri", uri); + const res = await fetch(url); + + if (!res.ok) { + return { scrobble: null, ok: res.ok }; + } + + const scrobble = (await res.json()) as Scrobble; + return { scrobble, ok: res.ok }; +} diff --git a/apps/embed/src/xrpc/getSong.ts b/apps/embed/src/xrpc/getSong.ts new file mode 100644 index 00000000..e69de29b -- 2.51.2