diff --git a/Cargo.lock b/Cargo.lock index 06b3081..ec30a9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,6 +142,17 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" +[[package]] +name = "cookie" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "cookie" version = "0.15.1" @@ -153,6 +164,22 @@ dependencies = [ "version_check", ] +[[package]] +name = "cookie_store" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3" +dependencies = [ + "cookie 0.14.4", + "idna", + "log", + "publicsuffix", + "serde", + "serde_json", + "time", + "url", +] + [[package]] name = "crc32fast" version = "1.2.1" @@ -803,6 +830,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "publicsuffix" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b4ce31ff0a27d93c8de1849cf58162283752f065a90d508f1105fa6c9a213f" +dependencies = [ + "idna", + "url", +] + [[package]] name = "quote" version = "1.0.9" @@ -916,6 +953,8 @@ dependencies = [ "async-compression", "base64", "bytes", + "cookie 0.14.4", + "cookie_store", "encoding_rs", "futures-core", "futures-util", @@ -934,6 +973,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "time", "tokio", "tokio-rustls", "tokio-util", @@ -1021,7 +1061,7 @@ version = "0.5.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23c8b7d512d2fcac2316ebe590cde67573844b99e6cc9ee0f53375fa16e25ebd" dependencies = [ - "cookie", + "cookie 0.15.1", "either", "http", "hyper", diff --git a/Cargo.toml b/Cargo.toml index f81e83d..1613650 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "dzmedia" version = "0.1.0" -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] rocket = { version = "0.5.0-rc.1", features = ["json"], default-features = false } serde = { version = "1.0", features = ["derive"] } -reqwest = { version = "0.11", features = ["json", "rustls-tls", "gzip"], default-features = false } +reqwest = { version = "0.11", features = ["json", "rustls-tls", "cookies", "gzip"], default-features = false } serde_json = "1.0" thiserror = "1.0" regex = "1" \ No newline at end of file diff --git a/src/api.rs b/src/api.rs index fdd6f6e..4fd7beb 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,8 +1,8 @@ use serde::{Serialize, Deserialize, de::DeserializeOwned}; -use std::{env, marker::Sized, time::Instant}; +use std::{env, marker::Sized, time::Instant, sync::Arc}; use thiserror::Error; use serde_json::{json, value::from_value}; -use regex::Regex; +use reqwest::{Client, Response, cookie::Jar, Url}; #[derive(Deserialize)] struct DeezerResponse { @@ -52,23 +52,28 @@ pub enum APIError { #[derive(Clone)] pub struct APIClient { - client: reqwest::Client, - license_token: String, + client: Client, + pub license_token: String, check_form: String, - pub sid: String, - arl: 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: reqwest::Client::new(), + client: builder.build().unwrap(), license_token: String::new(), check_form: String::new(), - sid: String::new(), - arl, renew_instant: None, } } else { @@ -76,8 +81,9 @@ impl APIClient { } } - async fn raw_api_call(&self, method: &str, params: &T) -> Result - where T: Serialize + ?Sized + 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" { @@ -86,13 +92,7 @@ impl APIClient { check_form = &self.check_form; } - let mut cookies = format!("arl={}", self.arl); - - if self.sid != "" && method != "deezer.getUserData" { - cookies.push_str(&format!("; sid={}", self.sid)) - } - - self.client.post("https://www.deezer.com/ajax/gw-light.php") + let json: DeezerResponse = self.client.post("https://www.deezer.com/ajax/gw-light.php") .query(&[ ("method", method), ("input", "3"), @@ -100,26 +100,9 @@ impl APIClient { ("api_token", check_form) ]) .json(params) - .header("cookie", &cookies) .send() - .await - } - - 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?; - } - - let json = self.raw_api_call(method, params) .await? - .json::() + .json() .await?; if let Some(error) = json.error.as_object() { @@ -134,24 +117,33 @@ impl APIClient { Ok(from_value(json.results)?) } - async fn renew(&mut self) -> Result<(), reqwest::Error> { - let resp = self.raw_api_call("deezer.getUserData", &json!({})).await?; + 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?; + } - let sid = resp.headers().get("set-cookie").unwrap().to_str().unwrap(); - let sid = Regex::new("^sid=(fr[\\da-f]+)").unwrap().captures(sid).unwrap(); - self.sid = (&sid[1]).to_string(); + self.no_renew_api_call(method, params).await + } - let json = resp.json::().await?.results; + 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 = json["checkForm"].as_str().unwrap().to_string(); - self.license_token = json["USER"]["OPTIONS"]["license_token"].as_str().unwrap().to_string(); + 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 { + 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!({ diff --git a/src/main.rs b/src/main.rs index cca9706..d5bfab4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,10 +46,10 @@ async fn get_url(req: Json, state_data: &State> } let mut client: APIClient; - let old_sid: String; + let old_license: String; { let state_data_read = state_data.read().unwrap(); - old_sid = state_data_read.client.sid.clone(); + old_license = state_data_read.client.license_token.clone(); client = state_data_read.client.clone(); } @@ -73,7 +73,7 @@ async fn get_url(req: Json, state_data: &State> Err(_) => return (Status::InternalServerError, "Error while getting response from media.deezer.com".to_string()) }; - if client.sid != old_sid { + if client.license_token != old_license { let mut state_data_write = state_data.write().unwrap(); state_data_write.client = client; }