Spaces:
Paused
Paused
Upload 2 files
Browse files- src/main.rs +152 -45
src/main.rs
CHANGED
|
@@ -1020,7 +1020,91 @@ async fn mobile_login(email: &str, password_md5: &str) -> Result<String, String>
|
|
| 1020 |
Ok(arl)
|
| 1021 |
}
|
| 1022 |
|
| 1023 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1024 |
let jar = Arc::new(Jar::default());
|
| 1025 |
let url_deezer = "https://www.deezer.com".parse::<Url>().map_err(|_| "web:url".to_string())?;
|
| 1026 |
jar.add_cookie_str("comeback=1; Domain=.deezer.com; Path=/", &url_deezer);
|
|
@@ -1032,6 +1116,13 @@ async fn web_login(email: &str, password_md5: &str) -> Result<String, String> {
|
|
| 1032 |
.build()
|
| 1033 |
.map_err(|_| "web:client".to_string())?;
|
| 1034 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1035 |
let _ = client
|
| 1036 |
.get("https://www.deezer.com/")
|
| 1037 |
.header(ACCEPT, "*/*")
|
|
@@ -1060,56 +1151,67 @@ async fn web_login(email: &str, password_md5: &str) -> Result<String, String> {
|
|
| 1060 |
return Err("web:no_checkForm".to_string());
|
| 1061 |
}
|
| 1062 |
|
| 1063 |
-
let
|
| 1064 |
-
|
| 1065 |
-
|
| 1066 |
-
|
| 1067 |
-
|
| 1068 |
-
|
| 1069 |
-
"login": email,
|
| 1070 |
-
"password": password_md5,
|
| 1071 |
-
"checkFormLogin": check_form
|
| 1072 |
-
}),
|
| 1073 |
-
None,
|
| 1074 |
-
false,
|
| 1075 |
-
)
|
| 1076 |
-
.await
|
| 1077 |
-
.map_err(|e| format!("web:checkCredentials:{e}"))?;
|
| 1078 |
|
| 1079 |
-
let
|
| 1080 |
-
&client,
|
| 1081 |
-
"deezer.getUserData",
|
| 1082 |
-
"null",
|
| 1083 |
-
None,
|
| 1084 |
-
&json!({}),
|
| 1085 |
-
None,
|
| 1086 |
-
false,
|
| 1087 |
-
)
|
| 1088 |
-
.await
|
| 1089 |
-
.map_err(|e| format!("web:getUserData2:{e}"))?;
|
| 1090 |
-
|
| 1091 |
-
let check_form2 = ud2.get("checkForm").and_then(|x| x.as_str()).unwrap_or("").trim().to_string();
|
| 1092 |
-
if check_form2.is_empty() {
|
| 1093 |
-
return Err("web:no_checkForm2".to_string());
|
| 1094 |
-
}
|
| 1095 |
-
|
| 1096 |
-
let arl_v = gw_light_call_custom(
|
| 1097 |
&client,
|
| 1098 |
"user.getArl",
|
| 1099 |
-
&
|
| 1100 |
None,
|
| 1101 |
&json!({}),
|
| 1102 |
None,
|
| 1103 |
true,
|
| 1104 |
)
|
| 1105 |
.await
|
| 1106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1107 |
|
| 1108 |
let arl = arl_v.as_str().unwrap_or("").to_string();
|
| 1109 |
if arl.len() < 100 {
|
| 1110 |
return Err(format!("web:arl_too_short:{}", arl.len()));
|
| 1111 |
}
|
| 1112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1113 |
}
|
| 1114 |
|
| 1115 |
fn worker_base() -> Option<String> {
|
|
@@ -1378,15 +1480,15 @@ async fn login(Json(req): Json<LoginReq>) -> impl IntoResponse {
|
|
| 1378 |
}
|
| 1379 |
|
| 1380 |
match web_login(&email, &pass).await {
|
| 1381 |
-
Ok(
|
| 1382 |
-
"arl": arl,
|
| 1383 |
-
"token":
|
| 1384 |
-
"uid":
|
| 1385 |
-
"access_token":
|
| 1386 |
}))).into_response(),
|
| 1387 |
Err(e) => json_error(
|
| 1388 |
StatusCode::UNAUTHORIZED,
|
| 1389 |
-
format!("login_failed;
|
| 1390 |
),
|
| 1391 |
}
|
| 1392 |
}
|
|
@@ -1763,10 +1865,15 @@ async fn pair_complete(State(state): State<AppState>, Form(f): Form<PairComplete
|
|
| 1763 |
pair_store_save(&state).await;
|
| 1764 |
Html(format!("<h3>Ошибка входа</h3><pre>{}</pre>", htmlesc(&e))).into_response()
|
| 1765 |
}
|
| 1766 |
-
Ok(
|
| 1767 |
let mut map = state.pair.write().await;
|
| 1768 |
if let Some(s) = map.get_mut(&code) {
|
| 1769 |
-
s.arl = Some(arl);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1770 |
s.email = Some(email);
|
| 1771 |
s.error = None;
|
| 1772 |
}
|
|
|
|
| 1020 |
Ok(arl)
|
| 1021 |
}
|
| 1022 |
|
| 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,
|
| 1035 |
+
password_md5: &str,
|
| 1036 |
+
) -> Result<String, String> {
|
| 1037 |
+
let app_id = deezer_app_id();
|
| 1038 |
+
let hash_src = format!("{app_id}{email}{password_md5}{DEEZER_APP_SECRET}");
|
| 1039 |
+
let hash = format!("{:x}", md5::compute(hash_src.as_bytes()));
|
| 1040 |
+
|
| 1041 |
+
let r = client
|
| 1042 |
+
.get("https://connect.deezer.com/oauth/user_auth.php")
|
| 1043 |
+
.query(&[
|
| 1044 |
+
("app_id", app_id.as_str()),
|
| 1045 |
+
("login", email),
|
| 1046 |
+
("password", password_md5),
|
| 1047 |
+
("hash", hash.as_str()),
|
| 1048 |
+
])
|
| 1049 |
+
.header(ACCEPT, "*/*")
|
| 1050 |
+
.header("Accept-Language", "en-US,en;q=0.9")
|
| 1051 |
+
.header(
|
| 1052 |
+
"User-Agent",
|
| 1053 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
| 1054 |
+
)
|
| 1055 |
+
.send()
|
| 1056 |
+
.await
|
| 1057 |
+
.map_err(|e| format!("network:{e}"))?;
|
| 1058 |
+
|
| 1059 |
+
let status = r.status();
|
| 1060 |
+
let text = r.text().await.map_err(|_| "read".to_string())?;
|
| 1061 |
+
let v: serde_json::Value = serde_json::from_str(&text).map_err(|_| {
|
| 1062 |
+
let snip: String = text.chars().take(200).collect();
|
| 1063 |
+
format!("parse:{}:{}", status.as_u16(), snip)
|
| 1064 |
+
})?;
|
| 1065 |
+
|
| 1066 |
+
if let Some(err) = v.get("error") {
|
| 1067 |
+
return Err(err.as_str().unwrap_or("error").to_string());
|
| 1068 |
+
}
|
| 1069 |
+
|
| 1070 |
+
let token = v
|
| 1071 |
+
.get("access_token")
|
| 1072 |
+
.and_then(|t| t.as_str())
|
| 1073 |
+
.unwrap_or("")
|
| 1074 |
+
.to_string();
|
| 1075 |
+
if token.is_empty() {
|
| 1076 |
+
return Err("no_token".to_string());
|
| 1077 |
+
}
|
| 1078 |
+
Ok(token)
|
| 1079 |
+
}
|
| 1080 |
+
|
| 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 = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
| 1085 |
+
|
| 1086 |
+
let _ = client
|
| 1087 |
+
.get("https://api.deezer.com/platform/generic/track/80085")
|
| 1088 |
+
.header("Authorization", format!("Bearer {access_token}"))
|
| 1089 |
+
.header(ACCEPT, "*/*")
|
| 1090 |
+
.header("Accept-Language", "en-US,en;q=0.9")
|
| 1091 |
+
.header("User-Agent", ua)
|
| 1092 |
+
.send()
|
| 1093 |
+
.await;
|
| 1094 |
+
|
| 1095 |
+
if !jar_has_cookie(jar, &url_deezer, "sid=") && !jar_has_cookie(jar, &url_api, "sid=") {
|
| 1096 |
+
let _ = client
|
| 1097 |
+
.get("https://api.deezer.com/platform/generic/track/3135556")
|
| 1098 |
+
.header("Authorization", format!("Bearer {access_token}"))
|
| 1099 |
+
.header(ACCEPT, "*/*")
|
| 1100 |
+
.header("Accept-Language", "en-US,en;q=0.9")
|
| 1101 |
+
.header("User-Agent", ua)
|
| 1102 |
+
.send()
|
| 1103 |
+
.await;
|
| 1104 |
+
}
|
| 1105 |
+
}
|
| 1106 |
+
|
| 1107 |
+
async fn web_login(email: &str, password_md5: &str) -> Result<WebLoginResult, String> {
|
| 1108 |
let jar = Arc::new(Jar::default());
|
| 1109 |
let url_deezer = "https://www.deezer.com".parse::<Url>().map_err(|_| "web:url".to_string())?;
|
| 1110 |
jar.add_cookie_str("comeback=1; Domain=.deezer.com; Path=/", &url_deezer);
|
|
|
|
| 1116 |
.build()
|
| 1117 |
.map_err(|_| "web:client".to_string())?;
|
| 1118 |
|
| 1119 |
+
// OAuth через connect.deezer.com — не блокируется так же, как gw-light checkCredentials
|
| 1120 |
+
let access_token = oauth_access_token(&client, email, password_md5)
|
| 1121 |
+
.await
|
| 1122 |
+
.map_err(|e| format!("oauth:{e}"))?;
|
| 1123 |
+
|
| 1124 |
+
establish_oauth_session(&client, &jar, &access_token).await;
|
| 1125 |
+
|
| 1126 |
let _ = client
|
| 1127 |
.get("https://www.deezer.com/")
|
| 1128 |
.header(ACCEPT, "*/*")
|
|
|
|
| 1151 |
return Err("web:no_checkForm".to_string());
|
| 1152 |
}
|
| 1153 |
|
| 1154 |
+
let uid = ud
|
| 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 |
"user.getArl",
|
| 1164 |
+
&check_form,
|
| 1165 |
None,
|
| 1166 |
&json!({}),
|
| 1167 |
None,
|
| 1168 |
true,
|
| 1169 |
)
|
| 1170 |
.await
|
| 1171 |
+
{
|
| 1172 |
+
Ok(v) => v,
|
| 1173 |
+
Err(e1) => {
|
| 1174 |
+
// Последний fallback — checkCredentials (часто режется на HF/VPS)
|
| 1175 |
+
let _ = gw_light_call_custom(
|
| 1176 |
+
&client,
|
| 1177 |
+
"user.checkCredentials",
|
| 1178 |
+
&check_form,
|
| 1179 |
+
None,
|
| 1180 |
+
&json!({
|
| 1181 |
+
"login": email,
|
| 1182 |
+
"password": password_md5,
|
| 1183 |
+
"checkFormLogin": check_form
|
| 1184 |
+
}),
|
| 1185 |
+
None,
|
| 1186 |
+
false,
|
| 1187 |
+
)
|
| 1188 |
+
.await;
|
| 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: check_form,
|
| 1212 |
+
uid,
|
| 1213 |
+
access_token,
|
| 1214 |
+
})
|
| 1215 |
}
|
| 1216 |
|
| 1217 |
fn worker_base() -> Option<String> {
|
|
|
|
| 1480 |
}
|
| 1481 |
|
| 1482 |
match web_login(&email, &pass).await {
|
| 1483 |
+
Ok(res) => (StatusCode::OK, Json(json!({
|
| 1484 |
+
"arl": res.arl,
|
| 1485 |
+
"token": res.token,
|
| 1486 |
+
"uid": res.uid,
|
| 1487 |
+
"access_token": res.access_token
|
| 1488 |
}))).into_response(),
|
| 1489 |
Err(e) => json_error(
|
| 1490 |
StatusCode::UNAUTHORIZED,
|
| 1491 |
+
format!("login_failed; {e}"),
|
| 1492 |
),
|
| 1493 |
}
|
| 1494 |
}
|
|
|
|
| 1865 |
pair_store_save(&state).await;
|
| 1866 |
Html(format!("<h3>Ошибка входа</h3><pre>{}</pre>", htmlesc(&e))).into_response()
|
| 1867 |
}
|
| 1868 |
+
Ok(login) => {
|
| 1869 |
let mut map = state.pair.write().await;
|
| 1870 |
if let Some(s) = map.get_mut(&code) {
|
| 1871 |
+
s.arl = Some(login.arl);
|
| 1872 |
+
s.token = Some(login.token);
|
| 1873 |
+
s.access_token = Some(login.access_token);
|
| 1874 |
+
if !login.uid.is_empty() {
|
| 1875 |
+
s.user_id = Some(login.uid);
|
| 1876 |
+
}
|
| 1877 |
s.email = Some(email);
|
| 1878 |
s.error = None;
|
| 1879 |
}
|