use base64::{Engine as _, engine::general_purpose}; use ntex::web; use std::time::Duration; use crate::util::requester::Requester; fn ssut51(arg: &str) -> u32 { arg.chars() .filter(|c| c.is_ascii_digit()) .map(|c| c.to_digit(10).unwrap()) .sum() } fn boo(sum1: u32, sum2: u32) -> String { let raw = format!("{}-{}-{}", sum1, "sxyprn.com", sum2); let encoded = general_purpose::STANDARD.encode(raw); encoded .replace('+', "-") .replace('/', "_") .replace('=', ".") } /// Extracts all CDN path values from the data-vnfo JSON attribute. pub(crate) fn extract_all_cdn_paths(html: &str) -> Vec { let json_str = match html.split("data-vnfo='").nth(1) { Some(s) => s.split('\'').next().unwrap_or(""), None => return vec![], }; if let Ok(serde_json::Value::Object(map)) = serde_json::from_str(json_str) { let paths: Vec = map .values() .filter_map(|v| v.as_str().map(|s| s.replace('\\', ""))) .filter(|s| !s.is_empty()) .collect(); if !paths.is_empty() { return paths; } } // Fallback: original single-value extraction let first = json_str .split("\":\"") .nth(1) .and_then(|s| s.split("\"}").next()) .map(|s| s.replace('\\', "")) .unwrap_or_default(); if first.is_empty() { vec![] } else { vec![first] } } /// Applies the sxyprn segment transformation to produce the pre-redirect CDN URL. pub(crate) fn transform_cdn_path(path: &str) -> Option { let mut tmp: Vec = path.split('/').map(|s| s.to_string()).collect(); if tmp.len() < 8 { return None; } let s6 = ssut51(&tmp[6]); let s7 = ssut51(&tmp[7]); tmp[1] = format!("{}8/{}", tmp[1], boo(s6, s7)); tmp[5] = format!("{}", tmp[5].parse::().unwrap_or(0).saturating_sub(s6 + s7)); Some(format!("https://sxyprn.com{}", tmp.join("/"))) } /// Races all candidate CDN URLs concurrently via blocking curl HEAD requests. /// Returns the final CDN URL from the first candidate that successfully redirects. async fn race_cdn_urls(candidate_urls: Vec) -> String { if candidate_urls.is_empty() { return String::new(); } let (tx, mut rx) = tokio::sync::mpsc::channel::(candidate_urls.len()); for cdn_url in candidate_urls { let tx = tx.clone(); tokio::spawn(async move { let result = tokio::task::spawn_blocking(move || { crate::util::get_redirect_location(&cdn_url) .ok() .flatten() .map(|loc| format!("https:{}", loc)) }) .await .ok() .flatten(); if let Some(url) = result { let _ = tx.send(url).await; } }); } drop(tx); tokio::time::timeout(Duration::from_secs(15), rx.recv()) .await .ok() .flatten() .unwrap_or_default() } /// Resolves all candidate CDN mirror URLs concurrently, collecting every /// mirror that redirects successfully within the timeout window. Unlike /// `race_cdn_urls`, this does not stop at the first success -- the mirrors /// are redundant copies of the same stream, and callers that want to serve /// directly-playable format URLs need as many working ones as possible. pub(crate) async fn resolve_all_cdn_urls(candidate_urls: Vec) -> Vec { if candidate_urls.is_empty() { return vec![]; } let handles: Vec<_> = candidate_urls .into_iter() .map(|cdn_url| { tokio::spawn(async move { tokio::task::spawn_blocking(move || { crate::util::get_redirect_location(&cdn_url) .ok() .flatten() .map(|loc| format!("https:{}", loc)) }) .await .ok() .flatten() }) }) .collect(); let deadline = tokio::time::Instant::now() + Duration::from_secs(15); let mut resolved = Vec::new(); for handle in handles { if let Ok(Ok(Some(url))) = tokio::time::timeout_at(deadline, handle).await { if !resolved.contains(&url) { resolved.push(url); } } } resolved } /// Fetches the sxyprn detail page for `slug` and resolves every mirror CDN /// URL it advertises. Used by the provider to eagerly populate `formats` /// for app clients, instead of the lazy single-mirror `/proxy/sxyprn/...` /// redirect used by other clients. pub(crate) async fn resolve_all_media_urls( requester: &mut Requester, base_url: &str, slug: &str, ) -> Vec { let full_url = format!("{}/post/{}", base_url, slug); let text = requester.get(&full_url, None).await.unwrap_or_default(); if text.is_empty() { return vec![]; } let candidate_urls: Vec = extract_all_cdn_paths(&text) .iter() .filter_map(|p| transform_cdn_path(p)) .collect(); resolve_all_cdn_urls(candidate_urls).await } #[derive(Debug, Clone)] pub struct SxyprnProxy {} impl SxyprnProxy { pub fn new() -> Self { SxyprnProxy {} } pub async fn get_video_url( &self, url: String, requester: web::types::State, ) -> String { if let Some(encoded) = url.strip_prefix("race/") { return self.resolve_race(encoded).await; } let mut requester = requester.get_ref().clone(); let full_url = format!("https://sxyprn.com/{}", url); let text = requester.get(&full_url, None).await.unwrap_or_default(); if text.is_empty() { return String::new(); } let cdn_paths = extract_all_cdn_paths(&text); let candidate_urls: Vec = cdn_paths .iter() .filter_map(|p| transform_cdn_path(p)) .collect(); if candidate_urls.is_empty() { return String::new(); } let joined = candidate_urls.join("|"); let encoded = general_purpose::URL_SAFE_NO_PAD.encode(joined.as_bytes()); format!("/proxy/sxyprn/race/{}", encoded) } async fn resolve_race(&self, encoded: &str) -> String { let bytes = match general_purpose::URL_SAFE_NO_PAD.decode(encoded.as_bytes()) { Ok(b) => b, Err(_) => return String::new(), }; let decoded = match String::from_utf8(bytes) { Ok(s) => s, Err(_) => return String::new(), }; let urls: Vec = decoded .split('|') .filter(|s| !s.is_empty()) .map(|s| s.to_string()) .collect(); race_cdn_urls(urls).await } }