recycleactor commited on
Commit
cb28adf
·
verified ·
1 Parent(s): 8734258

Upload main.rs

Browse files
Files changed (1) hide show
  1. src/main.rs +161 -18
src/main.rs CHANGED
@@ -8,7 +8,7 @@
8
  };
9
  use tower_http::{cors::{CorsLayer, Any}, compression::CompressionLayer, trace::TraceLayer};
10
  use serde_json::json;
11
- use serde::Deserialize;
12
  use std::collections::HashMap;
13
  use std::sync::Arc;
14
  use bytes::Bytes;
@@ -35,6 +35,7 @@ struct AppState {
35
  api: Arc<Mutex<APIClient>>,
36
  api_by_arl: Arc<RwLock<HashMap<String, Arc<Mutex<APIClient>>>>>,
37
  pair: Arc<RwLock<HashMap<String, PairSession>>>,
 
38
  }
39
 
40
  fn json_error(status: StatusCode, message: impl Into<String>) -> axum::response::Response {
@@ -1019,6 +1020,98 @@ async fn mobile_login(email: &str, password_md5: &str) -> Result<String, String>
1019
  Ok(arl)
1020
  }
1021
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1022
 
1023
  async fn gw_light_call(
1024
  client: &reqwest::Client,
@@ -1223,21 +1316,26 @@ async fn login(Json(req): Json<LoginReq>) -> impl IntoResponse {
1223
  return json_error(StatusCode::BAD_REQUEST, "Email and password_md5 required");
1224
  }
1225
 
1226
- // Используем мобильный API — не блокируется Cloudflare в датацентрах
1227
  match mobile_login(&email, &pass).await {
1228
- Ok(arl) => {
1229
- (StatusCode::OK, Json(json!({
 
 
 
 
 
 
1230
  "arl": arl,
1231
  "token": null,
1232
  "uid": "",
1233
  "access_token": null
1234
- }))).into_response()
 
1235
  }
1236
- Err(e) => json_error(StatusCode::UNAUTHORIZED, format!("login_failed; {e}")),
1237
  }
1238
  }
1239
 
1240
- #[derive(Clone)]
1241
  struct PairSession {
1242
  created_ms: u128,
1243
  access_token: Option<String>,
@@ -1248,6 +1346,34 @@ struct PairSession {
1248
  error: Option<String>,
1249
  }
1250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1251
  fn gen_pair_code() -> String {
1252
  let ms = now_ms();
1253
  let hex = format!("{:x}", md5::compute(format!("pair:{ms}:{SECRET}").as_bytes()));
@@ -1315,6 +1441,7 @@ async fn pair_start(State(state): State<AppState>) -> impl IntoResponse {
1315
  },
1316
  );
1317
  }
 
1318
  (StatusCode::OK, Json(json!({ "code": code }))).into_response()
1319
  }
1320
 
@@ -1332,6 +1459,8 @@ async fn pair_status(State(state): State<AppState>, Query(q): Query<PairStatusQu
1332
  };
1333
  if now.saturating_sub(sess.created_ms) > ttl_ms {
1334
  map.remove(&code);
 
 
1335
  return json_error(StatusCode::NOT_FOUND, "code expired");
1336
  }
1337
 
@@ -1399,6 +1528,8 @@ async fn pair_oauth(State(state): State<AppState>, Json(req): Json<PairOauthReq>
1399
  sess.user_id = Some(user_id);
1400
  }
1401
  sess.error = None;
 
 
1402
  (StatusCode::OK, Json(json!({ "ok": true }))).into_response()
1403
  }
1404
 
@@ -1564,26 +1695,34 @@ async fn pair_complete(State(state): State<AppState>, Form(f): Form<PairComplete
1564
 
1565
  let pass_md5 = format!("{:x}", md5::compute(password.as_bytes()));
1566
 
1567
- // Мобильный API не блокируется Cloudflare
1568
- match mobile_login(&email, &pass_md5).await {
 
 
 
 
 
 
 
1569
  Err(e) => {
1570
  let mut map = state.pair.write().await;
1571
  if let Some(s) = map.get_mut(&code) {
1572
  s.error = Some(e.clone());
1573
  }
 
 
1574
  Html(format!("<h3>Ошибка входа</h3><pre>{}</pre>", htmlesc(&e))).into_response()
1575
  }
1576
  Ok(arl) => {
1577
- {
1578
- let mut map = state.pair.write().await;
1579
- if let Some(s) = map.get_mut(&code) {
1580
- s.arl = Some(arl);
1581
- s.email = Some(email);
1582
- s.error = None;
1583
- }
1584
  }
1585
- Html("<h3>Готово</h3><p>Вернись на ТВ — плагин подключится автоматически.</p>".to_string())
1586
- .into_response()
 
1587
  }
1588
  }
1589
  }
@@ -1815,6 +1954,7 @@ async fn gw_page(Json(req): Json<GwPageReq>) -> impl IntoResponse {
1815
  jar.add_cookie_str(&format!("arl={arl}; Domain=.deezer.com; Path=/"), &url_deezer);
1816
 
1817
  let client = reqwest::Client::builder()
 
1818
  .cookie_provider(jar.clone())
1819
  .timeout(std::time::Duration::from_secs(20))
1820
  .build()
@@ -1944,6 +2084,7 @@ async fn gw_flow(Json(req): Json<GwFlowReq>) -> impl IntoResponse {
1944
  jar.add_cookie_str(&format!("arl={arl}; Domain=.deezer.com; Path=/"), &url_deezer);
1945
 
1946
  let client = reqwest::Client::builder()
 
1947
  .cookie_provider(jar.clone())
1948
  .timeout(std::time::Duration::from_secs(20))
1949
  .build()
@@ -2208,7 +2349,9 @@ async fn main() {
2208
  api: Arc::new(Mutex::new(APIClient::new())),
2209
  api_by_arl: Arc::new(RwLock::new(HashMap::new())),
2210
  pair: Arc::new(RwLock::new(HashMap::new())),
 
2211
  };
 
2212
 
2213
  let cors = CorsLayer::new()
2214
  .allow_origin(Any)
 
8
  };
9
  use tower_http::{cors::{CorsLayer, Any}, compression::CompressionLayer, trace::TraceLayer};
10
  use serde_json::json;
11
+ use serde::{Deserialize, Serialize};
12
  use std::collections::HashMap;
13
  use std::sync::Arc;
14
  use bytes::Bytes;
 
35
  api: Arc<Mutex<APIClient>>,
36
  api_by_arl: Arc<RwLock<HashMap<String, Arc<Mutex<APIClient>>>>>,
37
  pair: Arc<RwLock<HashMap<String, PairSession>>>,
38
+ pair_store_path: String,
39
  }
40
 
41
  fn json_error(status: StatusCode, message: impl Into<String>) -> axum::response::Response {
 
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);
1027
+
1028
+ let client = reqwest::Client::builder()
1029
+ .no_proxy()
1030
+ .cookie_provider(jar.clone())
1031
+ .timeout(std::time::Duration::from_secs(25))
1032
+ .build()
1033
+ .map_err(|_| "web:client".to_string())?;
1034
+
1035
+ let _ = client
1036
+ .get("https://www.deezer.com/")
1037
+ .header(ACCEPT, "*/*")
1038
+ .header("Accept-Language", "en-US,en;q=0.9")
1039
+ .header(
1040
+ "User-Agent",
1041
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
1042
+ )
1043
+ .send()
1044
+ .await;
1045
+
1046
+ let ud = gw_light_call_custom(
1047
+ &client,
1048
+ "deezer.getUserData",
1049
+ "null",
1050
+ None,
1051
+ &json!({}),
1052
+ None,
1053
+ false,
1054
+ )
1055
+ .await
1056
+ .map_err(|e| format!("web:getUserData:{e}"))?;
1057
+
1058
+ let check_form = ud.get("checkForm").and_then(|x| x.as_str()).unwrap_or("").trim().to_string();
1059
+ if check_form.is_empty() {
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
 
1116
  async fn gw_light_call(
1117
  client: &reqwest::Client,
 
1316
  return json_error(StatusCode::BAD_REQUEST, "Email and password_md5 required");
1317
  }
1318
 
 
1319
  match mobile_login(&email, &pass).await {
1320
+ Ok(arl) => (StatusCode::OK, Json(json!({
1321
+ "arl": arl,
1322
+ "token": null,
1323
+ "uid": "",
1324
+ "access_token": null
1325
+ }))).into_response(),
1326
+ Err(e1) => match web_login(&email, &pass).await {
1327
+ Ok(arl) => (StatusCode::OK, Json(json!({
1328
  "arl": arl,
1329
  "token": null,
1330
  "uid": "",
1331
  "access_token": null
1332
+ }))).into_response(),
1333
+ Err(e2) => json_error(StatusCode::UNAUTHORIZED, format!("login_failed; mobile:{e1}; web:{e2}")),
1334
  }
 
1335
  }
1336
  }
1337
 
1338
+ #[derive(Clone, Serialize, Deserialize)]
1339
  struct PairSession {
1340
  created_ms: u128,
1341
  access_token: Option<String>,
 
1346
  error: Option<String>,
1347
  }
1348
 
1349
+ async fn pair_store_load(state: &AppState) {
1350
+ let path = state.pair_store_path.trim();
1351
+ if path.is_empty() {
1352
+ return;
1353
+ }
1354
+ if let Ok(txt) = tokio::fs::read_to_string(path).await {
1355
+ if let Ok(map) = serde_json::from_str::<HashMap<String, PairSession>>(&txt) {
1356
+ *state.pair.write().await = map;
1357
+ }
1358
+ }
1359
+ }
1360
+
1361
+ async fn pair_store_save(state: &AppState) {
1362
+ let path = state.pair_store_path.trim();
1363
+ if path.is_empty() {
1364
+ return;
1365
+ }
1366
+ let map = state.pair.read().await.clone();
1367
+ let Ok(txt) = serde_json::to_string(&map) else { return };
1368
+ let tmp = format!("{path}.tmp");
1369
+ if tokio::fs::write(&tmp, txt).await.is_ok() {
1370
+ if tokio::fs::rename(&tmp, path).await.is_err() {
1371
+ let _ = tokio::fs::remove_file(path).await;
1372
+ let _ = tokio::fs::rename(&tmp, path).await;
1373
+ }
1374
+ }
1375
+ }
1376
+
1377
  fn gen_pair_code() -> String {
1378
  let ms = now_ms();
1379
  let hex = format!("{:x}", md5::compute(format!("pair:{ms}:{SECRET}").as_bytes()));
 
1441
  },
1442
  );
1443
  }
1444
+ pair_store_save(&state).await;
1445
  (StatusCode::OK, Json(json!({ "code": code }))).into_response()
1446
  }
1447
 
 
1459
  };
1460
  if now.saturating_sub(sess.created_ms) > ttl_ms {
1461
  map.remove(&code);
1462
+ drop(map);
1463
+ pair_store_save(&state).await;
1464
  return json_error(StatusCode::NOT_FOUND, "code expired");
1465
  }
1466
 
 
1528
  sess.user_id = Some(user_id);
1529
  }
1530
  sess.error = None;
1531
+ drop(map);
1532
+ pair_store_save(&state).await;
1533
  (StatusCode::OK, Json(json!({ "ok": true }))).into_response()
1534
  }
1535
 
 
1695
 
1696
  let pass_md5 = format!("{:x}", md5::compute(password.as_bytes()));
1697
 
1698
+ let res = match mobile_login(&email, &pass_md5).await {
1699
+ Ok(arl) => Ok(arl),
1700
+ Err(e1) => match web_login(&email, &pass_md5).await {
1701
+ Ok(arl) => Ok(arl),
1702
+ Err(e2) => Err(format!("mobile:{e1}; web:{e2}")),
1703
+ },
1704
+ };
1705
+
1706
+ match res {
1707
  Err(e) => {
1708
  let mut map = state.pair.write().await;
1709
  if let Some(s) = map.get_mut(&code) {
1710
  s.error = Some(e.clone());
1711
  }
1712
+ drop(map);
1713
+ pair_store_save(&state).await;
1714
  Html(format!("<h3>Ошибка входа</h3><pre>{}</pre>", htmlesc(&e))).into_response()
1715
  }
1716
  Ok(arl) => {
1717
+ let mut map = state.pair.write().await;
1718
+ if let Some(s) = map.get_mut(&code) {
1719
+ s.arl = Some(arl);
1720
+ s.email = Some(email);
1721
+ s.error = None;
 
 
1722
  }
1723
+ drop(map);
1724
+ pair_store_save(&state).await;
1725
+ Html("<h3>Готово</h3><p>Вернись на ТВ — плагин подключится автоматически.</p>".to_string()).into_response()
1726
  }
1727
  }
1728
  }
 
1954
  jar.add_cookie_str(&format!("arl={arl}; Domain=.deezer.com; Path=/"), &url_deezer);
1955
 
1956
  let client = reqwest::Client::builder()
1957
+ .no_proxy()
1958
  .cookie_provider(jar.clone())
1959
  .timeout(std::time::Duration::from_secs(20))
1960
  .build()
 
2084
  jar.add_cookie_str(&format!("arl={arl}; Domain=.deezer.com; Path=/"), &url_deezer);
2085
 
2086
  let client = reqwest::Client::builder()
2087
+ .no_proxy()
2088
  .cookie_provider(jar.clone())
2089
  .timeout(std::time::Duration::from_secs(20))
2090
  .build()
 
2349
  api: Arc::new(Mutex::new(APIClient::new())),
2350
  api_by_arl: Arc::new(RwLock::new(HashMap::new())),
2351
  pair: Arc::new(RwLock::new(HashMap::new())),
2352
+ pair_store_path: std::env::var("PAIR_STORE_PATH").ok().unwrap_or_else(|| "pair_store.json".to_string()),
2353
  };
2354
+ pair_store_load(&state).await;
2355
 
2356
  let cors = CorsLayer::new()
2357
  .allow_origin(Any)