feat(album): plumb featured artists and parental warning

Add Album.parental_warning and Album.artists (with the new AlbumArtist
struct) to qbz-models so the /album/get response surfaces both fields.
Mirror them in the QobuzAlbum / AlbumDetail TypeScript types and
populate featuredArtists (filtered to non-main contributors) and
parentalWarning in convertQobuzAlbum. Wire onFeaturedArtistClick from
the route so featured artist names route to their discography.

cc
This commit is contained in:
vicrodh committed 2026-05-06 22:06:54 -06:00
1 parent e1447159bb
commit e8d58196c5
4 files changed
+34

No files matched your search

+17
View File
@@ -273,6 +273,23 @@ pub struct Album {
/// Editorial awards (Qobuzissime, Album of the Week, press accolades). /// Editorial awards (Qobuzissime, Album of the Week, press accolades).
#[serde(default)] #[serde(default)]
pub awards: Option<Vec<AlbumAward>>, pub awards: Option<Vec<AlbumAward>>,
/// Parental advisory / explicit content marker.
#[serde(default)]
pub parental_warning: Option<bool>,
/// Full artist contributor list including roles. The primary artist is
/// duplicated here as `roles: ["main-artist"]`; non-main entries are
/// the album's featured artists.
#[serde(default)]
pub artists: Option<Vec<AlbumArtist>>,
}
/// Album artist contributor entry (main artist + featured artists).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlbumArtist {
pub id: u64,
pub name: String,
#[serde(default)]
pub roles: Option<Vec<String>>,
} }
/// A downloadable extra bundled with an album (e.g. PDF booklet) /// A downloadable extra bundled with an album (e.g. PDF booklet)
+11
View File
@@ -358,12 +358,23 @@ export function convertQobuzAlbum(album: QobuzAlbum): AlbumDetail {
album.maximum_sampling_rate album.maximum_sampling_rate
); );
const mainArtistId = album.artist?.id;
const featuredArtists = (album.artists ?? [])
.filter((entry) => {
const isMain = entry.roles?.some((role) => role === 'main-artist');
const isMainById = mainArtistId !== undefined && entry.id === mainArtistId;
return !isMain && !isMainById;
})
.map((entry) => ({ id: entry.id, name: entry.name }));
return { return {
id: album.id, id: album.id,
artwork, artwork,
title: album.title, title: album.title,
artist: album.artist?.name || 'Unknown Artist', artist: album.artist?.name || 'Unknown Artist',
artistId: album.artist?.id, artistId: album.artist?.id,
featuredArtists: featuredArtists.length > 0 ? featuredArtists : undefined,
parentalWarning: album.parental_warning ?? false,
year: album.release_date_original?.split('-')[0] || '', year: album.release_date_original?.split('-')[0] || '',
releaseDate: album.release_date_original, releaseDate: album.release_date_original,
label: album.label?.name || '', label: album.label?.name || '',
+5
View File
@@ -96,6 +96,7 @@ export interface QobuzAlbum {
title: string; title: string;
description?: string; description?: string;
artist: { id?: number; name: string }; artist: { id?: number; name: string };
artists?: { id: number; name: string; roles?: string[] }[];
image: QobuzImage; image: QobuzImage;
release_date_original?: string; release_date_original?: string;
hires_streamable?: boolean; hires_streamable?: boolean;
@@ -202,6 +203,10 @@ export interface AlbumDetail {
title: string; title: string;
artist: string; artist: string;
artistId?: number; artistId?: number;
/** Featured artists (excluding the main artist), in API order. */
featuredArtists?: { id: number; name: string }[];
/** Parental advisory marker — show explicit badge next to artist line. */
parentalWarning?: boolean;
year: string; year: string;
releaseDate?: string; // Full date in YYYY-MM-DD format releaseDate?: string; // Full date in YYYY-MM-DD format
label: string; label: string;
+1
View File
@@ -5922,6 +5922,7 @@
isPlaybackActive={isPlaying} isPlaybackActive={isPlaying}
onBack={navGoBack} onBack={navGoBack}
onArtistClick={() => selectedAlbum?.artistId && handleArtistClick(selectedAlbum.artistId)} onArtistClick={() => selectedAlbum?.artistId && handleArtistClick(selectedAlbum.artistId)}
onFeaturedArtistClick={(artistId) => handleArtistClick(artistId)}
onLabelClick={handleLabelClick} onLabelClick={handleLabelClick}
onAwardClick={handleAwardClick} onAwardClick={handleAwardClick}
onTrackPlay={handleAlbumTrackPlay} onTrackPlay={handleAlbumTrackPlay}