feat(api): add 7 label endpoints from v9.7.0.3 spec
Closes the gap between our client and the Qobuz Android app's current
label API (source: c30/b.java). Qobuz is rolling out a web beta so the
legacy /label/get is likely on borrowed time — adding the v9.7.0.3
endpoints now so the migration can happen in follow-up commits.
New endpoints (paths + client + core + bridge + v2 command + registered):
- /label/getAlbums → v2_get_label_albums
paginated catalog with sort/order/genre_ids/from_date/to_date
- /label/getNextReleases → v2_get_label_next_releases
- /label/getAwardedReleases → v2_get_label_awarded_releases
- /label/getPlaylists → v2_get_label_playlists
- /label/getTopArtists → v2_get_label_top_artists
- /label/story → v2_get_label_story
- /label/getList → v2_get_label_list (POST, mirrors
track/getList signing)
New response types in qbz-models:
- LabelListPage<T> — generic { has_more, items: [T], total?, offset?,
limit? } envelope, tolerant to either V2 list or SearchResultsPage
shapes until samples land
- LabelStoryResponse, LabelGetListResponse — best-effort with optional
fields so a shape mismatch is non-fatal
The legacy /label/get + get_label() + v2_get_label + LabelDetail are
retained untouched for now; frontend migration and deletion come next.
cc
This commit is contained in:
8 files changed
+685
-8
No files matched your search
+133
-3
@@ -8,9 +8,10 @@ use tokio::sync::RwLock;
|
||||
|
||||
use qbz_models::{
|
||||
Album, Artist, CoreEvent, DiscoverAlbum, DiscoverData, DiscoverPlaylistsResponse,
|
||||
DiscoverResponse, FrontendAdapter, GenreInfo, LabelDetail, LabelExploreResponse, LabelPageData,
|
||||
PageArtistResponse, Playlist, PlaylistTag, Quality, QueueState, QueueTrack, RepeatMode,
|
||||
SearchResultsPage, StreamUrl, Track, UserSession,
|
||||
DiscoverResponse, FrontendAdapter, GenreInfo, LabelDetail, LabelExploreResponse,
|
||||
LabelGetListResponse, LabelListPage, LabelPageData, LabelStoryResponse, PageArtistResponse,
|
||||
Playlist, PlaylistTag, Quality, QueueState, QueueTrack, RepeatMode, SearchResultsPage,
|
||||
StreamUrl, Track, UserSession,
|
||||
};
|
||||
use qbz_player::{PlaybackState, Player, QueueManager};
|
||||
use qbz_qobuz::QobuzClient;
|
||||
@@ -921,6 +922,135 @@ impl<A: FrontendAdapter + Send + Sync + 'static> QbzCore<A> {
|
||||
.map_err(CoreError::Api)
|
||||
}
|
||||
|
||||
/// Get a label's album catalog (paginated, replaces legacy /label/get).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn get_label_albums(
|
||||
&self,
|
||||
label_id: u64,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
sort: Option<String>,
|
||||
order: Option<String>,
|
||||
genre_ids: Option<String>,
|
||||
from_date: Option<String>,
|
||||
to_date: Option<String>,
|
||||
) -> Result<LabelListPage<Album>, CoreError> {
|
||||
let client = self.client.read().await;
|
||||
let client = client.as_ref().ok_or(CoreError::NotInitialized)?;
|
||||
client
|
||||
.get_label_albums(
|
||||
label_id,
|
||||
limit,
|
||||
offset,
|
||||
sort.as_deref(),
|
||||
order.as_deref(),
|
||||
genre_ids.as_deref(),
|
||||
from_date.as_deref(),
|
||||
to_date.as_deref(),
|
||||
)
|
||||
.await
|
||||
.map_err(CoreError::Api)
|
||||
}
|
||||
|
||||
/// Get a label's upcoming releases.
|
||||
pub async fn get_label_next_releases(
|
||||
&self,
|
||||
label_id: u64,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
genre_ids: Option<String>,
|
||||
) -> Result<LabelListPage<Album>, CoreError> {
|
||||
let client = self.client.read().await;
|
||||
let client = client.as_ref().ok_or(CoreError::NotInitialized)?;
|
||||
client
|
||||
.get_label_next_releases(label_id, limit, offset, genre_ids.as_deref())
|
||||
.await
|
||||
.map_err(CoreError::Api)
|
||||
}
|
||||
|
||||
/// Get a label's press-awarded releases.
|
||||
pub async fn get_label_awarded_releases(
|
||||
&self,
|
||||
label_id: u64,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
sort: Option<String>,
|
||||
order: Option<String>,
|
||||
genre_ids: Option<String>,
|
||||
) -> Result<LabelListPage<Album>, CoreError> {
|
||||
let client = self.client.read().await;
|
||||
let client = client.as_ref().ok_or(CoreError::NotInitialized)?;
|
||||
client
|
||||
.get_label_awarded_releases(
|
||||
label_id,
|
||||
limit,
|
||||
offset,
|
||||
sort.as_deref(),
|
||||
order.as_deref(),
|
||||
genre_ids.as_deref(),
|
||||
)
|
||||
.await
|
||||
.map_err(CoreError::Api)
|
||||
}
|
||||
|
||||
/// Get a label's curated playlists.
|
||||
pub async fn get_label_playlists(
|
||||
&self,
|
||||
label_id: u64,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
) -> Result<LabelListPage<Playlist>, CoreError> {
|
||||
let client = self.client.read().await;
|
||||
let client = client.as_ref().ok_or(CoreError::NotInitialized)?;
|
||||
client
|
||||
.get_label_playlists(label_id, limit, offset)
|
||||
.await
|
||||
.map_err(CoreError::Api)
|
||||
}
|
||||
|
||||
/// Get a label's top artists.
|
||||
pub async fn get_label_top_artists(
|
||||
&self,
|
||||
label_id: u64,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
) -> Result<LabelListPage<Artist>, CoreError> {
|
||||
let client = self.client.read().await;
|
||||
let client = client.as_ref().ok_or(CoreError::NotInitialized)?;
|
||||
client
|
||||
.get_label_top_artists(label_id, limit, offset)
|
||||
.await
|
||||
.map_err(CoreError::Api)
|
||||
}
|
||||
|
||||
/// Get a label's editorial story.
|
||||
pub async fn get_label_story(
|
||||
&self,
|
||||
label_id: u64,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
) -> Result<LabelStoryResponse, CoreError> {
|
||||
let client = self.client.read().await;
|
||||
let client = client.as_ref().ok_or(CoreError::NotInitialized)?;
|
||||
client
|
||||
.get_label_story(label_id, limit, offset)
|
||||
.await
|
||||
.map_err(CoreError::Api)
|
||||
}
|
||||
|
||||
/// Bulk hydrate labels by ID list.
|
||||
pub async fn get_label_list(
|
||||
&self,
|
||||
label_ids: Vec<u64>,
|
||||
) -> Result<LabelGetListResponse, CoreError> {
|
||||
let client = self.client.read().await;
|
||||
let client = client.as_ref().ok_or(CoreError::NotInitialized)?;
|
||||
client
|
||||
.get_label_list(&label_ids)
|
||||
.await
|
||||
.map_err(CoreError::Api)
|
||||
}
|
||||
|
||||
// ==================== Event Emission ====================
|
||||
|
||||
/// Emit an event to the frontend adapter
|
||||
|
||||
Reference in new issue
Block a user