feat(qbz-qobuz): implement Phase 4 - Qobuz API client
Extracted Qobuz API client from qbz-nix to qbz-qobuz crate. Modules: - client.rs: Main QobuzClient with all API methods - auth.rs: Request signing and authentication - bundle.rs: Bundle token extraction - endpoints.rs: API endpoint definitions - error.rs: API error types - performers.rs: Performers parsing Updated qbz-models with additional types: - Discover types (DiscoverResponse, DiscoverAlbum, etc.) - Artist page types (PageArtistResponse, ReleasesGridResponse, etc.) - Playlist tags (PlaylistTag, PlaylistTagsResponse) Dependencies: - reqwest for HTTP - md-5 for request signing - futures-util for concurrent requests - anyhow for error handling
This commit is contained in:
1 parent
ad7333d426
commit
0be0f6dcb0
10 files changed
+2410
-13
No files matched your search
@@ -46,4 +46,15 @@ pub use types::{
|
||||
PlaylistDuplicateResult, PlaylistGenre, PlaylistOwner, PlaylistWithTrackIds, Quality,
|
||||
SearchResults, SearchResultsPage, StreamRestriction, StreamUrl, Track, TracksContainer,
|
||||
UserSession,
|
||||
// Discover types
|
||||
DiscoverAlbum, DiscoverAlbumDates, DiscoverAlbumImage, DiscoverArtist, DiscoverAudioInfo,
|
||||
DiscoverContainer, DiscoverContainers, DiscoverData, DiscoverPlaylist, DiscoverPlaylistImage,
|
||||
DiscoverPlaylistsResponse, DiscoverResponse, PlaylistTag, PlaylistTagsResponse, RawPlaylistTag,
|
||||
// Artist page types
|
||||
PageArtistAward, PageArtistBiography, PageArtistImages, PageArtistName,
|
||||
PageArtistPhysicalSupport, PageArtistPlaylist, PageArtistPlaylistImages,
|
||||
PageArtistPlaylistOwner, PageArtistPlaylists, PageArtistPortrait, PageArtistRelease,
|
||||
PageArtistReleaseArtist, PageArtistReleaseContributor, PageArtistReleaseGroup,
|
||||
PageArtistResponse, PageArtistRights, PageArtistSimilar, PageArtistSimilarItem,
|
||||
PageArtistTrack, PageArtistTrackAlbum, ReleasesGridResponse,
|
||||
};
|
||||
@@ -410,3 +410,323 @@ pub struct Favorites {
|
||||
pub tracks: Option<SearchResultsPage<Track>>,
|
||||
pub artists: Option<SearchResultsPage<Artist>>,
|
||||
}
|
||||
|
||||
// ============ Discover API Types ============
|
||||
|
||||
/// Discover index response
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DiscoverResponse {
|
||||
pub containers: DiscoverContainers,
|
||||
}
|
||||
|
||||
/// All discover containers
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DiscoverContainers {
|
||||
pub playlists: Option<DiscoverContainer<DiscoverPlaylist>>,
|
||||
pub ideal_discography: Option<DiscoverContainer<DiscoverAlbum>>,
|
||||
pub playlists_tags: Option<DiscoverContainer<PlaylistTag>>,
|
||||
pub new_releases: Option<DiscoverContainer<DiscoverAlbum>>,
|
||||
pub qobuzissims: Option<DiscoverContainer<DiscoverAlbum>>,
|
||||
pub most_streamed: Option<DiscoverContainer<DiscoverAlbum>>,
|
||||
pub press_awards: Option<DiscoverContainer<DiscoverAlbum>>,
|
||||
pub album_of_the_week: Option<DiscoverContainer<DiscoverAlbum>>,
|
||||
}
|
||||
|
||||
/// Generic discover container
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DiscoverContainer<T> {
|
||||
pub id: String,
|
||||
pub data: DiscoverData<T>,
|
||||
}
|
||||
|
||||
/// Generic discover data with items
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DiscoverData<T> {
|
||||
pub has_more: bool,
|
||||
pub items: Vec<T>,
|
||||
}
|
||||
|
||||
/// Playlist from discover endpoint
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DiscoverPlaylist {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub owner: PlaylistOwner,
|
||||
pub image: DiscoverPlaylistImage,
|
||||
pub description: Option<String>,
|
||||
pub duration: u32,
|
||||
pub tracks_count: u32,
|
||||
pub genres: Option<Vec<PlaylistGenre>>,
|
||||
pub tags: Option<Vec<PlaylistTag>>,
|
||||
}
|
||||
|
||||
/// Playlist image from discover
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DiscoverPlaylistImage {
|
||||
pub rectangle: Option<String>,
|
||||
pub covers: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Playlist tag (for filtering)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlaylistTag {
|
||||
pub id: u64,
|
||||
pub slug: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Raw playlist tag from /playlist/getTags
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RawPlaylistTag {
|
||||
pub slug: String,
|
||||
pub name_json: String,
|
||||
pub position: Option<String>,
|
||||
pub is_discover: Option<String>,
|
||||
pub featured_tag_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Response from /playlist/getTags
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlaylistTagsResponse {
|
||||
pub tags: Vec<RawPlaylistTag>,
|
||||
}
|
||||
|
||||
/// Response from discover/playlists endpoint
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DiscoverPlaylistsResponse {
|
||||
pub has_more: bool,
|
||||
pub items: Vec<DiscoverPlaylist>,
|
||||
}
|
||||
|
||||
/// Album from discover endpoint
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DiscoverAlbum {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub version: Option<String>,
|
||||
pub track_count: Option<u32>,
|
||||
pub duration: Option<u32>,
|
||||
pub parental_warning: Option<bool>,
|
||||
pub image: DiscoverAlbumImage,
|
||||
pub artists: Vec<DiscoverArtist>,
|
||||
pub label: Option<Label>,
|
||||
pub genre: Option<Genre>,
|
||||
pub dates: Option<DiscoverAlbumDates>,
|
||||
pub audio_info: Option<DiscoverAudioInfo>,
|
||||
}
|
||||
|
||||
/// Album image from discover endpoint
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DiscoverAlbumImage {
|
||||
pub small: Option<String>,
|
||||
pub thumbnail: Option<String>,
|
||||
pub large: Option<String>,
|
||||
}
|
||||
|
||||
/// Artist in discover album
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DiscoverArtist {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub roles: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Album dates from discover
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DiscoverAlbumDates {
|
||||
pub download: Option<String>,
|
||||
pub original: Option<String>,
|
||||
pub stream: Option<String>,
|
||||
}
|
||||
|
||||
/// Audio info from discover album
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DiscoverAudioInfo {
|
||||
pub maximum_sampling_rate: Option<f64>,
|
||||
pub maximum_bit_depth: Option<u32>,
|
||||
pub maximum_channel_count: Option<u32>,
|
||||
}
|
||||
|
||||
// ============ Artist Page Types (/artist/page) ============
|
||||
|
||||
/// Top-level response from /artist/page
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistResponse {
|
||||
pub id: u64,
|
||||
pub name: PageArtistName,
|
||||
pub artist_category: Option<String>,
|
||||
pub biography: Option<PageArtistBiography>,
|
||||
pub images: Option<PageArtistImages>,
|
||||
pub similar_artists: Option<PageArtistSimilar>,
|
||||
pub top_tracks: Option<Vec<PageArtistTrack>>,
|
||||
pub last_release: Option<serde_json::Value>,
|
||||
pub releases: Option<Vec<PageArtistReleaseGroup>>,
|
||||
pub tracks_appears_on: Option<Vec<PageArtistTrack>>,
|
||||
pub playlists: Option<PageArtistPlaylists>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistName {
|
||||
pub display: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistBiography {
|
||||
pub content: Option<String>,
|
||||
pub source: Option<serde_json::Value>,
|
||||
pub language: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistImages {
|
||||
pub portrait: Option<PageArtistPortrait>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistPortrait {
|
||||
pub hash: String,
|
||||
pub format: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistSimilar {
|
||||
pub has_more: bool,
|
||||
pub items: Vec<PageArtistSimilarItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistSimilarItem {
|
||||
pub id: u64,
|
||||
pub name: PageArtistName,
|
||||
pub images: Option<PageArtistImages>,
|
||||
}
|
||||
|
||||
/// A group of releases by type
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistReleaseGroup {
|
||||
#[serde(rename = "type")]
|
||||
pub release_type: String,
|
||||
pub has_more: bool,
|
||||
pub items: Vec<PageArtistRelease>,
|
||||
}
|
||||
|
||||
/// A release item from /artist/page
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistRelease {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub version: Option<String>,
|
||||
pub tracks_count: Option<u32>,
|
||||
pub artist: Option<PageArtistReleaseArtist>,
|
||||
pub artists: Option<Vec<PageArtistReleaseContributor>>,
|
||||
pub image: Option<ImageSet>,
|
||||
pub label: Option<Label>,
|
||||
pub genre: Option<Genre>,
|
||||
pub release_type: Option<String>,
|
||||
pub release_tags: Option<Vec<String>>,
|
||||
pub duration: Option<u32>,
|
||||
pub dates: Option<DiscoverAlbumDates>,
|
||||
pub parental_warning: Option<bool>,
|
||||
pub audio_info: Option<DiscoverAudioInfo>,
|
||||
pub rights: Option<PageArtistRights>,
|
||||
pub awards: Option<Vec<PageArtistAward>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistReleaseArtist {
|
||||
pub id: u64,
|
||||
pub name: PageArtistName,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistReleaseContributor {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub roles: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistRights {
|
||||
pub streamable: Option<bool>,
|
||||
pub hires_streamable: Option<bool>,
|
||||
pub hires_purchasable: Option<bool>,
|
||||
pub purchasable: Option<bool>,
|
||||
pub downloadable: Option<bool>,
|
||||
pub previewable: Option<bool>,
|
||||
pub sampleable: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistAward {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub awarded_at: Option<String>,
|
||||
}
|
||||
|
||||
/// Track from /artist/page
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistTrack {
|
||||
pub id: u64,
|
||||
pub title: String,
|
||||
pub version: Option<String>,
|
||||
pub duration: Option<u32>,
|
||||
pub isrc: Option<String>,
|
||||
pub parental_warning: Option<bool>,
|
||||
pub artist: Option<PageArtistReleaseArtist>,
|
||||
pub composer: Option<serde_json::Value>,
|
||||
pub audio_info: Option<DiscoverAudioInfo>,
|
||||
pub rights: Option<PageArtistRights>,
|
||||
pub physical_support: Option<PageArtistPhysicalSupport>,
|
||||
pub album: Option<PageArtistTrackAlbum>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistPhysicalSupport {
|
||||
pub media_number: Option<u32>,
|
||||
pub track_number: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistTrackAlbum {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub version: Option<String>,
|
||||
pub image: Option<ImageSet>,
|
||||
pub label: Option<Label>,
|
||||
pub genre: Option<Genre>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistPlaylists {
|
||||
pub has_more: bool,
|
||||
pub items: Vec<PageArtistPlaylist>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistPlaylist {
|
||||
pub id: u64,
|
||||
pub title: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub owner: Option<PageArtistPlaylistOwner>,
|
||||
pub tracks_count: Option<u32>,
|
||||
pub duration: Option<u32>,
|
||||
pub images: Option<PageArtistPlaylistImages>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistPlaylistOwner {
|
||||
pub id: u64,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageArtistPlaylistImages {
|
||||
pub rectangle: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Response from /artist/getReleasesGrid
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReleasesGridResponse {
|
||||
pub has_more: bool,
|
||||
pub items: Vec<PageArtistRelease>,
|
||||
}
|
||||
Reference in new issue
Block a user