use ntex::web; use regex::Regex; use std::process::Command; use url::Url; use crate::util::requester::Requester; #[derive(Debug, Clone)] pub struct PorndishProxy {} impl PorndishProxy { pub fn new() -> Self { Self {} } fn normalize_detail_url(endpoint: &str) -> Option { let endpoint = endpoint.trim(); if endpoint.is_empty() { return None; } if endpoint.starts_with("http://") || endpoint.starts_with("https://") { Some(endpoint.to_string()) } else { Some(format!("https://{}", endpoint.trim_start_matches('/'))) } } fn parse_url(url: &str) -> Option { Url::parse(url).ok() } fn is_porndish_host(host: &str) -> bool { matches!(host, "www.porndish.com" | "porndish.com") } fn is_myvidplay_host(host: &str) -> bool { matches!(host, "myvidplay.com" | "www.myvidplay.com") } fn is_vidara_host(host: &str) -> bool { matches!(host, "vidara.so" | "www.vidara.so") } fn is_allowed_detail_url(url: &str) -> bool { let Some(url) = Self::parse_url(url) else { return false; }; if url.scheme() != "https" { return false; } let Some(host) = url.host_str() else { return false; }; Self::is_porndish_host(host) && url.path().starts_with("/porn/") } fn is_allowed_myvidplay_iframe_url(url: &str) -> bool { let Some(url) = Self::parse_url(url) else { return false; }; if url.scheme() != "https" { return false; } let Some(host) = url.host_str() else { return false; }; Self::is_myvidplay_host(host) && url.path().starts_with("/e/") } fn is_allowed_myvidplay_pass_url(url: &str) -> bool { let Some(url) = Self::parse_url(url) else { return false; }; if url.scheme() != "https" { return false; } let Some(host) = url.host_str() else { return false; }; Self::is_myvidplay_host(host) && url.path().starts_with("/pass_md5/") } fn is_allowed_vidara_iframe_url(url: &str) -> bool { let Some(url) = Self::parse_url(url) else { return false; }; if url.scheme() != "https" { return false; } let Some(host) = url.host_str() else { return false; }; Self::is_vidara_host(host) && url.path().starts_with("/e/") } fn vidara_api_url(iframe_url: &str) -> Option { let url = Self::parse_url(iframe_url)?; if !Self::is_allowed_vidara_iframe_url(iframe_url) { return None; } let filecode = url .path_segments()? .filter(|segment| !segment.is_empty()) .next_back()? .to_string(); if filecode.is_empty() { return None; } Some(format!("https://vidara.so/api/stream?filecode={filecode}")) } fn regex(value: &str) -> Option { Regex::new(value).ok() } async fn fetch_with_curl_cffi(url: &str, referer: Option<&str>) -> Option { let url = url.to_string(); let referer = referer.unwrap_or("").to_string(); let output = tokio::task::spawn_blocking(move || { Command::new("python3") .arg("-c") .arg( r#" import sys from curl_cffi import requests url = sys.argv[1] referer = sys.argv[2] if len(sys.argv) > 2 else "" headers = {} if referer: headers["Referer"] = referer response = requests.get( url, impersonate="chrome", timeout=30, allow_redirects=True, headers=headers, ) if response.status_code >= 400: sys.exit(1) sys.stdout.buffer.write(response.content) "#, ) .arg(url) .arg(referer) .output() }) .await .ok()? .ok()?; if !output.status.success() { return None; } Some(String::from_utf8_lossy(&output.stdout).to_string()) } async fn resolve_first_redirect(url: &str) -> Option { let url = url.to_string(); let output = tokio::task::spawn_blocking(move || { Command::new("python3") .arg("-c") .arg( r#" import sys from curl_cffi import requests url = sys.argv[1] response = requests.get( url, impersonate="chrome", timeout=30, allow_redirects=False, ) location = response.headers.get("location", "") if location: sys.stdout.write(location) "#, ) .arg(url) .output() }) .await .ok()? .ok()?; let location = String::from_utf8_lossy(&output.stdout).trim().to_string(); if location.is_empty() { None } else { Some(location) } } fn extract_iframe_fragments(html: &str) -> Vec { let Some(regex) = Self::regex(r#"const\s+[A-Za-z0-9_]+Content\s*=\s*"((?:\\.|[^"\\])*)";"#) else { return vec![]; }; let mut fragments = Vec::new(); for captures in regex.captures_iter(html) { let Some(value) = captures.get(1).map(|value| value.as_str()) else { continue; }; let encoded = format!("\"{value}\""); let decoded = serde_json::from_str::(&encoded).unwrap_or_default(); if decoded.contains(" Option { let regex = Self::regex(r#"(?is)]+src="([^"]+)"[^>]*>"#)?; regex .captures(fragment) .and_then(|captures| captures.get(1)) .map(|value| value.as_str().to_string()) } async fn resolve_myvidplay_stream(iframe_url: &str) -> Option { if !Self::is_allowed_myvidplay_iframe_url(iframe_url) { return None; } let html = Self::fetch_with_curl_cffi(iframe_url, Some("https://www.porndish.com/")).await?; let pass_regex = Self::regex(r#"\$\.get\(\s*['"](/pass_md5/[^'"]+)['"]"#)?; let path = pass_regex .captures(&html) .and_then(|captures| captures.get(1)) .map(|value| value.as_str().to_string())?; let token = path.trim_end_matches('/').rsplit('/').next()?.to_string(); if token.is_empty() { return None; } let pass_url = if path.starts_with("http://") || path.starts_with("https://") { path } else { let base = Url::parse(iframe_url).ok()?; base.join(&path).ok()?.to_string() }; if !Self::is_allowed_myvidplay_pass_url(&pass_url) { return None; } let base = Self::fetch_with_curl_cffi(&pass_url, Some(iframe_url)) .await? .trim() .to_string(); if base.is_empty() || base == "RELOAD" || !base.starts_with("http") { return None; } let chars = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .ok()? .as_millis(); let suffix = (0..10) .map(|index| { let pos = ((now + (index as u128 * 17)) % chars.len() as u128) as usize; chars[pos] as char }) .collect::(); let stream_url = format!("{base}{suffix}?token={token}&expiry={now}"); Some( Self::resolve_first_redirect(&stream_url) .await .unwrap_or(stream_url), ) } async fn resolve_vidara_stream(iframe_url: &str) -> Option { let api_url = Self::vidara_api_url(iframe_url)?; let response = Self::fetch_with_curl_cffi(&api_url, Some(iframe_url)).await?; let json: serde_json::Value = serde_json::from_str(&response).ok()?; let stream_url = json .get("streaming_url") .and_then(|value| value.as_str())? .trim() .to_string(); if stream_url.is_empty() { return None; } Some(stream_url) } pub async fn get_video_url( &self, url: String, _requester: web::types::State, ) -> String { let Some(detail_url) = Self::normalize_detail_url(&url) else { return String::new(); }; if !Self::is_allowed_detail_url(&detail_url) { return String::new(); } let Some(html) = Self::fetch_with_curl_cffi(&detail_url, Some("https://www.porndish.com/")).await else { return String::new(); }; let mut fallback_iframe: Option = None; for fragment in Self::extract_iframe_fragments(&html) { let Some(iframe_url) = Self::parse_embed_source(&fragment) else { continue; }; let iframe_url = if iframe_url.starts_with("http://") || iframe_url.starts_with("https://") { iframe_url } else if iframe_url.starts_with("//") { format!("https:{iframe_url}") } else { continue; }; if Self::is_allowed_vidara_iframe_url(&iframe_url) { if let Some(stream_url) = Self::resolve_vidara_stream(&iframe_url).await { return stream_url; } } if fallback_iframe.is_none() && Self::is_allowed_myvidplay_iframe_url(&iframe_url) { fallback_iframe = Some(iframe_url); } } if let Some(iframe_url) = fallback_iframe { if let Some(stream_url) = Self::resolve_myvidplay_stream(&iframe_url).await { return stream_url; } } String::new() } } #[cfg(test)] mod tests { use super::PorndishProxy; #[test] fn allows_only_porndish_detail_urls() { assert!(PorndishProxy::is_allowed_detail_url( "https://www.porndish.com/porn/example/" )); assert!(!PorndishProxy::is_allowed_detail_url( "https://www.porndish.com/search/example/" )); assert!(!PorndishProxy::is_allowed_detail_url( "https://example.com/porn/example/" )); } }