recycleactor commited on
Commit
f618c33
·
verified ·
1 Parent(s): 3c7fc10

Upload main.rs

Browse files
Files changed (1) hide show
  1. src/main.rs +303 -0
src/main.rs CHANGED
@@ -1064,6 +1064,87 @@ async fn gw_light_call(
1064
  }
1065
  }
1066
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1067
  fn jar_has_cookie(jar: &Jar, url: &Url, cookie_prefix: &str) -> bool {
1068
  let Some(hv) = jar.cookies(url) else { return false };
1069
  let Ok(s) = hv.to_str() else { return false };
@@ -1799,6 +1880,226 @@ async fn oauth_arl(Json(req): Json<OauthArlReq>) -> impl IntoResponse {
1799
  (StatusCode::OK, Json(json!({ "arl": arl }))).into_response()
1800
  }
1801
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1802
  #[derive(Debug, Deserialize)]
1803
  struct OauthFlowReq {
1804
  access_token: String,
@@ -2023,6 +2324,8 @@ async fn main() {
2023
  .route("/pair/oauth", post(pair_oauth))
2024
  .route("/pair/oauth_cb", get(pair_oauth_cb))
2025
  .route("/pair/complete", post(pair_complete))
 
 
2026
  .route("/oauth/me", post(oauth_me))
2027
  .route("/oauth/playlists", post(oauth_playlists))
2028
  .route("/oauth/playlist_tracks", post(oauth_playlist_tracks))
 
1064
  }
1065
  }
1066
 
1067
+ async fn gw_light_call_custom(
1068
+ client: &reqwest::Client,
1069
+ method: &str,
1070
+ api_token: &str,
1071
+ gateway_input: Option<&str>,
1072
+ params: &serde_json::Value,
1073
+ user_id: Option<&str>,
1074
+ use_get: bool,
1075
+ ) -> Result<serde_json::Value, String> {
1076
+ fn cid() -> String {
1077
+ SystemTime::now()
1078
+ .duration_since(UNIX_EPOCH)
1079
+ .map(|d| d.as_millis().to_string())
1080
+ .unwrap_or_else(|_| "0".to_string())
1081
+ }
1082
+
1083
+ let cid = cid();
1084
+ let mut req = if use_get || method == "user.getArl" {
1085
+ client.get("https://www.deezer.com/ajax/gw-light.php")
1086
+ } else {
1087
+ client
1088
+ .post("https://www.deezer.com/ajax/gw-light.php")
1089
+ .header("Content-Type", "text/plain;charset=UTF-8")
1090
+ .body(params.to_string())
1091
+ };
1092
+
1093
+ if let Some(uid) = user_id.filter(|s| !s.trim().is_empty()) {
1094
+ req = req.header("x-deezer-user", uid);
1095
+ }
1096
+
1097
+ let mut qp: Vec<(&str, &str)> = vec![
1098
+ ("method", method),
1099
+ ("input", "3"),
1100
+ ("api_version", "1.0"),
1101
+ ("api_token", api_token),
1102
+ ("cid", cid.as_str()),
1103
+ ];
1104
+ if let Some(gi) = gateway_input.filter(|s| !s.trim().is_empty()) {
1105
+ qp.push(("gateway_input", gi));
1106
+ }
1107
+
1108
+ let r = req
1109
+ .query(&qp)
1110
+ .header(ACCEPT, "*/*")
1111
+ .header("Cache-Control", "max-age=0")
1112
+ .header("Connection", "keep-alive")
1113
+ .header("Origin", "https://www.deezer.com")
1114
+ .header("Referer", "https://www.deezer.com/")
1115
+ .header("Sec-Fetch-Site", "same-origin")
1116
+ .header("Sec-Fetch-Mode", "cors")
1117
+ .header("Sec-Fetch-Dest", "empty")
1118
+ .header("X-Requested-With", "XMLHttpRequest")
1119
+ .header("Accept-Language", "en-US,en;q=0.9")
1120
+ .header("Content-Language", "en-US")
1121
+ .header("X-User-IP", "1.1.1.1")
1122
+ .header("x-deezer-client-ip", "1.1.1.1")
1123
+ .header(
1124
+ "User-Agent",
1125
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
1126
+ )
1127
+ .send()
1128
+ .await
1129
+ .map_err(|_| "network".to_string())?;
1130
+
1131
+ let status = r.status();
1132
+ let text = r.text().await.map_err(|_| "read".to_string())?;
1133
+ let json: GwResp = serde_json::from_str(&text).map_err(|_| {
1134
+ let snip: String = text.chars().take(200).collect();
1135
+ format!("parse:{}:{}", status.as_u16(), snip)
1136
+ })?;
1137
+
1138
+ if let Some(error) = json.error.as_object() {
1139
+ for (code, message) in error {
1140
+ let msg = message.as_str().unwrap_or("");
1141
+ return Err(format!("{}:{}", code, msg));
1142
+ }
1143
+ }
1144
+
1145
+ Ok(json.results)
1146
+ }
1147
+
1148
  fn jar_has_cookie(jar: &Jar, url: &Url, cookie_prefix: &str) -> bool {
1149
  let Some(hv) = jar.cookies(url) else { return false };
1150
  let Ok(s) = hv.to_str() else { return false };
 
1880
  (StatusCode::OK, Json(json!({ "arl": arl }))).into_response()
1881
  }
1882
 
1883
+ #[derive(Debug, Deserialize)]
1884
+ struct GwPageReq {
1885
+ arl: String,
1886
+ token: Option<String>,
1887
+ user_id: Option<String>,
1888
+ page: Option<String>,
1889
+ lang: Option<String>,
1890
+ }
1891
+
1892
+ async fn gw_page(Json(req): Json<GwPageReq>) -> impl IntoResponse {
1893
+ let arl = req.arl.trim().to_string();
1894
+ if arl.len() < 20 {
1895
+ return json_error(StatusCode::BAD_REQUEST, "arl required");
1896
+ }
1897
+ let page = req.page.unwrap_or_else(|| "home".to_string()).trim().to_string();
1898
+ if page.is_empty() {
1899
+ return json_error(StatusCode::BAD_REQUEST, "page required");
1900
+ }
1901
+ let lang = req
1902
+ .lang
1903
+ .unwrap_or_else(|| "en".to_string())
1904
+ .trim()
1905
+ .to_string();
1906
+ let lang = if lang.is_empty() { "en".to_string() } else { lang };
1907
+
1908
+ let jar = Arc::new(Jar::default());
1909
+ let url_deezer = "https://www.deezer.com".parse::<Url>().unwrap();
1910
+ jar.add_cookie_str("comeback=1; Domain=.deezer.com", &url_deezer);
1911
+ jar.add_cookie_str(&format!("arl={arl}; Domain=.deezer.com; Path=/"), &url_deezer);
1912
+
1913
+ let client = reqwest::Client::builder()
1914
+ .cookie_provider(jar.clone())
1915
+ .timeout(std::time::Duration::from_secs(20))
1916
+ .build()
1917
+ .unwrap();
1918
+
1919
+ let ud = match gw_light_call_custom(
1920
+ &client,
1921
+ "deezer.getUserData",
1922
+ "null",
1923
+ None,
1924
+ &json!({}),
1925
+ None,
1926
+ true,
1927
+ )
1928
+ .await
1929
+ {
1930
+ Ok(v) => v,
1931
+ Err(e) => return json_error(StatusCode::UNAUTHORIZED, format!("gw:getUserData:{e}")),
1932
+ };
1933
+
1934
+ let token = req
1935
+ .token
1936
+ .unwrap_or_default()
1937
+ .trim()
1938
+ .to_string()
1939
+ .chars()
1940
+ .filter(|c| !c.is_control())
1941
+ .collect::<String>();
1942
+ let api_token = if !token.is_empty() {
1943
+ token
1944
+ } else {
1945
+ ud.get("checkForm")
1946
+ .and_then(|x| x.as_str())
1947
+ .unwrap_or("")
1948
+ .trim()
1949
+ .to_string()
1950
+ };
1951
+ if api_token.is_empty() {
1952
+ return json_error(StatusCode::SERVICE_UNAVAILABLE, "gw:no_checkForm");
1953
+ }
1954
+
1955
+ let user_id = req
1956
+ .user_id
1957
+ .unwrap_or_default()
1958
+ .trim()
1959
+ .to_string()
1960
+ .chars()
1961
+ .filter(|c| c.is_ascii_digit())
1962
+ .collect::<String>();
1963
+ let user_id = if !user_id.is_empty() {
1964
+ Some(user_id)
1965
+ } else {
1966
+ ud.get("USER")
1967
+ .and_then(|u| u.get("USER_ID"))
1968
+ .and_then(|x| x.as_i64())
1969
+ .filter(|n| *n > 0)
1970
+ .map(|n| n.to_string())
1971
+ };
1972
+
1973
+ let support = json!({
1974
+ "ads": [],
1975
+ "deeplink-list": ["deeplink"],
1976
+ "event-card": ["live-event"],
1977
+ "grid-preview-one": ["album","artist","artistLineUp","channel","livestream","flow","playlist","radio","show","smarttracklist","track","user","video-link","external-link"],
1978
+ "grid-preview-two": ["album","artist","artistLineUp","channel","livestream","flow","playlist","radio","show","smarttracklist","track","user","video-link","external-link"],
1979
+ "grid": ["album","artist","artistLineUp","channel","livestream","flow","playlist","radio","show","smarttracklist","track","user","video-link","external-link"],
1980
+ "horizontal-grid": ["album","artist","artistLineUp","channel","livestream","flow","playlist","radio","show","smarttracklist","track","user","video-link","external-link"],
1981
+ "horizontal-list": ["track","song"],
1982
+ "item-highlight": ["radio"],
1983
+ "large-card": ["album","external-link","playlist","show","video-link"],
1984
+ "list": ["episode"],
1985
+ "mini-banner": ["external-link"],
1986
+ "slideshow": ["album","artist","channel","external-link","flow","livestream","playlist","show","smarttracklist","user","video-link"],
1987
+ "small-horizontal-grid": ["flow"],
1988
+ "long-card-horizontal-grid": ["album","artist","artistLineUp","channel","livestream","flow","playlist","radio","show","smarttracklist","track","user","video-link","external-link"],
1989
+ "filterable-grid": ["flow"]
1990
+ });
1991
+
1992
+ let gateway_input = json!({
1993
+ "PAGE": page,
1994
+ "VERSION": "2.5",
1995
+ "SUPPORT": support,
1996
+ "LANG": lang,
1997
+ "OPTIONS": ["deeplink_newsandentertainment", "deeplink_subscribeoffer"]
1998
+ })
1999
+ .to_string();
2000
+
2001
+ let results = match gw_light_call_custom(
2002
+ &client,
2003
+ "page.get",
2004
+ &api_token,
2005
+ Some(&gateway_input),
2006
+ &json!({}),
2007
+ user_id.as_deref(),
2008
+ false,
2009
+ )
2010
+ .await
2011
+ {
2012
+ Ok(v) => v,
2013
+ Err(e) => return json_error(StatusCode::SERVICE_UNAVAILABLE, format!("gw:page.get:{e}")),
2014
+ };
2015
+
2016
+ (StatusCode::OK, Json(json!({ "results": results }))).into_response()
2017
+ }
2018
+
2019
+ #[derive(Debug, Deserialize)]
2020
+ struct GwFlowReq {
2021
+ arl: String,
2022
+ token: Option<String>,
2023
+ user_id: String,
2024
+ config_id: Option<String>,
2025
+ }
2026
+
2027
+ async fn gw_flow(Json(req): Json<GwFlowReq>) -> impl IntoResponse {
2028
+ let arl = req.arl.trim().to_string();
2029
+ if arl.len() < 20 {
2030
+ return json_error(StatusCode::BAD_REQUEST, "arl required");
2031
+ }
2032
+ let user_id = req.user_id.trim().to_string();
2033
+ if user_id.is_empty() {
2034
+ return json_error(StatusCode::BAD_REQUEST, "user_id required");
2035
+ }
2036
+
2037
+ let jar = Arc::new(Jar::default());
2038
+ let url_deezer = "https://www.deezer.com".parse::<Url>().unwrap();
2039
+ jar.add_cookie_str("comeback=1; Domain=.deezer.com", &url_deezer);
2040
+ jar.add_cookie_str(&format!("arl={arl}; Domain=.deezer.com; Path=/"), &url_deezer);
2041
+
2042
+ let client = reqwest::Client::builder()
2043
+ .cookie_provider(jar.clone())
2044
+ .timeout(std::time::Duration::from_secs(20))
2045
+ .build()
2046
+ .unwrap();
2047
+
2048
+ let ud = match gw_light_call_custom(
2049
+ &client,
2050
+ "deezer.getUserData",
2051
+ "null",
2052
+ None,
2053
+ &json!({}),
2054
+ None,
2055
+ true,
2056
+ )
2057
+ .await
2058
+ {
2059
+ Ok(v) => v,
2060
+ Err(e) => return json_error(StatusCode::UNAUTHORIZED, format!("gw:getUserData:{e}")),
2061
+ };
2062
+
2063
+ let token = req.token.unwrap_or_default().trim().to_string();
2064
+ let api_token = if !token.is_empty() {
2065
+ token
2066
+ } else {
2067
+ ud.get("checkForm")
2068
+ .and_then(|x| x.as_str())
2069
+ .unwrap_or("")
2070
+ .trim()
2071
+ .to_string()
2072
+ };
2073
+ if api_token.is_empty() {
2074
+ return json_error(StatusCode::SERVICE_UNAVAILABLE, "gw:no_checkForm");
2075
+ }
2076
+
2077
+ let mut params = json!({ "user_id": user_id });
2078
+ let config_id = req.config_id.unwrap_or_default().trim().to_string();
2079
+ if !config_id.is_empty() && config_id != "default" {
2080
+ if let Some(obj) = params.as_object_mut() {
2081
+ obj.insert("config_id".to_string(), serde_json::Value::String(config_id));
2082
+ }
2083
+ }
2084
+
2085
+ let results = match gw_light_call_custom(
2086
+ &client,
2087
+ "radio.getUserRadio",
2088
+ &api_token,
2089
+ None,
2090
+ &params,
2091
+ Some(req.user_id.as_str()),
2092
+ false,
2093
+ )
2094
+ .await
2095
+ {
2096
+ Ok(v) => v,
2097
+ Err(e) => return json_error(StatusCode::SERVICE_UNAVAILABLE, format!("gw:radio.getUserRadio:{e}")),
2098
+ };
2099
+
2100
+ (StatusCode::OK, Json(json!({ "results": results }))).into_response()
2101
+ }
2102
+
2103
  #[derive(Debug, Deserialize)]
2104
  struct OauthFlowReq {
2105
  access_token: String,
 
2324
  .route("/pair/oauth", post(pair_oauth))
2325
  .route("/pair/oauth_cb", get(pair_oauth_cb))
2326
  .route("/pair/complete", post(pair_complete))
2327
+ .route("/gw/page", post(gw_page))
2328
+ .route("/gw/flow", post(gw_flow))
2329
  .route("/oauth/me", post(oauth_me))
2330
  .route("/oauth/playlists", post(oauth_playlists))
2331
  .route("/oauth/playlist_tracks", post(oauth_playlist_tracks))