Spaces:
Paused
Paused
Upload 2 files
Browse files- src/main.rs +241 -148
src/main.rs
CHANGED
|
@@ -1023,12 +1023,163 @@ async fn mobile_login(email: &str, password_md5: &str) -> Result<String, String>
|
|
| 1023 |
const DEEZER_APP_SECRET: &str = "a83bf7f38ad2f137e444727cfc3775cf";
|
| 1024 |
|
| 1025 |
struct WebLoginResult {
|
| 1026 |
-
arl: String,
|
| 1027 |
token: String,
|
| 1028 |
uid: String,
|
| 1029 |
access_token: String,
|
| 1030 |
}
|
| 1031 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1032 |
async fn oauth_access_token(
|
| 1033 |
client: &reqwest::Client,
|
| 1034 |
email: &str,
|
|
@@ -1081,7 +1232,17 @@ async fn oauth_access_token(
|
|
| 1081 |
async fn establish_oauth_session(client: &reqwest::Client, jar: &Jar, access_token: &str) {
|
| 1082 |
let url_deezer = "https://www.deezer.com".parse::<Url>().unwrap();
|
| 1083 |
let url_api = "https://api.deezer.com".parse::<Url>().unwrap();
|
| 1084 |
-
let ua =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1085 |
|
| 1086 |
let _ = client
|
| 1087 |
.get("https://api.deezer.com/platform/generic/track/80085")
|
|
@@ -1116,7 +1277,19 @@ async fn web_login(email: &str, password_md5: &str) -> Result<WebLoginResult, St
|
|
| 1116 |
.build()
|
| 1117 |
.map_err(|_| "web:client".to_string())?;
|
| 1118 |
|
| 1119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1120 |
let access_token = oauth_access_token(&client, email, password_md5)
|
| 1121 |
.await
|
| 1122 |
.map_err(|e| format!("oauth:{e}"))?;
|
|
@@ -1127,89 +1300,58 @@ async fn web_login(email: &str, password_md5: &str) -> Result<WebLoginResult, St
|
|
| 1127 |
.get("https://www.deezer.com/")
|
| 1128 |
.header(ACCEPT, "*/*")
|
| 1129 |
.header("Accept-Language", "en-US,en;q=0.9")
|
| 1130 |
-
.header(
|
| 1131 |
-
"User-Agent",
|
| 1132 |
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
| 1133 |
-
)
|
| 1134 |
.send()
|
| 1135 |
.await;
|
| 1136 |
|
| 1137 |
-
let
|
| 1138 |
&client,
|
| 1139 |
-
"
|
| 1140 |
-
|
| 1141 |
-
|
| 1142 |
-
&json!({}),
|
| 1143 |
-
None,
|
| 1144 |
-
false,
|
| 1145 |
)
|
| 1146 |
.await
|
| 1147 |
-
.
|
| 1148 |
-
|
| 1149 |
-
|
| 1150 |
-
|
| 1151 |
-
return Err("web:no_checkForm".to_string());
|
| 1152 |
-
}
|
| 1153 |
|
| 1154 |
-
let uid =
|
| 1155 |
-
.get("USER")
|
| 1156 |
-
.and_then(|u| u.get("USER_ID"))
|
| 1157 |
-
.and_then(|x| x.as_i64())
|
| 1158 |
-
.map(|n| n.to_string())
|
| 1159 |
-
.unwrap_or_default();
|
| 1160 |
-
|
| 1161 |
-
let arl_v = match gw_light_call_custom(
|
| 1162 |
&client,
|
| 1163 |
-
|
| 1164 |
-
|
| 1165 |
-
|
| 1166 |
-
&json!({}),
|
| 1167 |
-
None,
|
| 1168 |
-
true,
|
| 1169 |
)
|
| 1170 |
.await
|
| 1171 |
{
|
| 1172 |
Ok(v) => v,
|
| 1173 |
-
Err(
|
| 1174 |
-
//
|
| 1175 |
-
let
|
| 1176 |
-
|
| 1177 |
-
|
| 1178 |
-
|
| 1179 |
-
|
| 1180 |
-
|
| 1181 |
-
|
| 1182 |
-
|
| 1183 |
-
|
| 1184 |
-
|
| 1185 |
-
|
| 1186 |
-
|
| 1187 |
-
|
| 1188 |
-
|
| 1189 |
-
|
| 1190 |
-
gw_light_call_custom(
|
| 1191 |
-
&client,
|
| 1192 |
-
"user.getArl",
|
| 1193 |
-
&check_form,
|
| 1194 |
-
None,
|
| 1195 |
-
&json!({}),
|
| 1196 |
-
None,
|
| 1197 |
-
true,
|
| 1198 |
-
)
|
| 1199 |
-
.await
|
| 1200 |
-
.map_err(|e2| format!("web:getArl:{e1}; retry:{e2}"))?
|
| 1201 |
}
|
| 1202 |
};
|
| 1203 |
|
| 1204 |
-
let arl = arl_v.as_str().unwrap_or("").to_string();
|
| 1205 |
-
if arl.len() < 100 {
|
| 1206 |
-
return Err(format!("web:arl_too_short:{}", arl.len()));
|
| 1207 |
-
}
|
| 1208 |
-
|
| 1209 |
Ok(WebLoginResult {
|
| 1210 |
-
arl,
|
| 1211 |
-
token
|
| 1212 |
-
uid
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1213 |
access_token,
|
| 1214 |
})
|
| 1215 |
}
|
|
@@ -1868,18 +2010,27 @@ async fn pair_complete(State(state): State<AppState>, Form(f): Form<PairComplete
|
|
| 1868 |
Ok(login) => {
|
| 1869 |
let mut map = state.pair.write().await;
|
| 1870 |
if let Some(s) = map.get_mut(&code) {
|
| 1871 |
-
s.arl =
|
| 1872 |
-
s.token =
|
| 1873 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1874 |
if !login.uid.is_empty() {
|
| 1875 |
-
s.user_id = Some(login.uid);
|
| 1876 |
}
|
| 1877 |
s.email = Some(email);
|
| 1878 |
s.error = None;
|
| 1879 |
}
|
| 1880 |
drop(map);
|
| 1881 |
pair_store_save(&state).await;
|
| 1882 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1883 |
}
|
| 1884 |
}
|
| 1885 |
}
|
|
@@ -1984,7 +2135,6 @@ async fn oauth_arl(Json(req): Json<OauthArlReq>) -> impl IntoResponse {
|
|
| 1984 |
|
| 1985 |
let jar = Arc::new(Jar::default());
|
| 1986 |
let url_deezer = "https://www.deezer.com".parse::<Url>().unwrap();
|
| 1987 |
-
let url_api = "https://api.deezer.com".parse::<Url>().unwrap();
|
| 1988 |
jar.add_cookie_str("comeback=1; Domain=.deezer.com", &url_deezer);
|
| 1989 |
jar.add_cookie_str("dzr_uniq_id=; Domain=.deezer.com", &url_deezer);
|
| 1990 |
let client = reqwest::Client::builder()
|
|
@@ -1998,86 +2148,29 @@ async fn oauth_arl(Json(req): Json<OauthArlReq>) -> impl IntoResponse {
|
|
| 1998 |
.get("https://www.deezer.com/")
|
| 1999 |
.header(ACCEPT, "*/*")
|
| 2000 |
.header("Accept-Language", "en-US,en;q=0.9")
|
| 2001 |
-
.header(
|
| 2002 |
-
"User-Agent",
|
| 2003 |
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
| 2004 |
-
)
|
| 2005 |
.send()
|
| 2006 |
.await;
|
| 2007 |
|
| 2008 |
-
let
|
| 2009 |
-
|
| 2010 |
-
.header("Authorization", format!("Bearer {}", token))
|
| 2011 |
-
.header(ACCEPT, "*/*")
|
| 2012 |
-
.header("Accept-Language", "en-US,en;q=0.9")
|
| 2013 |
-
.header(
|
| 2014 |
-
"User-Agent",
|
| 2015 |
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
| 2016 |
-
)
|
| 2017 |
-
.send()
|
| 2018 |
-
.await;
|
| 2019 |
-
|
| 2020 |
-
if !jar_has_cookie(&jar, &url_deezer, "sid=") && !jar_has_cookie(&jar, &url_api, "sid=") {
|
| 2021 |
-
let _ = client
|
| 2022 |
-
.get("https://api.deezer.com/platform/generic/track/3135556")
|
| 2023 |
-
.header("Authorization", format!("Bearer {}", token))
|
| 2024 |
-
.header(ACCEPT, "*/*")
|
| 2025 |
-
.header("Accept-Language", "en-US,en;q=0.9")
|
| 2026 |
-
.header(
|
| 2027 |
-
"User-Agent",
|
| 2028 |
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
| 2029 |
-
)
|
| 2030 |
-
.send()
|
| 2031 |
-
.await;
|
| 2032 |
}
|
| 2033 |
|
| 2034 |
-
|
| 2035 |
-
let mut ud_err: Option<String> = None;
|
| 2036 |
-
for attempt in 0..2 {
|
| 2037 |
-
match gw_light_call(&client, &jar, "deezer.getUserData", "null", json!({})).await {
|
| 2038 |
-
Ok(v) => {
|
| 2039 |
-
ud = Some(v);
|
| 2040 |
-
break;
|
| 2041 |
-
}
|
| 2042 |
-
Err(e) => {
|
| 2043 |
-
ud_err = Some(e);
|
| 2044 |
-
if attempt == 0 {
|
| 2045 |
-
let _ = client
|
| 2046 |
-
.get("https://www.deezer.com/")
|
| 2047 |
-
.header(ACCEPT, "*/*")
|
| 2048 |
-
.header("Accept-Language", "en-US,en;q=0.9")
|
| 2049 |
-
.header(
|
| 2050 |
-
"User-Agent",
|
| 2051 |
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
| 2052 |
-
)
|
| 2053 |
-
.send()
|
| 2054 |
-
.await;
|
| 2055 |
-
}
|
| 2056 |
-
}
|
| 2057 |
-
}
|
| 2058 |
-
}
|
| 2059 |
-
let Some(ud) = ud else {
|
| 2060 |
-
return json_error(
|
| 2061 |
-
StatusCode::SERVICE_UNAVAILABLE,
|
| 2062 |
-
ud_err.unwrap_or_else(|| "gw:getUserData".to_string()),
|
| 2063 |
-
);
|
| 2064 |
-
};
|
| 2065 |
|
| 2066 |
-
let
|
| 2067 |
-
|
| 2068 |
-
|
| 2069 |
-
|
|
|
|
|
|
|
| 2070 |
|
| 2071 |
-
|
| 2072 |
-
Ok(
|
| 2073 |
-
|
| 2074 |
-
|
| 2075 |
-
|
| 2076 |
-
if arl.len() < 20 {
|
| 2077 |
-
return json_error(StatusCode::SERVICE_UNAVAILABLE, "no arl");
|
| 2078 |
}
|
| 2079 |
-
|
| 2080 |
-
(StatusCode::OK, Json(json!({ "arl": arl }))).into_response()
|
| 2081 |
}
|
| 2082 |
|
| 2083 |
#[derive(Debug, Deserialize)]
|
|
|
|
| 1023 |
const DEEZER_APP_SECRET: &str = "a83bf7f38ad2f137e444727cfc3775cf";
|
| 1024 |
|
| 1025 |
struct WebLoginResult {
|
| 1026 |
+
arl: Option<String>,
|
| 1027 |
token: String,
|
| 1028 |
uid: String,
|
| 1029 |
access_token: String,
|
| 1030 |
}
|
| 1031 |
|
| 1032 |
+
fn gw_browser_ua() -> &'static str {
|
| 1033 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
|
| 1034 |
+
}
|
| 1035 |
+
|
| 1036 |
+
fn gw_cid() -> String {
|
| 1037 |
+
SystemTime::now()
|
| 1038 |
+
.duration_since(UNIX_EPOCH)
|
| 1039 |
+
.map(|d| d.as_millis().to_string())
|
| 1040 |
+
.unwrap_or_else(|_| "0".to_string())
|
| 1041 |
+
}
|
| 1042 |
+
|
| 1043 |
+
fn capture_sid_from_response(jar: &Jar, url: &Url, headers: &reqwest::header::HeaderMap) {
|
| 1044 |
+
for cookie_hdr in headers.get_all(reqwest::header::SET_COOKIE) {
|
| 1045 |
+
if let Ok(s) = cookie_hdr.to_str() {
|
| 1046 |
+
for part in s.split(',') {
|
| 1047 |
+
let part = part.trim();
|
| 1048 |
+
let name_val = part.split(';').next().unwrap_or("").trim();
|
| 1049 |
+
if name_val.starts_with("sid=") {
|
| 1050 |
+
jar.add_cookie_str(
|
| 1051 |
+
&format!("{name_val}; Domain=.deezer.com; Path=/"),
|
| 1052 |
+
url,
|
| 1053 |
+
);
|
| 1054 |
+
}
|
| 1055 |
+
}
|
| 1056 |
+
}
|
| 1057 |
+
}
|
| 1058 |
+
}
|
| 1059 |
+
|
| 1060 |
+
/// Deezer выдаёт анонимный sid в Set-Cookie ответа на GET user.getArl — нужен до OAuth.
|
| 1061 |
+
async fn ensure_anonymous_sid(client: &reqwest::Client, jar: &Jar) -> Result<(), String> {
|
| 1062 |
+
let url_deezer = "https://www.deezer.com".parse::<Url>().map_err(|_| "sid:url".to_string())?;
|
| 1063 |
+
if jar_has_cookie(jar, &url_deezer, "sid=") {
|
| 1064 |
+
return Ok(());
|
| 1065 |
+
}
|
| 1066 |
+
|
| 1067 |
+
let cid = gw_cid();
|
| 1068 |
+
let r = client
|
| 1069 |
+
.get("https://www.deezer.com/ajax/gw-light.php")
|
| 1070 |
+
.query(&[
|
| 1071 |
+
("method", "user.getArl"),
|
| 1072 |
+
("input", "3"),
|
| 1073 |
+
("output", "3"),
|
| 1074 |
+
("api_version", "1.0"),
|
| 1075 |
+
("api_token", "null"),
|
| 1076 |
+
("cid", cid.as_str()),
|
| 1077 |
+
])
|
| 1078 |
+
.header(ACCEPT, "*/*")
|
| 1079 |
+
.header("Origin", "https://www.deezer.com")
|
| 1080 |
+
.header("Referer", "https://www.deezer.com/")
|
| 1081 |
+
.header("Sec-Fetch-Site", "same-origin")
|
| 1082 |
+
.header("Sec-Fetch-Mode", "cors")
|
| 1083 |
+
.header("Sec-Fetch-Dest", "empty")
|
| 1084 |
+
.header("User-Agent", gw_browser_ua())
|
| 1085 |
+
.send()
|
| 1086 |
+
.await
|
| 1087 |
+
.map_err(|e| format!("sid:network:{e}"))?;
|
| 1088 |
+
|
| 1089 |
+
capture_sid_from_response(jar, &url_deezer, r.headers());
|
| 1090 |
+
|
| 1091 |
+
if !jar_has_cookie(jar, &url_deezer, "sid=") {
|
| 1092 |
+
return Err("sid:not_set".to_string());
|
| 1093 |
+
}
|
| 1094 |
+
Ok(())
|
| 1095 |
+
}
|
| 1096 |
+
|
| 1097 |
+
async fn gw_fetch_arl(
|
| 1098 |
+
client: &reqwest::Client,
|
| 1099 |
+
email: &str,
|
| 1100 |
+
password_md5: &str,
|
| 1101 |
+
user_id: Option<&str>,
|
| 1102 |
+
) -> Result<(String, String, String), String> {
|
| 1103 |
+
let mut last_err = String::from("getArl:none");
|
| 1104 |
+
|
| 1105 |
+
for attempt in 0..3u8 {
|
| 1106 |
+
let ud = gw_light_call_custom(
|
| 1107 |
+
client,
|
| 1108 |
+
"deezer.getUserData",
|
| 1109 |
+
"null",
|
| 1110 |
+
None,
|
| 1111 |
+
&json!({}),
|
| 1112 |
+
None,
|
| 1113 |
+
attempt > 0,
|
| 1114 |
+
)
|
| 1115 |
+
.await
|
| 1116 |
+
.map_err(|e| format!("getUserData:{e}"))?;
|
| 1117 |
+
|
| 1118 |
+
let check_form = ud
|
| 1119 |
+
.get("checkForm")
|
| 1120 |
+
.and_then(|x| x.as_str())
|
| 1121 |
+
.unwrap_or("")
|
| 1122 |
+
.trim()
|
| 1123 |
+
.to_string();
|
| 1124 |
+
if check_form.is_empty() {
|
| 1125 |
+
last_err = "no_checkForm".to_string();
|
| 1126 |
+
continue;
|
| 1127 |
+
}
|
| 1128 |
+
|
| 1129 |
+
let uid = ud
|
| 1130 |
+
.get("USER")
|
| 1131 |
+
.and_then(|u| u.get("USER_ID"))
|
| 1132 |
+
.and_then(|x| x.as_i64())
|
| 1133 |
+
.map(|n| n.to_string())
|
| 1134 |
+
.unwrap_or_default();
|
| 1135 |
+
|
| 1136 |
+
let uid_for_hdr = user_id
|
| 1137 |
+
.filter(|s| !s.trim().is_empty())
|
| 1138 |
+
.or_else(|| if uid.is_empty() { None } else { Some(uid.as_str()) });
|
| 1139 |
+
|
| 1140 |
+
match gw_light_call_custom(
|
| 1141 |
+
client,
|
| 1142 |
+
"user.getArl",
|
| 1143 |
+
&check_form,
|
| 1144 |
+
None,
|
| 1145 |
+
&json!({}),
|
| 1146 |
+
uid_for_hdr,
|
| 1147 |
+
true,
|
| 1148 |
+
)
|
| 1149 |
+
.await
|
| 1150 |
+
{
|
| 1151 |
+
Ok(v) => {
|
| 1152 |
+
let arl = v.as_str().unwrap_or("").to_string();
|
| 1153 |
+
if arl.len() >= 100 {
|
| 1154 |
+
return Ok((arl, check_form, uid));
|
| 1155 |
+
}
|
| 1156 |
+
last_err = format!("arl_too_short:{}", arl.len());
|
| 1157 |
+
}
|
| 1158 |
+
Err(e) => {
|
| 1159 |
+
last_err = e.clone();
|
| 1160 |
+
if e.contains("NEED_USER_AUTH") && attempt == 0 {
|
| 1161 |
+
let _ = gw_light_call_custom(
|
| 1162 |
+
client,
|
| 1163 |
+
"user.checkCredentials",
|
| 1164 |
+
&check_form,
|
| 1165 |
+
None,
|
| 1166 |
+
&json!({
|
| 1167 |
+
"login": email,
|
| 1168 |
+
"password": password_md5,
|
| 1169 |
+
"checkFormLogin": check_form
|
| 1170 |
+
}),
|
| 1171 |
+
uid_for_hdr,
|
| 1172 |
+
false,
|
| 1173 |
+
)
|
| 1174 |
+
.await;
|
| 1175 |
+
}
|
| 1176 |
+
}
|
| 1177 |
+
}
|
| 1178 |
+
}
|
| 1179 |
+
|
| 1180 |
+
Err(format!("getArl:{last_err}"))
|
| 1181 |
+
}
|
| 1182 |
+
|
| 1183 |
async fn oauth_access_token(
|
| 1184 |
client: &reqwest::Client,
|
| 1185 |
email: &str,
|
|
|
|
| 1232 |
async fn establish_oauth_session(client: &reqwest::Client, jar: &Jar, access_token: &str) {
|
| 1233 |
let url_deezer = "https://www.deezer.com".parse::<Url>().unwrap();
|
| 1234 |
let url_api = "https://api.deezer.com".parse::<Url>().unwrap();
|
| 1235 |
+
let ua = gw_browser_ua();
|
| 1236 |
+
|
| 1237 |
+
let _ = client
|
| 1238 |
+
.get("https://api.deezer.com/user/me")
|
| 1239 |
+
.query(&[("access_token", access_token)])
|
| 1240 |
+
.header("Authorization", format!("Bearer {access_token}"))
|
| 1241 |
+
.header(ACCEPT, "*/*")
|
| 1242 |
+
.header("Accept-Language", "en-US,en;q=0.9")
|
| 1243 |
+
.header("User-Agent", ua)
|
| 1244 |
+
.send()
|
| 1245 |
+
.await;
|
| 1246 |
|
| 1247 |
let _ = client
|
| 1248 |
.get("https://api.deezer.com/platform/generic/track/80085")
|
|
|
|
| 1277 |
.build()
|
| 1278 |
.map_err(|_| "web:client".to_string())?;
|
| 1279 |
|
| 1280 |
+
let _ = client
|
| 1281 |
+
.get("https://www.deezer.com/")
|
| 1282 |
+
.header(ACCEPT, "*/*")
|
| 1283 |
+
.header("Accept-Language", "en-US,en;q=0.9")
|
| 1284 |
+
.header("User-Agent", gw_browser_ua())
|
| 1285 |
+
.send()
|
| 1286 |
+
.await;
|
| 1287 |
+
|
| 1288 |
+
// sid нужен ДО OAuth (как в официальном клиенте Deezer)
|
| 1289 |
+
ensure_anonymous_sid(&client, &jar)
|
| 1290 |
+
.await
|
| 1291 |
+
.map_err(|e| format!("web:{e}"))?;
|
| 1292 |
+
|
| 1293 |
let access_token = oauth_access_token(&client, email, password_md5)
|
| 1294 |
.await
|
| 1295 |
.map_err(|e| format!("oauth:{e}"))?;
|
|
|
|
| 1300 |
.get("https://www.deezer.com/")
|
| 1301 |
.header(ACCEPT, "*/*")
|
| 1302 |
.header("Accept-Language", "en-US,en;q=0.9")
|
| 1303 |
+
.header("User-Agent", gw_browser_ua())
|
|
|
|
|
|
|
|
|
|
| 1304 |
.send()
|
| 1305 |
.await;
|
| 1306 |
|
| 1307 |
+
let uid_from_api = api_deezer_get(
|
| 1308 |
&client,
|
| 1309 |
+
"/user/me",
|
| 1310 |
+
&access_token,
|
| 1311 |
+
&[],
|
|
|
|
|
|
|
|
|
|
| 1312 |
)
|
| 1313 |
.await
|
| 1314 |
+
.ok()
|
| 1315 |
+
.and_then(|v| v.get("id").and_then(|x| x.as_i64()))
|
| 1316 |
+
.filter(|id| *id > 0)
|
| 1317 |
+
.map(|id| id.to_string());
|
|
|
|
|
|
|
| 1318 |
|
| 1319 |
+
let (arl, token, uid) = match gw_fetch_arl(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1320 |
&client,
|
| 1321 |
+
email,
|
| 1322 |
+
password_md5,
|
| 1323 |
+
uid_from_api.as_deref(),
|
|
|
|
|
|
|
|
|
|
| 1324 |
)
|
| 1325 |
.await
|
| 1326 |
{
|
| 1327 |
Ok(v) => v,
|
| 1328 |
+
Err(_e) => {
|
| 1329 |
+
// ARL не обязателен — OAuth уже даёт доступ к библиотеке
|
| 1330 |
+
if let Ok(mobile_arl) = mobile_login(email, password_md5).await {
|
| 1331 |
+
(
|
| 1332 |
+
mobile_arl,
|
| 1333 |
+
String::new(),
|
| 1334 |
+
uid_from_api.clone().unwrap_or_default(),
|
| 1335 |
+
)
|
| 1336 |
+
} else {
|
| 1337 |
+
return Ok(WebLoginResult {
|
| 1338 |
+
arl: None,
|
| 1339 |
+
token: String::new(),
|
| 1340 |
+
uid: uid_from_api.unwrap_or_default(),
|
| 1341 |
+
access_token,
|
| 1342 |
+
});
|
| 1343 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1344 |
}
|
| 1345 |
};
|
| 1346 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1347 |
Ok(WebLoginResult {
|
| 1348 |
+
arl: Some(arl),
|
| 1349 |
+
token,
|
| 1350 |
+
uid: if uid.is_empty() {
|
| 1351 |
+
uid_from_api.unwrap_or_default()
|
| 1352 |
+
} else {
|
| 1353 |
+
uid
|
| 1354 |
+
},
|
| 1355 |
access_token,
|
| 1356 |
})
|
| 1357 |
}
|
|
|
|
| 2010 |
Ok(login) => {
|
| 2011 |
let mut map = state.pair.write().await;
|
| 2012 |
if let Some(s) = map.get_mut(&code) {
|
| 2013 |
+
s.arl = login.arl.clone();
|
| 2014 |
+
s.token = if login.token.is_empty() {
|
| 2015 |
+
None
|
| 2016 |
+
} else {
|
| 2017 |
+
Some(login.token.clone())
|
| 2018 |
+
};
|
| 2019 |
+
s.access_token = Some(login.access_token.clone());
|
| 2020 |
if !login.uid.is_empty() {
|
| 2021 |
+
s.user_id = Some(login.uid.clone());
|
| 2022 |
}
|
| 2023 |
s.email = Some(email);
|
| 2024 |
s.error = None;
|
| 2025 |
}
|
| 2026 |
drop(map);
|
| 2027 |
pair_store_save(&state).await;
|
| 2028 |
+
let msg = if login.arl.is_some() {
|
| 2029 |
+
"<h3>Готово</h3><p>Вернись на ТВ — плагин подключится автоматически.</p>"
|
| 2030 |
+
} else {
|
| 2031 |
+
"<h3>Вход выполнен</h3><p>Библиотека доступна. ARL не получен — полные треки могут не работать, попробуй кнопку «Войти через Deezer».</p>"
|
| 2032 |
+
};
|
| 2033 |
+
Html(msg.to_string()).into_response()
|
| 2034 |
}
|
| 2035 |
}
|
| 2036 |
}
|
|
|
|
| 2135 |
|
| 2136 |
let jar = Arc::new(Jar::default());
|
| 2137 |
let url_deezer = "https://www.deezer.com".parse::<Url>().unwrap();
|
|
|
|
| 2138 |
jar.add_cookie_str("comeback=1; Domain=.deezer.com", &url_deezer);
|
| 2139 |
jar.add_cookie_str("dzr_uniq_id=; Domain=.deezer.com", &url_deezer);
|
| 2140 |
let client = reqwest::Client::builder()
|
|
|
|
| 2148 |
.get("https://www.deezer.com/")
|
| 2149 |
.header(ACCEPT, "*/*")
|
| 2150 |
.header("Accept-Language", "en-US,en;q=0.9")
|
| 2151 |
+
.header("User-Agent", gw_browser_ua())
|
|
|
|
|
|
|
|
|
|
| 2152 |
.send()
|
| 2153 |
.await;
|
| 2154 |
|
| 2155 |
+
if let Err(e) = ensure_anonymous_sid(&client, &jar).await {
|
| 2156 |
+
return json_error(StatusCode::SERVICE_UNAVAILABLE, format!("oauth_arl:{e}"));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2157 |
}
|
| 2158 |
|
| 2159 |
+
establish_oauth_session(&client, &jar, &token).await;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2160 |
|
| 2161 |
+
let uid = api_deezer_get(&client, "/user/me", &token, &[])
|
| 2162 |
+
.await
|
| 2163 |
+
.ok()
|
| 2164 |
+
.and_then(|v| v.get("id").and_then(|x| x.as_i64()))
|
| 2165 |
+
.filter(|id| *id > 0)
|
| 2166 |
+
.map(|id| id.to_string());
|
| 2167 |
|
| 2168 |
+
match gw_fetch_arl(&client, "", "", uid.as_deref()).await {
|
| 2169 |
+
Ok((arl, _, _)) if arl.len() >= 20 => {
|
| 2170 |
+
(StatusCode::OK, Json(json!({ "arl": arl }))).into_response()
|
| 2171 |
+
}
|
| 2172 |
+
Err(e) => json_error(StatusCode::SERVICE_UNAVAILABLE, e),
|
|
|
|
|
|
|
| 2173 |
}
|
|
|
|
|
|
|
| 2174 |
}
|
| 2175 |
|
| 2176 |
#[derive(Debug, Deserialize)]
|