367 lines
10 KiB
Rust
367 lines
10 KiB
Rust
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<T> {
|
|
pub limit: Option<usize>,
|
|
pub offset: usize,
|
|
pub total_number_of_items: usize,
|
|
pub items: Vec<T>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Item {
|
|
pub id: i64,
|
|
pub name: String,
|
|
pub artist_types: Vec<String>,
|
|
pub url: String,
|
|
pub picture: Value,
|
|
pub popularity: i64,
|
|
pub artist_roles: Vec<ArtistRole>,
|
|
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<ClientError> 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<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub refresh_token: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub scope: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub grant_type: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub device_code: Option<String>,
|
|
}
|
|
|
|
#[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<String>,
|
|
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, ClientError> {
|
|
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<String>,
|
|
pub premium_streaming_only: bool,
|
|
pub track_number: u64,
|
|
pub volume_number: u64,
|
|
pub version: Value,
|
|
pub popularity: u64,
|
|
pub copyright: Option<String>,
|
|
pub url: Option<String>,
|
|
pub isrc: Option<String>,
|
|
pub editable: bool,
|
|
pub explicit: bool,
|
|
pub audio_quality: String,
|
|
pub audio_modes: Vec<String>,
|
|
pub artist: Artist,
|
|
pub artists: Vec<Artist>,
|
|
pub album: Album,
|
|
pub mixes: Mixes,
|
|
}
|
|
|
|
impl From<Track> 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<String>,
|
|
}
|
|
|
|
impl From<Album> 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<String>,
|
|
}
|
|
|
|
#[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<String>,
|
|
pub urls: Vec<String>,
|
|
}
|
|
|
|
impl FromStr for PlaybackManifest {
|
|
type Err = ClientError;
|
|
fn from_str(input: &str) -> Result<PlaybackManifest, Self::Err> {
|
|
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<PlaylistAndFavorite> 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<usize>,
|
|
pub number_of_videos: Option<usize>,
|
|
pub creator: Option<Creator>,
|
|
pub description: Option<String>,
|
|
pub duration: Option<u32>,
|
|
pub last_updated: Option<String>,
|
|
pub created: Option<String>,
|
|
#[serde(rename = "type")]
|
|
pub type_field: Option<String>,
|
|
pub public_playlist: bool,
|
|
pub url: Option<String>,
|
|
pub image: Option<String>,
|
|
pub popularity: Option<i64>,
|
|
pub square_image: Option<String>,
|
|
pub promoted_artists: Option<Vec<Value>>,
|
|
pub last_item_added_at: Option<String>,
|
|
}
|
|
|
|
impl From<Playlist> 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<String>,
|
|
pub premium_streaming_only: bool,
|
|
pub track_number: i64,
|
|
pub volume_number: i64,
|
|
pub version: Option<String>,
|
|
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<String>,
|
|
pub artist: Artist,
|
|
pub artists: Vec<Artist>,
|
|
pub album: Album,
|
|
pub mixes: Mixes,
|
|
pub date_added: String,
|
|
pub index: i64,
|
|
pub item_uuid: String,
|
|
}
|