use crate::Error;
use crate::List;
use crate::Order;
use crate::OrderDirection;
use crate::TIDAL_API_BASE_URL;
use crate::TidalClient;
use crate::album::{Album, AlbumType};
use crate::deserialize_null_default;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct Artist {
pub id: u64,
pub name: String,
#[serde(default)]
pub handle: Option<String>,
pub picture: Option<String>,
pub url: String,
pub user_id: Option<u64>,
#[serde(default)]
pub popularity: Option<u32>,
#[serde(default = "Default::default")]
pub artist_types: Vec<String>,
#[serde(default = "Default::default")]
pub artist_roles: Vec<ArtistRole>,
#[serde(default)]
pub selected_album_cover_fallback: Option<String>,
#[serde(default, deserialize_with = "deserialize_null_default")]
pub mixes: HashMap<String, String>,
pub spotlighted: bool,
}
impl Artist {
pub fn picture_url(&self, height: u16, width: u16) -> Option<String> {
match &self.picture {
Some(picture) => {
let picture_path = picture.replace('-', "/");
Some(format!(
"https://resources.tidal.com/images/{picture_path}/{height}x{width}.jpg"
))
}
None => match &self.selected_album_cover_fallback {
Some(selected_album_cover_fallback) => {
let picture_path = selected_album_cover_fallback.replace('-', "/");
Some(format!(
"https://resources.tidal.com/images/{picture_path}/{height}x{width}.jpg"
))
}
None => None,
},
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
pub struct FavoriteArtist {
pub created: String,
pub item: Artist,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct ArtistRole {
pub category: String,
pub category_id: i64,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct ArtistSummary {
pub id: u64,
pub name: String,
pub picture: Option<String>,
#[serde(default)]
pub contains_cover: bool,
#[serde(default)]
pub popularity: Option<u32>,
#[serde(rename = "type")]
#[serde(default)]
pub artist_type: Option<String>,
}
impl ArtistSummary {
pub fn picture_url(&self, height: u16, width: u16) -> Option<String> {
let picture_path = self
.picture
.as_ref()
.map(|picture| picture.replace('-', "/"));
picture_path.map(|picture_path| {
format!("https://resources.tidal.com/images/{picture_path}/{height}x{width}.jpg")
})
}
}
impl TidalClient {
pub async fn artist(&self, artist_id: u64) -> Result<Artist, Error> {
let url = format!("{TIDAL_API_BASE_URL}/artists/{artist_id}");
let params = serde_json::json!({
"countryCode": self.get_country_code(),
"locale": self.get_locale(),
"deviceType": self.get_device_type().as_ref(),
});
let resp: Artist = self
.do_request(Method::GET, &url, Some(params), None)
.await?;
Ok(resp)
}
pub async fn favorite_artists(
&self,
offset: Option<u32>,
limit: Option<u32>,
order: Option<Order>,
order_direction: Option<OrderDirection>,
) -> Result<List<FavoriteArtist>, Error> {
let user_id = self
.get_user_id()
.ok_or(Error::UserAuthenticationRequired)?;
let offset = offset.unwrap_or(0);
let limit = limit.unwrap_or(100);
let url = format!("{TIDAL_API_BASE_URL}/users/{user_id}/favorites/artists");
let params = serde_json::json!({
"offset": offset,
"limit": limit,
"order": order.unwrap_or(Order::Date).as_ref(),
"orderDirection": order_direction.unwrap_or(OrderDirection::Desc).as_ref(),
"countryCode": self.get_country_code(),
"locale": self.get_locale(),
"deviceType": self.get_device_type().as_ref(),
});
let resp: List<FavoriteArtist> = self
.do_request(Method::GET, &url, Some(params), None)
.await?;
Ok(resp)
}
pub async fn artist_albums(
&self,
artist_id: u64,
album_type: Option<AlbumType>,
offset: Option<u32>,
limit: Option<u32>,
) -> Result<List<Album>, Error> {
let offset = offset.unwrap_or(0);
let limit = limit.unwrap_or(100);
let url = format!("{TIDAL_API_BASE_URL}/artists/{artist_id}/albums");
let mut params = serde_json::json!({
"offset": offset,
"limit": limit,
"countryCode": self.get_country_code(),
"locale": self.get_locale(),
"deviceType": self.get_device_type().as_ref(),
});
if let Some(album_type) = album_type {
params["filter"] = serde_json::Value::String(album_type.as_ref().to_string());
}
let resp: List<Album> = self
.do_request(Method::GET, &url, Some(params), None)
.await?;
Ok(resp)
}
pub async fn add_favorite_artist(&self, artist_id: u64) -> Result<(), Error> {
let user_id = self
.get_user_id()
.ok_or(Error::UserAuthenticationRequired)?;
let url = format!("{TIDAL_API_BASE_URL}/users/{user_id}/favorites/artists");
let params = serde_json::json!({
"artistId": artist_id,
"countryCode": self.get_country_code(),
"locale": self.get_locale(),
"deviceType": self.get_device_type().as_ref(),
});
let _: Value = self
.do_request(Method::POST, &url, Some(params), None)
.await?;
Ok(())
}
pub async fn remove_favorite_artist(&self, artist_id: u64) -> Result<(), Error> {
let user_id = self
.get_user_id()
.ok_or(Error::UserAuthenticationRequired)?;
let url = format!("{TIDAL_API_BASE_URL}/users/{user_id}/favorites/artists/{artist_id}");
let params = serde_json::json!({
"countryCode": self.get_country_code(),
"locale": self.get_locale(),
"deviceType": self.get_device_type().as_ref(),
});
let _: Value = self
.do_request(Method::DELETE, &url, Some(params), None)
.await?;
Ok(())
}
}