use ntex::web; use regex::Regex; use crate::util::requester::Requester; const BASE_URL: &str = "https://www.clapdat.com"; #[derive(Debug, Clone)] pub struct ClapdatProxy {} impl ClapdatProxy { pub fn new() -> Self { Self {} } fn normalize_detail_url(endpoint: &str) -> Option { let value = endpoint.trim().trim_start_matches('/'); if value.is_empty() { return None; } let detail_url = if value.starts_with("http://") || value.starts_with("https://") { value.to_string() } else { format!("https://{}", value) }; let detail_url = detail_url.replacen("http://", "https://", 1); let parsed = url::Url::parse(&detail_url).ok()?; let host = parsed.host_str()?; if !(host == "www.clapdat.com" || host == "clapdat.com") { return None; } if !parsed.path().starts_with("/video/") { return None; } Some(detail_url) } fn clapdat_decode(input: &str) -> Option> { let compact = if input.len() > 209 { format!("{}{}", &input[..19], &input[209..]) } else { input.to_string() }; let cleaned: String = compact .chars() .filter(|c| c.is_ascii_alphanumeric() || *c == '+' || *c == '/') .collect(); if cleaned.is_empty() { return None; } let mut padded = cleaned; while padded.len() % 4 != 0 { padded.push('='); } base64::Engine::decode(&base64::engine::general_purpose::STANDARD, padded.as_bytes()).ok() } fn extract_media_url(html: &str) -> Option { let domain_re = Regex::new(r#"file_domain:"([^"]+)""#).ok()?; let file_re = Regex::new(r#"file:"([^"]+)""#).ok()?; let domain = domain_re .captures(html) .and_then(|caps| caps.get(1).map(|m| m.as_str().trim().to_string()))?; let encoded = file_re .captures(html) .and_then(|caps| caps.get(1).map(|m| m.as_str().trim().to_string()))?; let decoded = Self::clapdat_decode(&encoded)?; let path: String = decoded.into_iter().map(char::from).collect(); if path.is_empty() { return None; } Some(format!("https://{}/{}", domain, path.trim_start_matches('/'))) } } impl crate::proxies::Proxy for ClapdatProxy { 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(); }; let mut requester = requester.get_ref().clone(); let headers = vec![ ( "accept".to_string(), "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8".to_string(), ), ("accept-language".to_string(), "en-US,en;q=0.8".to_string()), ( "user-agent".to_string(), "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36".to_string(), ), ("referer".to_string(), BASE_URL.to_string()), ]; let html = requester .get_with_headers(&detail_url, headers, Some(wreq::Version::HTTP_11)) .await .unwrap_or_default(); if html.is_empty() { return String::new(); } Self::extract_media_url(&html).unwrap_or_default() } }