use std::{str::FromStr, string::FromUtf8Error}; use serde::{Deserialize, Serialize}; use serde_json::Value; use thiserror::Error; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Page { pub limit: Option, pub offset: usize, pub total_number_of_items: usize, pub items: Vec, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Item { pub id: i64, pub name: String, pub artist_types: Vec, pub url: String, pub picture: Value, pub popularity: i64, pub artist_roles: Vec, pub mixes: Mixes, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ArtistRole { pub category_id: i64, pub category: String, } #[derive(Error, Debug)] pub enum ClientError { #[error("connecting to the tidal api servers failed")] ConnectionError, #[error("internal reqwest error")] HttpClientError(#[from] reqwest::Error), #[error("internal serde url error")] SerdeUrlError(#[from] serde_urlencoded::ser::Error), #[error("authentication failed")] AuthError(String), #[error("base64 decoding failed")] Base64DecodeError(#[from] base64::DecodeError), #[error("utf8 decoding failed")] Utf8DecodeError(#[from] FromUtf8Error), #[error("json decoding failed")] JsonDecodeError(#[from] serde_json::Error), } impl From for crabidy_core::ProviderError { fn from(err: ClientError) -> Self { match err { ClientError::ConnectionError => Self::FetchError, ClientError::HttpClientError(_) => Self::FetchError, ClientError::SerdeUrlError(_) => Self::FetchError, _ => Self::Other, } } } #[derive(Serialize, Deserialize, Debug, Default)] pub struct DeviceAuthRequest { pub client_id: String, #[serde(skip_serializing_if = "Option::is_none")] pub client_secret: Option, #[serde(skip_serializing_if = "Option::is_none")] pub refresh_token: Option, #[serde(skip_serializing_if = "Option::is_none")] pub scope: Option, #[serde(skip_serializing_if = "Option::is_none")] pub grant_type: Option, #[serde(skip_serializing_if = "Option::is_none")] pub device_code: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all(deserialize = "camelCase"))] pub struct DeviceAuthResponse { pub device_code: String, pub user_code: String, pub verification_uri: String, pub verification_uri_complete: String, pub expires_in: u64, pub interval: u64, } #[derive(Serialize, Deserialize, Debug)] pub struct RefreshResponse { pub user: UserResponse, pub access_token: String, pub refresh_token: Option, pub token_type: String, pub expires_in: u64, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all(deserialize = "camelCase"))] pub struct UserResponse { pub user_id: u64, pub country_code: String, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TrackPlayback { pub track_id: i64, pub asset_presentation: String, pub audio_mode: String, pub audio_quality: String, pub manifest_mime_type: String, pub manifest_hash: String, pub manifest: String, pub album_replay_gain: f64, pub album_peak_amplitude: f64, pub track_replay_gain: f64, pub track_peak_amplitude: f64, } impl TrackPlayback { pub fn get_manifest(&self) -> Result { PlaybackManifest::from_str(&self.manifest) } } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Track { pub id: u64, pub title: String, pub duration: u64, pub replay_gain: f64, pub peak: f64, pub allow_streaming: bool, pub stream_ready: bool, pub stream_start_date: Option, pub premium_streaming_only: bool, pub track_number: u64, pub volume_number: u64, pub version: Value, pub popularity: u64, pub copyright: Option, pub url: Option, pub isrc: Option, pub editable: bool, pub explicit: bool, pub audio_quality: String, pub audio_modes: Vec, pub artist: Artist, pub artists: Vec, pub album: Album, pub mixes: Mixes, } impl From for crabidy_core::proto::crabidy::Track { fn from(track: Track) -> Self { Self { uuid: format!("track:{}", track.id), title: track.title, artist: track.artist.name, album: Some(track.album.into()), duration: Some(track.duration as u32), } } } impl From<&Track> for crabidy_core::proto::crabidy::Track { fn from(track: &Track) -> Self { Self { uuid: format!("track:{}", track.id), title: track.title.clone(), artist: track.artist.name.clone(), album: Some(track.album.clone().into()), duration: Some(track.duration as u32), } } } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Artist { pub id: i64, pub name: String, #[serde(rename = "type")] pub type_field: String, pub picture: Value, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Artist2 { pub id: i64, pub name: String, #[serde(rename = "type")] pub type_field: String, pub picture: Value, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Album { pub id: i64, pub title: String, pub cover: String, pub vibrant_color: String, pub video_cover: Value, pub release_date: Option, } impl From for crabidy_core::proto::crabidy::Album { fn from(album: Album) -> Self { Self { title: album.title, release_date: album.release_date, } } } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Mixes { #[serde(rename = "TRACK_MIX")] pub track_mix: Option, } #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all(deserialize = "camelCase"))] pub struct PlaybackManifest { pub mime_type: String, pub codecs: String, pub encryption_type: EncryptionType, pub key_id: Option, pub urls: Vec, } impl FromStr for PlaybackManifest { type Err = ClientError; fn from_str(input: &str) -> Result { let decode = base64::decode(input)?; let json = String::from_utf8(decode)?; let parsed: PlaybackManifest = serde_json::from_str(&json)?; Ok(parsed) } } #[derive(Serialize, Deserialize, Debug)] pub enum EncryptionType { #[serde(rename = "NONE")] None, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct User { pub id: i64, pub username: String, pub profile_name: String, pub first_name: String, pub last_name: String, pub email: String, pub email_verified: bool, pub country_code: String, pub created: String, pub newsletter: bool, #[serde(rename = "acceptedEULA")] pub accepted_eula: bool, pub gender: Value, pub date_of_birth: String, pub facebook_uid: i64, pub apple_uid: Value, pub partner: i64, pub tidal_id: Value, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaylistAndFavorite { #[serde(rename = "type")] pub type_field: String, pub created: String, pub playlist: Playlist, } impl From for crabidy_core::proto::crabidy::LibraryNode { fn from(a: PlaylistAndFavorite) -> Self { a.playlist.into() } } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Playlist { pub uuid: String, pub title: String, pub number_of_tracks: Option, pub number_of_videos: Option, pub creator: Option, pub description: Option, pub duration: Option, pub last_updated: Option, pub created: Option, #[serde(rename = "type")] pub type_field: Option, pub public_playlist: bool, pub url: Option, pub image: Option, pub popularity: Option, pub square_image: Option, pub promoted_artists: Option>, pub last_item_added_at: Option, } impl From for crabidy_core::proto::crabidy::LibraryNode { fn from(a: Playlist) -> Self { crabidy_core::proto::crabidy::LibraryNode { title: a.title, uuid: format!("node:playlist:{}", a.uuid), tracks: Vec::new(), parent: None, children: Vec::new(), is_queable: true, } } } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Creator { pub id: i64, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaylistTrack { pub id: i64, pub title: String, pub duration: i64, pub replay_gain: f64, pub peak: f64, pub allow_streaming: bool, pub stream_ready: bool, pub stream_start_date: Option, pub premium_streaming_only: bool, pub track_number: i64, pub volume_number: i64, pub version: Option, pub popularity: i64, pub copyright: String, pub description: Value, pub url: String, pub isrc: String, pub editable: bool, pub explicit: bool, pub audio_quality: String, pub audio_modes: Vec, pub artist: Artist, pub artists: Vec, pub album: Album, pub mixes: Mixes, pub date_added: String, pub index: i64, pub item_uuid: String, }