recycleactor commited on
Commit
ecc2f66
·
verified ·
1 Parent(s): 5ed7694

Upload main.rs

Browse files
Files changed (1) hide show
  1. src/main.rs +140 -0
src/main.rs CHANGED
@@ -808,6 +808,145 @@ async fn stream_info(State(state): State<AppState>, Query(q): Query<StreamParams
808
  }))).into_response()
809
  }
810
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
811
  async fn stream(
812
  State(state): State<AppState>,
813
  Query(q): Query<StreamParams>,
@@ -3351,6 +3490,7 @@ async fn main() {
3351
  .route("/get_url", post(get_url))
3352
  .route("/fetch", get(fetch))
3353
  .route("/stream", get(stream))
 
3354
  .route("/stream_info", get(stream_info))
3355
  .route("/user_data", post(user_data))
3356
  .route("/playlists", post(playlists))
 
808
  }))).into_response()
809
  }
810
 
811
+ /// /download — like /stream but downloads full track into memory and returns with Content-Length.
812
+ /// Telegram requires Content-Length for sendAudio by URL; this endpoint ensures it.
813
+ async fn download(
814
+ State(state): State<AppState>,
815
+ Query(q): Query<StreamParams>,
816
+ ) -> impl IntoResponse {
817
+ let formats = match q.format.as_deref().unwrap_or("AUTO") {
818
+ "FLAC" => vec![Format::FLAC, Format::MP3_320, Format::MP3_128],
819
+ "MP3_320" => vec![Format::MP3_320, Format::MP3_128],
820
+ "MP3_MISC" => vec![Format::MP3_MISC, Format::MP3_128],
821
+ "MP3_128" => vec![Format::MP3_128],
822
+ _ => vec![Format::FLAC, Format::MP3_320, Format::MP3_128],
823
+ };
824
+
825
+ let arl_key = q.arl.as_ref().map(|s| s.trim().to_string()).filter(|s| !s.is_empty());
826
+ if arl_key.is_none() {
827
+ return (StatusCode::SERVICE_UNAVAILABLE, "arl required").into_response();
828
+ }
829
+
830
+ // Resolve correct FALLBACK_ID for decryption
831
+ let client = api_client_for_arl(&state, arl_key.clone()).await;
832
+ let target_id = {
833
+ let mut c = client.lock().await;
834
+ resolve_fallback_id(&mut c, q.id).await
835
+ };
836
+ let mut decrypt_id = target_id;
837
+
838
+ let mut url: Option<String> = None;
839
+ let mut used_fmt = String::new();
840
+ let mut last_err: Option<String> = None;
841
+
842
+ // Try DZMEDIA_UPSTREAM first for highest quality
843
+ if url.is_none() {
844
+ if let Ok(text) = upstream_get_url_text(&formats, &vec![target_id]).await {
845
+ if let Ok(v) = serde_json::from_str::<serde_json::Value>(&text) {
846
+ let format_strings: Vec<String> = formats.iter().map(|f| format!("{:?}", f)).collect();
847
+ let format_strs: Vec<&str> = format_strings.iter().map(|s| s.as_str()).collect();
848
+ let (u, f, _) = extract_media_url_and_format(&v, &format_strs);
849
+ if !u.is_empty() {
850
+ url = Some(u);
851
+ used_fmt = f;
852
+ }
853
+ }
854
+ }
855
+ }
856
+
857
+ // Fallback: ARL path
858
+ for attempt in 0..2u8 {
859
+ if url.is_some() { break; }
860
+ if attempt >= 1 {
861
+ if let Some(ref arl) = arl_key {
862
+ state.api_by_arl.write().await.remove(arl);
863
+ }
864
+ }
865
+ let client = api_client_for_arl(&state, arl_key.clone()).await;
866
+ let res = {
867
+ let mut c = client.lock().await;
868
+ if attempt >= 1 {
869
+ if let Err(e) = c.force_renew().await {
870
+ last_err = Some(format!("renew:{}", e));
871
+ continue;
872
+ }
873
+ }
874
+ match timeout(Duration::from_secs(15), media_url_for_track(&mut c, target_id, &formats)).await {
875
+ Ok(v) => v,
876
+ Err(_) => Err("timeout".to_string()),
877
+ }
878
+ };
879
+ match res {
880
+ Ok((u, f, id_used)) => { url = Some(u); used_fmt = f; decrypt_id = id_used; break; }
881
+ Err(e) => { last_err = Some(e); }
882
+ }
883
+ }
884
+
885
+ let cdn_url = match url {
886
+ Some(u) => u,
887
+ None => {
888
+ let err = last_err.unwrap_or_else(|| "download:no_url".to_string());
889
+ tracing::error!("download {} failed: {}", q.id, err);
890
+ return (StatusCode::NOT_FOUND, "Not Found").into_response();
891
+ }
892
+ };
893
+
894
+ // Download the full encrypted file
895
+ let http = reqwest::Client::builder().no_proxy().timeout(Duration::from_secs(120)).build().unwrap();
896
+ let resp = match http.get(&cdn_url).send().await {
897
+ Ok(r) => r,
898
+ Err(e) => {
899
+ tracing::error!("download {} cdn fetch failed: {}", q.id, e);
900
+ return (StatusCode::BAD_GATEWAY, "CDN fetch failed").into_response();
901
+ }
902
+ };
903
+ if !resp.status().is_success() {
904
+ return (StatusCode::BAD_GATEWAY, "CDN error").into_response();
905
+ }
906
+ let content_type = resp.headers()
907
+ .get(reqwest::header::CONTENT_TYPE)
908
+ .and_then(|v| v.to_str().ok())
909
+ .unwrap_or("audio/mpeg")
910
+ .to_string();
911
+ let enc_bytes = match resp.bytes().await {
912
+ Ok(b) => b,
913
+ Err(e) => {
914
+ tracing::error!("download {} cdn read failed: {}", q.id, e);
915
+ return (StatusCode::BAD_GATEWAY, "CDN read failed").into_response();
916
+ }
917
+ };
918
+
919
+ // Decrypt in-memory (every 3rd 2048-byte block)
920
+ let key = blowfish_key(decrypt_id);
921
+ let mut dec = enc_bytes.to_vec();
922
+ let block_size = 2048usize;
923
+ let mut bi = 0usize;
924
+ let mut pos = 0usize;
925
+ while pos + block_size <= dec.len() {
926
+ if bi % 3 == 0 {
927
+ let _ = decrypt_stripe(&mut dec[pos..pos + block_size], &key);
928
+ }
929
+ bi += 1;
930
+ pos += block_size;
931
+ }
932
+
933
+ // Determine file extension
934
+ let ext = if used_fmt.contains("FLAC") || content_type.contains("flac") { "flac" } else { "mp3" };
935
+ let filename = format!("track_{}.{}", q.id, ext);
936
+ let ct = if ext == "flac" { "audio/flac" } else { "audio/mpeg" };
937
+
938
+ axum::response::Response::builder()
939
+ .status(200)
940
+ .header("Content-Type", ct)
941
+ .header("Content-Length", dec.len().to_string())
942
+ .header("Content-Disposition", format!("attachment; filename=\"{}\"", filename))
943
+ .header("Accept-Ranges", "none")
944
+ .header("Access-Control-Allow-Origin", "*")
945
+ .body(axum::body::Body::from(dec))
946
+ .unwrap()
947
+ .into_response()
948
+ }
949
+
950
  async fn stream(
951
  State(state): State<AppState>,
952
  Query(q): Query<StreamParams>,
 
3490
  .route("/get_url", post(get_url))
3491
  .route("/fetch", get(fetch))
3492
  .route("/stream", get(stream))
3493
+ .route("/download", get(download))
3494
  .route("/stream_info", get(stream_info))
3495
  .route("/user_data", post(user_data))
3496
  .route("/playlists", post(playlists))