163 lines
No EOL
4.4 KiB
Rust
163 lines
No EOL
4.4 KiB
Rust
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<Instant>,
|
|
}
|
|
|
|
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::<Url>().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<P, T>(&mut self, method: &str, params: &P) -> Result<T, APIError>
|
|
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<P, T>(&mut self, method: &str, params: &P) -> Result<T, APIError>
|
|
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<Format>, track_tokens: Vec<&str>) -> Result<Response, reqwest::Error> {
|
|
let formats: Vec<DeezerFormat> = 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
|
|
}
|
|
} |