use serde::{Serialize, Deserialize, de::DeserializeOwned}; use std::{env, marker::Sized, time::Instant, sync::Arc}; use thiserror::Error; use serde_json::{json, value::from_value}; use reqwest::{Client, Response, cookie::Jar, Url}; #[derive(Deserialize)] struct DeezerResponse { error: serde_json::Value, results: serde_json::Value, } #[derive(Deserialize, Serialize, Debug)] #[allow(non_camel_case_types)] pub enum Format { AAC_64, AAC_96, FLAC, MP3_MISC, MP3_32, MP3_64, MP3_128, MP3_192, MP3_256, MP3_320, SBC_256, MP4_RA1, MP4_RA2, MP4_RA3, } #[derive(Serialize)] struct DeezerFormat<'a> { cipher: &'a str, format: &'a Format, } #[derive(Error, Debug)] pub enum APIError { #[error("reqwest error")] Reqwest(#[from] reqwest::Error), #[error("Deezer API error (code: {code:?}, message: {message:?})")] DeezerError { code: String, message: String, }, #[error("Couldn't deserialize JSON response")] JSON(#[from] serde_json::error::Error) } #[derive(Clone)] pub struct APIClient { client: Client, pub license_token: String, check_form: String, renew_instant: Option, } impl APIClient { pub fn new() -> Self { if let Ok(arl) = env::var("ARL") { let builder = Client::builder(); let cookie = format!("arl={}; Domain=.deezer.com", arl); let url = "https://www.deezer.com".parse::().unwrap(); let jar = Jar::default(); jar.add_cookie_str(&cookie, &url); let builder = builder.cookie_provider(Arc::new(jar)); Self { client: builder.build().unwrap(), license_token: String::new(), check_form: String::new(), renew_instant: None, } } else { panic!("ARL environment variable required"); } } async fn no_renew_api_call(&mut self, method: &str, params: &P) -> Result where P: Serialize + ?Sized, T: DeserializeOwned { let check_form; if method == "deezer.getUserData" { check_form = "" } else { check_form = &self.check_form; } let json: DeezerResponse = self.client.post("https://www.deezer.com/ajax/gw-light.php") .query(&[ ("method", method), ("input", "3"), ("api_version", "1.0"), ("api_token", check_form) ]) .json(params) .send() .await? .json() .await?; if let Some(error) = json.error.as_object() { for (code, message) in error { return Err(APIError::DeezerError { code: code.clone(), message: message.as_str().unwrap().to_string() }) } } Ok(from_value(json.results)?) } pub async fn api_call(&mut self, method: &str, params: &P) -> Result where P: Serialize + ?Sized, T: DeserializeOwned { if let Some(i) = self.renew_instant { if i.elapsed().as_secs() >= 3600 { self.renew().await?; } } else { self.renew().await?; } self.no_renew_api_call(method, params).await } async fn renew(&mut self) -> Result<(), APIError> { let user_data: serde_json::Value = self.no_renew_api_call("deezer.getUserData", &json!({})).await?; self.check_form = user_data["checkForm"].as_str().unwrap().to_string(); self.license_token = user_data["USER"]["OPTIONS"]["license_token"].as_str().unwrap().to_string(); self.renew_instant = Some(Instant::now()); Ok(()) } pub async fn get_media(&self, formats: &Vec, track_tokens: Vec<&str>) -> Result { let formats: Vec = formats.iter().map(|f| DeezerFormat { cipher: "BF_CBC_STRIPE", format: f }).collect(); let req = json!({ "license_token": self.license_token, "media": [{ "formats": formats, "type": "FULL" }], "track_tokens": track_tokens }); self.client.post("https://media.deezer.com/v1/get_url") .json(&req) .send() .await } }