recycleactor commited on
Commit
5dab5eb
·
verified ·
1 Parent(s): 71f13fa

Upload 2 files

Browse files
Files changed (1) hide show
  1. 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
- async fn web_login(email: &str, password_md5: &str) -> Result<String, String> {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 _ = gw_light_call_custom(
1064
- &client,
1065
- "user.checkCredentials",
1066
- &check_form,
1067
- None,
1068
- &json!({
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 ud2 = gw_light_call_custom(
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
- &check_form2,
1100
  None,
1101
  &json!({}),
1102
  None,
1103
  true,
1104
  )
1105
  .await
1106
- .map_err(|e| format!("web:getArl:{e}"))?;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- Ok(arl)
 
 
 
 
 
 
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(arl) => (StatusCode::OK, Json(json!({
1382
- "arl": arl,
1383
- "token": null,
1384
- "uid": "",
1385
- "access_token": null
1386
  }))).into_response(),
1387
  Err(e) => json_error(
1388
  StatusCode::UNAUTHORIZED,
1389
- format!("login_failed; web:{e}"),
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(arl) => {
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
  }