recycleactor commited on
Commit
b7f32f6
·
verified ·
1 Parent(s): e31f897

Upload 2 files

Browse files
Files changed (1) hide show
  1. src/main.rs +179 -73
src/main.rs CHANGED
@@ -40,6 +40,13 @@ fn json_error(status: StatusCode, message: impl Into<String>) -> axum::response:
40
  (status, Json(json!({ "error": message.into() }))).into_response()
41
  }
42
 
 
 
 
 
 
 
 
43
  async fn api_client_for_arl(state: &AppState, arl: Option<String>) -> Arc<Mutex<APIClient>> {
44
  let Some(arl) = arl.map(|s| s.trim().to_string()).filter(|s| !s.is_empty()) else {
45
  return state.api.clone();
@@ -57,6 +64,65 @@ async fn api_client_for_arl(state: &AppState, arl: Option<String>) -> Arc<Mutex<
57
  c
58
  }
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  #[derive(Deserialize)]
61
  struct DeezerTrackList {
62
  data: Vec<DeezerTrack>
@@ -203,73 +269,47 @@ async fn stream(State(state): State<AppState>, Query(q): Query<StreamParams>) ->
203
  "AUTO" | _ => vec![Format::FLAC, Format::MP3_320, Format::MP3_128],
204
  };
205
 
206
- fn extract_media_url(media_json: &serde_json::Value) -> String {
207
- let data = media_json.get("data").and_then(|v| v.as_array());
208
- let Some(data0) = data.and_then(|a| a.get(0)) else {
209
- return String::new();
 
 
 
 
 
 
 
 
 
210
  };
211
- let media_arr = data0.get("media").and_then(|v| v.as_array());
212
- let Some(media_arr) = media_arr else {
213
- return String::new();
214
- };
215
- for m in media_arr {
216
- if let Some(sources) = m.get("sources").and_then(|v| v.as_array()) {
217
- for s in sources {
218
- if let Some(url) = s.get("url").and_then(|v| v.as_str()) {
219
- let url = url.trim();
220
- if !url.is_empty() {
221
- return url.to_string();
222
- }
 
 
 
223
  }
224
  }
225
  }
226
  }
227
- String::new()
228
  }
229
 
230
- let client = api_client_for_arl(&state, q.arl.clone()).await;
231
- let url = {
232
- let mut client = client.lock().await;
233
- let resp: Result<DeezerTrackList, APIError> = client
234
- .api_call(
235
- "song.getListData",
236
- &json!({"sng_ids":[q.id],"array_default":["SNG_ID","TRACK_TOKEN"]}),
237
- )
238
- .await;
239
-
240
- let track_list = match resp {
241
- Ok(t) => t,
242
- Err(e) => return (StatusCode::SERVICE_UNAVAILABLE, e.to_string()).into_response(),
243
- };
244
-
245
- if track_list.data.is_empty() {
246
- return (StatusCode::BAD_REQUEST, "No valid ID".to_string()).into_response();
247
- }
248
-
249
- let track_token = track_list.data[0].TRACK_TOKEN.as_str();
250
- let media_resp = match client.get_media(&formats, vec![track_token]).await {
251
- Ok(r) => r,
252
- Err(e) => return (StatusCode::SERVICE_UNAVAILABLE, e.to_string()).into_response(),
253
- };
254
-
255
- let media_json: serde_json::Value = match media_resp.json().await {
256
- Ok(v) => v,
257
- Err(_) => {
258
- return (StatusCode::SERVICE_UNAVAILABLE, "Bad media response".to_string())
259
- .into_response()
260
- }
261
- };
262
-
263
- let url = extract_media_url(&media_json);
264
-
265
- if url.is_empty() {
266
- return (
267
- StatusCode::SERVICE_UNAVAILABLE,
268
- "No url in media response".to_string(),
269
- )
270
- .into_response();
271
- }
272
- url
273
  };
274
 
275
  let http = reqwest::Client::builder()
@@ -707,10 +747,7 @@ struct PairSession {
707
  }
708
 
709
  fn gen_pair_code() -> String {
710
- let ms = SystemTime::now()
711
- .duration_since(UNIX_EPOCH)
712
- .map(|d| d.as_millis())
713
- .unwrap_or(0);
714
  let hex = format!("{:x}", md5::compute(format!("pair:{ms}:{SECRET}").as_bytes()));
715
  hex.chars().take(10).collect::<String>()
716
  }
@@ -722,12 +759,15 @@ struct PairStatusQuery {
722
 
723
  async fn pair_start(State(state): State<AppState>) -> impl IntoResponse {
724
  let code = gen_pair_code();
725
- let created_ms = SystemTime::now()
726
- .duration_since(UNIX_EPOCH)
727
- .map(|d| d.as_millis())
728
- .unwrap_or(0);
729
  {
730
  let mut map = state.pair.write().await;
 
 
 
 
 
 
731
  map.insert(
732
  code.clone(),
733
  PairSession {
@@ -749,10 +789,16 @@ async fn pair_status(State(state): State<AppState>, Query(q): Query<PairStatusQu
749
  return json_error(StatusCode::BAD_REQUEST, "code required");
750
  }
751
 
752
- let map = state.pair.read().await;
 
 
753
  let Some(sess) = map.get(&code) else {
754
  return json_error(StatusCode::NOT_FOUND, "no such code");
755
  };
 
 
 
 
756
 
757
  if let Some(err) = &sess.error {
758
  return (StatusCode::OK, Json(json!({ "status": "error", "error": err }))).into_response();
@@ -1053,22 +1099,82 @@ async fn oauth_arl(Json(req): Json<OauthArlReq>) -> impl IntoResponse {
1053
 
1054
  let jar = Arc::new(Jar::default());
1055
  let url_deezer = "https://www.deezer.com".parse::<Url>().unwrap();
 
1056
  jar.add_cookie_str("comeback=1; Domain=.deezer.com", &url_deezer);
 
1057
  let client = reqwest::Client::builder()
1058
- .cookie_provider(jar)
1059
  .timeout(std::time::Duration::from_secs(20))
1060
  .build()
1061
  .unwrap();
1062
 
 
 
 
 
 
 
 
 
 
 
 
1063
  let _ = client
1064
  .get("https://api.deezer.com/platform/generic/track/80085")
1065
  .header("Authorization", format!("Bearer {}", token))
 
 
 
 
 
 
1066
  .send()
1067
  .await;
1068
 
1069
- let ud = match gw_light_call(&client, "deezer.getUserData", "null", json!({})).await {
1070
- Ok(v) => v,
1071
- Err(e) => return json_error(StatusCode::SERVICE_UNAVAILABLE, e),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1072
  };
1073
 
1074
  let check_form = ud["checkForm"].as_str().unwrap_or("").to_string();
 
40
  (status, Json(json!({ "error": message.into() }))).into_response()
41
  }
42
 
43
+ fn now_ms() -> u128 {
44
+ SystemTime::now()
45
+ .duration_since(UNIX_EPOCH)
46
+ .map(|d| d.as_millis())
47
+ .unwrap_or(0)
48
+ }
49
+
50
  async fn api_client_for_arl(state: &AppState, arl: Option<String>) -> Arc<Mutex<APIClient>> {
51
  let Some(arl) = arl.map(|s| s.trim().to_string()).filter(|s| !s.is_empty()) else {
52
  return state.api.clone();
 
64
  c
65
  }
66
 
67
+ fn extract_media_url(media_json: &serde_json::Value) -> String {
68
+ let data = media_json.get("data").and_then(|v| v.as_array());
69
+ let Some(data0) = data.and_then(|a| a.get(0)) else {
70
+ return String::new();
71
+ };
72
+ let media_arr = data0.get("media").and_then(|v| v.as_array());
73
+ let Some(media_arr) = media_arr else {
74
+ return String::new();
75
+ };
76
+ for m in media_arr {
77
+ if let Some(sources) = m.get("sources").and_then(|v| v.as_array()) {
78
+ for s in sources {
79
+ if let Some(url) = s.get("url").and_then(|v| v.as_str()) {
80
+ let url = url.trim();
81
+ if !url.is_empty() {
82
+ return url.to_string();
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ String::new()
89
+ }
90
+
91
+ async fn media_url_for_track(
92
+ client: &mut APIClient,
93
+ id: u32,
94
+ formats: &Vec<Format>,
95
+ ) -> Result<String, String> {
96
+ let resp: Result<DeezerTrackList, APIError> = client
97
+ .api_call(
98
+ "song.getListData",
99
+ &json!({"sng_ids":[id],"array_default":["SNG_ID","TRACK_TOKEN"]}),
100
+ )
101
+ .await;
102
+
103
+ let track_list = resp.map_err(|e| e.to_string())?;
104
+ if track_list.data.is_empty() {
105
+ return Err("No valid ID".to_string());
106
+ }
107
+
108
+ let track_token = track_list.data[0].TRACK_TOKEN.as_str();
109
+ let media_resp = client
110
+ .get_media(formats, vec![track_token])
111
+ .await
112
+ .map_err(|e| e.to_string())?;
113
+
114
+ let media_json: serde_json::Value = media_resp
115
+ .json()
116
+ .await
117
+ .map_err(|_| "Bad media response".to_string())?;
118
+
119
+ let url = extract_media_url(&media_json);
120
+ if url.is_empty() {
121
+ return Err("No url in media response".to_string());
122
+ }
123
+ Ok(url)
124
+ }
125
+
126
  #[derive(Deserialize)]
127
  struct DeezerTrackList {
128
  data: Vec<DeezerTrack>
 
269
  "AUTO" | _ => vec![Format::FLAC, Format::MP3_320, Format::MP3_128],
270
  };
271
 
272
+ let arl_key = q
273
+ .arl
274
+ .clone()
275
+ .map(|s| s.trim().to_string())
276
+ .filter(|s| !s.is_empty());
277
+
278
+ let mut url: Option<String> = None;
279
+ let mut last_err: Option<String> = None;
280
+ for attempt in 0..2 {
281
+ let client = api_client_for_arl(&state, arl_key.clone()).await;
282
+ let res = {
283
+ let mut client = client.lock().await;
284
+ media_url_for_track(&mut client, q.id, &formats).await
285
  };
286
+
287
+ match res {
288
+ Ok(u) => {
289
+ url = Some(u);
290
+ break;
291
+ }
292
+ Err(e) => {
293
+ last_err = Some(e);
294
+ if attempt == 0 {
295
+ if let Some(arl) = arl_key.clone() {
296
+ let mut map = state.api_by_arl.write().await;
297
+ map.remove(&arl);
298
+ } else {
299
+ let mut global = state.api.lock().await;
300
+ *global = APIClient::new();
301
  }
302
  }
303
  }
304
  }
 
305
  }
306
 
307
+ let Some(url) = url else {
308
+ return (
309
+ StatusCode::SERVICE_UNAVAILABLE,
310
+ last_err.unwrap_or_else(|| "stream:error".to_string()),
311
+ )
312
+ .into_response();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  };
314
 
315
  let http = reqwest::Client::builder()
 
747
  }
748
 
749
  fn gen_pair_code() -> String {
750
+ let ms = now_ms();
 
 
 
751
  let hex = format!("{:x}", md5::compute(format!("pair:{ms}:{SECRET}").as_bytes()));
752
  hex.chars().take(10).collect::<String>()
753
  }
 
759
 
760
  async fn pair_start(State(state): State<AppState>) -> impl IntoResponse {
761
  let code = gen_pair_code();
762
+ let created_ms = now_ms();
 
 
 
763
  {
764
  let mut map = state.pair.write().await;
765
+ let now = created_ms;
766
+ let ttl_ms: u128 = 10 * 60 * 1000;
767
+ map.retain(|_, v| now.saturating_sub(v.created_ms) <= ttl_ms);
768
+ if map.len() >= 512 {
769
+ map.clear();
770
+ }
771
  map.insert(
772
  code.clone(),
773
  PairSession {
 
789
  return json_error(StatusCode::BAD_REQUEST, "code required");
790
  }
791
 
792
+ let now = now_ms();
793
+ let ttl_ms: u128 = 10 * 60 * 1000;
794
+ let mut map = state.pair.write().await;
795
  let Some(sess) = map.get(&code) else {
796
  return json_error(StatusCode::NOT_FOUND, "no such code");
797
  };
798
+ if now.saturating_sub(sess.created_ms) > ttl_ms {
799
+ map.remove(&code);
800
+ return json_error(StatusCode::NOT_FOUND, "code expired");
801
+ }
802
 
803
  if let Some(err) = &sess.error {
804
  return (StatusCode::OK, Json(json!({ "status": "error", "error": err }))).into_response();
 
1099
 
1100
  let jar = Arc::new(Jar::default());
1101
  let url_deezer = "https://www.deezer.com".parse::<Url>().unwrap();
1102
+ let url_api = "https://api.deezer.com".parse::<Url>().unwrap();
1103
  jar.add_cookie_str("comeback=1; Domain=.deezer.com", &url_deezer);
1104
+ jar.add_cookie_str("dzr_uniq_id=; Domain=.deezer.com", &url_deezer);
1105
  let client = reqwest::Client::builder()
1106
+ .cookie_provider(jar.clone())
1107
  .timeout(std::time::Duration::from_secs(20))
1108
  .build()
1109
  .unwrap();
1110
 
1111
+ let _ = client
1112
+ .get("https://www.deezer.com/")
1113
+ .header(ACCEPT, "*/*")
1114
+ .header("Accept-Language", "en-US,en;q=0.9")
1115
+ .header(
1116
+ "User-Agent",
1117
+ "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
1118
+ )
1119
+ .send()
1120
+ .await;
1121
+
1122
  let _ = client
1123
  .get("https://api.deezer.com/platform/generic/track/80085")
1124
  .header("Authorization", format!("Bearer {}", token))
1125
+ .header(ACCEPT, "*/*")
1126
+ .header("Accept-Language", "en-US,en;q=0.9")
1127
+ .header(
1128
+ "User-Agent",
1129
+ "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
1130
+ )
1131
  .send()
1132
  .await;
1133
 
1134
+ if !jar_has_cookie(&jar, &url_deezer, "sid=") && !jar_has_cookie(&jar, &url_api, "sid=") {
1135
+ let _ = client
1136
+ .get("https://api.deezer.com/platform/generic/track/3135556")
1137
+ .header("Authorization", format!("Bearer {}", token))
1138
+ .header(ACCEPT, "*/*")
1139
+ .header("Accept-Language", "en-US,en;q=0.9")
1140
+ .header(
1141
+ "User-Agent",
1142
+ "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
1143
+ )
1144
+ .send()
1145
+ .await;
1146
+ }
1147
+
1148
+ let mut ud: Option<serde_json::Value> = None;
1149
+ let mut ud_err: Option<String> = None;
1150
+ for attempt in 0..2 {
1151
+ match gw_light_call(&client, "deezer.getUserData", "null", json!({})).await {
1152
+ Ok(v) => {
1153
+ ud = Some(v);
1154
+ break;
1155
+ }
1156
+ Err(e) => {
1157
+ ud_err = Some(e);
1158
+ if attempt == 0 {
1159
+ let _ = client
1160
+ .get("https://www.deezer.com/")
1161
+ .header(ACCEPT, "*/*")
1162
+ .header("Accept-Language", "en-US,en;q=0.9")
1163
+ .header(
1164
+ "User-Agent",
1165
+ "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
1166
+ )
1167
+ .send()
1168
+ .await;
1169
+ }
1170
+ }
1171
+ }
1172
+ }
1173
+ let Some(ud) = ud else {
1174
+ return json_error(
1175
+ StatusCode::SERVICE_UNAVAILABLE,
1176
+ ud_err.unwrap_or_else(|| "gw:getUserData".to_string()),
1177
+ );
1178
  };
1179
 
1180
  let check_form = ud["checkForm"].as_str().unwrap_or("").to_string();