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:
vicrodh committed 2026-04-16 22:48:38 -06:00
1 parent e83caa7648
commit 595b71e4eb
8 files changed
+685 -8

No files matched your search

+133 -3
View File
@@ -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
+3
View File
@@ -68,9 +68,12 @@ pub use types::{
Label,
LabelDetail,
LabelExploreResponse,
LabelGetListResponse,
LabelListPage,
LabelPageContainer,
LabelPageData,
LabelPageGenericList,
LabelStoryResponse,
// Award types
AlbumAward,
AwardMagazine,
+62
View File
@@ -534,6 +534,68 @@ pub struct LabelExploreResponse {
pub items: Option<Vec<serde_json::Value>>,
}
// ============ Label Sub-resource Types (v9.7.0.3 API) ============
//
// The label page (/label/page) returns an aggregated snapshot; the
// getAlbums / getPlaylists / getTopArtists / getNextReleases /
// getAwardedReleases endpoints return paginated lists for each
// sub-resource. Per Qobuz convention these use the V2 list envelope
// { has_more, items: [...] }. Deserialized shapes are best-effort: if
// the server wraps items in e.g. { albums: { items: ... } }, the
// Optional fallbacks still keep the call non-fatal.
/// Generic paginated response from /label/get* endpoints.
///
/// `T` is typed (`Album`, `Playlist`, `Artist`) but all fields are
/// tolerant so the same struct works if the server returns a bare
/// items list or a nested one.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LabelListPage<T> {
#[serde(default)]
pub has_more: Option<bool>,
#[serde(default = "Vec::new")]
pub items: Vec<T>,
#[serde(default)]
pub total: Option<u32>,
#[serde(default)]
pub offset: Option<u32>,
#[serde(default)]
pub limit: Option<u32>,
}
/// Response from /label/story.
///
/// Shape inferred from `c30/b.java` — returns editorial / story content
/// for a label. Actual fields beyond the label identity are not fully
/// known; everything past `id` / `name` / `description` is kept open.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LabelStoryResponse {
pub id: Option<u64>,
pub name: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub story: Option<String>,
#[serde(default)]
pub image: Option<serde_json::Value>,
#[serde(default)]
pub has_more: Option<bool>,
#[serde(default)]
pub items: Option<Vec<serde_json::Value>>,
}
/// Response from /label/getList (POST). Bulk lookup that hydrates
/// label metadata for a set of label IDs.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LabelGetListResponse {
#[serde(default = "Vec::new")]
pub labels: Vec<Label>,
/// Fallback for unknown envelope shape — preserved as raw JSON if
/// the server wraps differently than expected.
#[serde(default)]
pub extra: Option<serde_json::Value>,
}
/// Genre model
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Genre {
+190
View File
@@ -1636,6 +1636,10 @@ impl QobuzClient {
}
/// Get label by ID with albums
///
/// DEPRECATED: `/label/get` is not documented in the v9.7.0.3 spec.
/// Use `get_label_albums` (catalog) + `get_label_page` (metadata).
/// Kept temporarily for backward-compat during frontend migration.
pub async fn get_label(&self, label_id: u64, limit: u32, offset: u32) -> Result<LabelDetail> {
let url = endpoints::build_url(paths::LABEL_GET);
let locale = self.locale().await;
@@ -1659,6 +1663,192 @@ impl QobuzClient {
Ok(serde_json::from_value(response)?)
}
/// Get a label's album catalog (paginated, filterable).
///
/// Replaces the legacy `/label/get?extra=albums` path.
#[allow(clippy::too_many_arguments)]
pub async fn get_label_albums(
&self,
label_id: u64,
limit: u32,
offset: u32,
sort: Option<&str>,
order: Option<&str>,
genre_ids: Option<&str>,
from_date: Option<&str>,
to_date: Option<&str>,
) -> Result<LabelListPage<Album>> {
let url = endpoints::build_url(paths::LABEL_GET_ALBUMS);
let mut params: Vec<(&str, String)> = vec![
("label_id", label_id.to_string()),
("limit", limit.to_string()),
("offset", offset.to_string()),
];
if let Some(v) = sort { params.push(("sort", v.to_string())); }
if let Some(v) = order { params.push(("order", v.to_string())); }
if let Some(v) = genre_ids { params.push(("genre_ids", v.to_string())); }
if let Some(v) = from_date { params.push(("from_date", v.to_string())); }
if let Some(v) = to_date { params.push(("to_date", v.to_string())); }
log::debug!("[API] get_label_albums({}, limit={}, offset={})", label_id, limit, offset);
let response: Value = self
.signed_get(&url, "labelgetalbums", &params)
.await?
.json()
.await?;
Ok(serde_json::from_value(response)?)
}
/// Get a label's upcoming releases.
pub async fn get_label_next_releases(
&self,
label_id: u64,
limit: u32,
offset: u32,
genre_ids: Option<&str>,
) -> Result<LabelListPage<Album>> {
let url = endpoints::build_url(paths::LABEL_GET_NEXT_RELEASES);
let mut params: Vec<(&str, String)> = vec![
("label_id", label_id.to_string()),
("limit", limit.to_string()),
("offset", offset.to_string()),
];
if let Some(v) = genre_ids { params.push(("genre_ids", v.to_string())); }
log::debug!("[API] get_label_next_releases({})", label_id);
let response: Value = self
.signed_get(&url, "labelgetnextreleases", &params)
.await?
.json()
.await?;
Ok(serde_json::from_value(response)?)
}
/// Get a label's press-awarded releases.
pub async fn get_label_awarded_releases(
&self,
label_id: u64,
limit: u32,
offset: u32,
sort: Option<&str>,
order: Option<&str>,
genre_ids: Option<&str>,
) -> Result<LabelListPage<Album>> {
let url = endpoints::build_url(paths::LABEL_GET_AWARDED_RELEASES);
let mut params: Vec<(&str, String)> = vec![
("label_id", label_id.to_string()),
("limit", limit.to_string()),
("offset", offset.to_string()),
];
if let Some(v) = sort { params.push(("sort", v.to_string())); }
if let Some(v) = order { params.push(("order", v.to_string())); }
if let Some(v) = genre_ids { params.push(("genre_ids", v.to_string())); }
log::debug!("[API] get_label_awarded_releases({})", label_id);
let response: Value = self
.signed_get(&url, "labelgetawardedreleases", &params)
.await?
.json()
.await?;
Ok(serde_json::from_value(response)?)
}
/// Get a label's curated playlists (paginated).
pub async fn get_label_playlists(
&self,
label_id: u64,
limit: u32,
offset: u32,
) -> Result<LabelListPage<Playlist>> {
let url = endpoints::build_url(paths::LABEL_GET_PLAYLISTS);
log::debug!("[API] get_label_playlists({})", label_id);
let response: Value = self
.signed_get(&url, "labelgetplaylists", &[
("label_id", label_id.to_string()),
("limit", limit.to_string()),
("offset", offset.to_string()),
])
.await?
.json()
.await?;
Ok(serde_json::from_value(response)?)
}
/// Get a label's top artists (paginated).
pub async fn get_label_top_artists(
&self,
label_id: u64,
limit: u32,
offset: u32,
) -> Result<LabelListPage<Artist>> {
let url = endpoints::build_url(paths::LABEL_GET_TOP_ARTISTS);
log::debug!("[API] get_label_top_artists({})", label_id);
let response: Value = self
.signed_get(&url, "labelgettopartists", &[
("label_id", label_id.to_string()),
("limit", limit.to_string()),
("offset", offset.to_string()),
])
.await?
.json()
.await?;
Ok(serde_json::from_value(response)?)
}
/// Get a label's editorial / story content.
pub async fn get_label_story(
&self,
label_id: u64,
limit: u32,
offset: u32,
) -> Result<LabelStoryResponse> {
let url = endpoints::build_url(paths::LABEL_STORY);
log::debug!("[API] get_label_story({})", label_id);
let response: Value = self
.signed_get(&url, "labelstory", &[
("label_id", label_id.to_string()),
("limit", limit.to_string()),
("offset", offset.to_string()),
])
.await?
.json()
.await?;
Ok(serde_json::from_value(response)?)
}
/// Bulk lookup a set of labels by ID (POST).
///
/// Follows the same signing convention as `get_tracks_batch` (see
/// `track/getList`): the sig covers the joined ID list as a query
/// string key, and the JSON body carries the list itself.
pub async fn get_label_list(&self, label_ids: &[u64]) -> Result<LabelGetListResponse> {
let url = endpoints::build_url(paths::LABEL_GET_LIST);
let headers = self.api_headers().await?;
let timestamp = get_timestamp();
let secret = self.secret().await?;
let ids_str: String = label_ids
.iter()
.map(|id| id.to_string())
.collect::<Vec<_>>()
.join(",");
let sig = sign_request("labelgetList", &[("label_ids", &ids_str)], timestamp, &secret);
let body = serde_json::json!({ "label_ids": label_ids });
log::debug!("[API] get_label_list POST ({} ids)", label_ids.len());
let response: Value = self
.http
.post(&url)
.headers(headers)
.query(&[("request_ts", timestamp.to_string()), ("request_sig", sig)])
.json(&body)
.send()
.await?
.json()
.await?;
Ok(serde_json::from_value(response)?)
}
// === Authenticated endpoints ===
/// Get stream URL for a track (requires auth + signature)
+10
View File
@@ -53,9 +53,19 @@ pub mod paths {
pub const FAVORITE_GET_NEW_RELEASES: &str = "/favorite/getNewReleases";
// Label
// NOTE: /label/get is retained only for backward-compat until the
// frontend is fully migrated; the v9.7.0.3 spec drops it in favor of
// /label/getAlbums.
pub const LABEL_GET: &str = "/label/get";
pub const LABEL_PAGE: &str = "/label/page";
pub const LABEL_EXPLORE: &str = "/label/explore";
pub const LABEL_GET_ALBUMS: &str = "/label/getAlbums";
pub const LABEL_GET_NEXT_RELEASES: &str = "/label/getNextReleases";
pub const LABEL_GET_AWARDED_RELEASES: &str = "/label/getAwardedReleases";
pub const LABEL_GET_PLAYLISTS: &str = "/label/getPlaylists";
pub const LABEL_GET_TOP_ARTISTS: &str = "/label/getTopArtists";
pub const LABEL_STORY: &str = "/label/story";
pub const LABEL_GET_LIST: &str = "/label/getList";
// Award
pub const AWARD_PAGE: &str = "/award/page";
+184 -2
View File
@@ -2,8 +2,9 @@ use tauri::State;
use qbz_models::{
Album, Artist, AwardPageData, DiscoverAlbum, DiscoverData, DiscoverPlaylistsResponse,
DiscoverResponse, GenreInfo, LabelDetail, LabelExploreResponse, LabelPageData,
PageArtistResponse, PlaylistTag, SearchResultsPage, Track,
DiscoverResponse, GenreInfo, LabelDetail, LabelExploreResponse, LabelGetListResponse,
LabelListPage, LabelPageData, LabelStoryResponse, PageArtistResponse, Playlist, PlaylistTag,
SearchResultsPage, Track,
};
use crate::artist_blacklist::BlacklistState;
@@ -442,6 +443,187 @@ pub async fn v2_get_label_page(
.map_err(RuntimeError::Internal)
}
/// Get a label's album catalog (paginated, sortable, filterable).
/// Replaces the legacy `v2_get_label` which called `/label/get`.
#[tauri::command]
#[allow(non_snake_case, clippy::too_many_arguments)]
pub async fn v2_get_label_albums(
labelId: u64,
limit: Option<u32>,
offset: Option<u32>,
sort: Option<String>,
order: Option<String>,
genreIds: Option<String>,
fromDate: Option<String>,
toDate: Option<String>,
bridge: State<'_, CoreBridgeState>,
runtime: State<'_, RuntimeManagerState>,
) -> Result<LabelListPage<Album>, RuntimeError> {
runtime
.manager()
.check_requirements(CommandRequirement::RequiresCoreBridgeAuth)
.await?;
let limit = limit.unwrap_or(50);
let offset = offset.unwrap_or(0);
log::info!("[V2] get_label_albums: {} limit={} offset={}", labelId, limit, offset);
let bridge = bridge.get().await;
bridge
.get_label_albums(labelId, limit, offset, sort, order, genreIds, fromDate, toDate)
.await
.map_err(RuntimeError::Internal)
}
/// Get a label's upcoming releases.
#[tauri::command]
#[allow(non_snake_case)]
pub async fn v2_get_label_next_releases(
labelId: u64,
limit: Option<u32>,
offset: Option<u32>,
genreIds: Option<String>,
bridge: State<'_, CoreBridgeState>,
runtime: State<'_, RuntimeManagerState>,
) -> Result<LabelListPage<Album>, RuntimeError> {
runtime
.manager()
.check_requirements(CommandRequirement::RequiresCoreBridgeAuth)
.await?;
let limit = limit.unwrap_or(50);
let offset = offset.unwrap_or(0);
log::info!("[V2] get_label_next_releases: {}", labelId);
let bridge = bridge.get().await;
bridge
.get_label_next_releases(labelId, limit, offset, genreIds)
.await
.map_err(RuntimeError::Internal)
}
/// Get a label's press-awarded releases.
#[tauri::command]
#[allow(non_snake_case)]
pub async fn v2_get_label_awarded_releases(
labelId: u64,
limit: Option<u32>,
offset: Option<u32>,
sort: Option<String>,
order: Option<String>,
genreIds: Option<String>,
bridge: State<'_, CoreBridgeState>,
runtime: State<'_, RuntimeManagerState>,
) -> Result<LabelListPage<Album>, RuntimeError> {
runtime
.manager()
.check_requirements(CommandRequirement::RequiresCoreBridgeAuth)
.await?;
let limit = limit.unwrap_or(50);
let offset = offset.unwrap_or(0);
log::info!("[V2] get_label_awarded_releases: {}", labelId);
let bridge = bridge.get().await;
bridge
.get_label_awarded_releases(labelId, limit, offset, sort, order, genreIds)
.await
.map_err(RuntimeError::Internal)
}
/// Get a label's curated playlists.
#[tauri::command]
#[allow(non_snake_case)]
pub async fn v2_get_label_playlists(
labelId: u64,
limit: Option<u32>,
offset: Option<u32>,
bridge: State<'_, CoreBridgeState>,
runtime: State<'_, RuntimeManagerState>,
) -> Result<LabelListPage<Playlist>, RuntimeError> {
runtime
.manager()
.check_requirements(CommandRequirement::RequiresCoreBridgeAuth)
.await?;
let limit = limit.unwrap_or(50);
let offset = offset.unwrap_or(0);
log::info!("[V2] get_label_playlists: {}", labelId);
let bridge = bridge.get().await;
bridge
.get_label_playlists(labelId, limit, offset)
.await
.map_err(RuntimeError::Internal)
}
/// Get a label's top artists.
#[tauri::command]
#[allow(non_snake_case)]
pub async fn v2_get_label_top_artists(
labelId: u64,
limit: Option<u32>,
offset: Option<u32>,
bridge: State<'_, CoreBridgeState>,
runtime: State<'_, RuntimeManagerState>,
) -> Result<LabelListPage<Artist>, RuntimeError> {
runtime
.manager()
.check_requirements(CommandRequirement::RequiresCoreBridgeAuth)
.await?;
let limit = limit.unwrap_or(50);
let offset = offset.unwrap_or(0);
log::info!("[V2] get_label_top_artists: {}", labelId);
let bridge = bridge.get().await;
bridge
.get_label_top_artists(labelId, limit, offset)
.await
.map_err(RuntimeError::Internal)
}
/// Get a label's editorial story.
#[tauri::command]
#[allow(non_snake_case)]
pub async fn v2_get_label_story(
labelId: u64,
limit: Option<u32>,
offset: Option<u32>,
bridge: State<'_, CoreBridgeState>,
runtime: State<'_, RuntimeManagerState>,
) -> Result<LabelStoryResponse, RuntimeError> {
runtime
.manager()
.check_requirements(CommandRequirement::RequiresCoreBridgeAuth)
.await?;
let limit = limit.unwrap_or(50);
let offset = offset.unwrap_or(0);
log::info!("[V2] get_label_story: {}", labelId);
let bridge = bridge.get().await;
bridge
.get_label_story(labelId, limit, offset)
.await
.map_err(RuntimeError::Internal)
}
/// Bulk hydrate labels by a list of IDs (POST /label/getList).
#[tauri::command]
#[allow(non_snake_case)]
pub async fn v2_get_label_list(
labelIds: Vec<u64>,
bridge: State<'_, CoreBridgeState>,
runtime: State<'_, RuntimeManagerState>,
) -> Result<LabelGetListResponse, RuntimeError> {
runtime
.manager()
.check_requirements(CommandRequirement::RequiresCoreBridgeAuth)
.await?;
log::info!("[V2] get_label_list: {} ids", labelIds.len());
let bridge = bridge.get().await;
bridge
.get_label_list(labelIds)
.await
.map_err(RuntimeError::Internal)
}
/// Enumerate award catalog — paginated list of awards with their ids
/// and names. Used by the frontend to resolve award ids by name when
/// /album/get omits them.
+96 -3
View File
@@ -14,9 +14,9 @@ use qbz_audio::{settings::AudioSettingsStore, AudioDiagnostic, AudioSettings, Vi
use qbz_core::QbzCore;
use qbz_models::{
Album, Artist, DiscoverAlbum, DiscoverData, DiscoverPlaylistsResponse, DiscoverResponse,
GenreInfo, LabelDetail, LabelExploreResponse, LabelPageData, PageArtistResponse, Playlist,
PlaylistTag, Quality, QueueState, QueueTrack, RepeatMode, SearchResultsPage, StreamUrl, Track,
UserSession,
GenreInfo, LabelDetail, LabelExploreResponse, LabelGetListResponse, LabelListPage,
LabelPageData, LabelStoryResponse, PageArtistResponse, Playlist, PlaylistTag, Quality,
QueueState, QueueTrack, RepeatMode, SearchResultsPage, StreamUrl, Track, UserSession,
};
use qbz_player::{PlaybackState, Player};
@@ -620,6 +620,99 @@ impl CoreBridge {
.map_err(|e| e.to_string())
}
/// Label catalog (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>, String> {
self.core
.get_label_albums(label_id, limit, offset, sort, order, genre_ids, from_date, to_date)
.await
.map_err(|e| e.to_string())
}
pub async fn get_label_next_releases(
&self,
label_id: u64,
limit: u32,
offset: u32,
genre_ids: Option<String>,
) -> Result<LabelListPage<Album>, String> {
self.core
.get_label_next_releases(label_id, limit, offset, genre_ids)
.await
.map_err(|e| e.to_string())
}
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>, String> {
self.core
.get_label_awarded_releases(label_id, limit, offset, sort, order, genre_ids)
.await
.map_err(|e| e.to_string())
}
pub async fn get_label_playlists(
&self,
label_id: u64,
limit: u32,
offset: u32,
) -> Result<LabelListPage<Playlist>, String> {
self.core
.get_label_playlists(label_id, limit, offset)
.await
.map_err(|e| e.to_string())
}
pub async fn get_label_top_artists(
&self,
label_id: u64,
limit: u32,
offset: u32,
) -> Result<LabelListPage<Artist>, String> {
self.core
.get_label_top_artists(label_id, limit, offset)
.await
.map_err(|e| e.to_string())
}
pub async fn get_label_story(
&self,
label_id: u64,
limit: u32,
offset: u32,
) -> Result<LabelStoryResponse, String> {
self.core
.get_label_story(label_id, limit, offset)
.await
.map_err(|e| e.to_string())
}
pub async fn get_label_list(
&self,
label_ids: Vec<u64>,
) -> Result<LabelGetListResponse, String> {
self.core
.get_label_list(label_ids)
.await
.map_err(|e| e.to_string())
}
// ==================== Streaming ====================
/// Get stream URL for a track with quality fallback
+7
View File
@@ -1620,6 +1620,13 @@ pub fn run() {
commands_v2::v2_get_label,
commands_v2::v2_get_label_page,
commands_v2::v2_get_label_explore,
commands_v2::v2_get_label_albums,
commands_v2::v2_get_label_next_releases,
commands_v2::v2_get_label_awarded_releases,
commands_v2::v2_get_label_playlists,
commands_v2::v2_get_label_top_artists,
commands_v2::v2_get_label_story,
commands_v2::v2_get_label_list,
commands_v2::v2_get_award_page,
commands_v2::v2_get_award_albums,
commands_v2::v2_get_award_explore,