mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
feat(now-playing): redesign page as info dashboard (#266)
Replace the flat Now Playing page with a two-column dashboard: hero (cover, metadata, badges, Last.fm stats + love button, release-age tagline), and a grid of themed cards (Album tracklist with sliding window, most-played-by-artist, credits, about-the-artist with Last.fm bio fallback, compact discography contact-sheet with expand, upcoming tours via Bandsintown). Adds lastfmGetTrackInfo + lastfmGetArtistStats for global listener counts + userplaycount, plus Last.fm love/unlove wired to the new hero button. Module-level TTL caches per entity keep same-artist track switches instant. Artist-image guard filters the well-known Last.fm no-image placeholder so the card collapses cleanly when no real image exists. Co-authored-by: Psychotoxical <dev@psysonic.app> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
f0e5b2542b
commit
b4f31e0954
@@ -300,3 +300,79 @@ export async function lastfmScrobble(
|
||||
// best effort
|
||||
}
|
||||
}
|
||||
|
||||
export interface LastfmTrackInfo {
|
||||
listeners: number;
|
||||
playcount: number;
|
||||
userPlaycount: number | null;
|
||||
userLoved: boolean;
|
||||
tags: string[];
|
||||
url: string | null;
|
||||
}
|
||||
|
||||
export async function lastfmGetTrackInfo(
|
||||
artist: string,
|
||||
track: string,
|
||||
username?: string,
|
||||
): Promise<LastfmTrackInfo | null> {
|
||||
try {
|
||||
const params: Record<string, string> = { method: 'track.getInfo', artist, track };
|
||||
if (username) params.username = username;
|
||||
const data = await call(params, false, true);
|
||||
const t = data?.track;
|
||||
if (!t) return null;
|
||||
const rawTags = t.toptags?.tag;
|
||||
const tags = rawTags
|
||||
? (Array.isArray(rawTags) ? rawTags : [rawTags]).map((tg: any) => String(tg.name)).slice(0, 5)
|
||||
: [];
|
||||
const userPc = t.userplaycount != null ? Number(t.userplaycount) : null;
|
||||
return {
|
||||
listeners: Number(t.listeners) || 0,
|
||||
playcount: Number(t.playcount) || 0,
|
||||
userPlaycount: Number.isFinite(userPc) ? userPc : null,
|
||||
userLoved: t.userloved === '1' || t.userloved === 1,
|
||||
tags,
|
||||
url: t.url ?? null,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export interface LastfmArtistStats {
|
||||
listeners: number;
|
||||
playcount: number;
|
||||
userPlaycount: number | null;
|
||||
tags: string[];
|
||||
url: string | null;
|
||||
bio: string | null;
|
||||
}
|
||||
|
||||
export async function lastfmGetArtistStats(
|
||||
artist: string,
|
||||
username?: string,
|
||||
): Promise<LastfmArtistStats | null> {
|
||||
try {
|
||||
const params: Record<string, string> = { method: 'artist.getInfo', artist };
|
||||
if (username) params.username = username;
|
||||
const data = await call(params, false, true);
|
||||
const a = data?.artist;
|
||||
if (!a) return null;
|
||||
const rawTags = a.tags?.tag;
|
||||
const tags = rawTags
|
||||
? (Array.isArray(rawTags) ? rawTags : [rawTags]).map((tg: any) => String(tg.name)).slice(0, 5)
|
||||
: [];
|
||||
const userPc = a.stats?.userplaycount != null ? Number(a.stats.userplaycount) : null;
|
||||
const bioRaw = (a.bio?.content || a.bio?.summary || '').trim();
|
||||
return {
|
||||
listeners: Number(a.stats?.listeners) || 0,
|
||||
playcount: Number(a.stats?.playcount) || 0,
|
||||
userPlaycount: Number.isFinite(userPc) ? userPc : null,
|
||||
tags,
|
||||
url: a.url ?? null,
|
||||
bio: bioRaw || null,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user