use ntex::web; use regex::Regex; use wreq::Version; use crate::util::requester::Requester; #[derive(Debug, Clone)] pub struct SpankbangProxy {} impl SpankbangProxy { pub fn new() -> Self { SpankbangProxy {} } fn request_headers() -> Vec<(String, String)> { vec![ ( "accept".to_string(), "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8" .to_string(), ), ("accept-language".to_string(), "en-US,en;q=0.6".to_string()), ("cache-control".to_string(), "no-cache".to_string()), ("pragma".to_string(), "no-cache".to_string()), ("priority".to_string(), "u=0, i".to_string()), ( "sec-ch-ua".to_string(), r#""Chromium";v="146", "Not-A.Brand";v="24", "Brave";v="146""#.to_string(), ), ("sec-ch-ua-mobile".to_string(), "?0".to_string()), ("sec-ch-ua-platform".to_string(), "\"Linux\"".to_string()), ("sec-fetch-dest".to_string(), "document".to_string()), ("sec-fetch-mode".to_string(), "navigate".to_string()), ("sec-fetch-site".to_string(), "none".to_string()), ("sec-fetch-user".to_string(), "?1".to_string()), ("sec-gpc".to_string(), "1".to_string()), ( "upgrade-insecure-requests".to_string(), "1".to_string(), ), ( "user-agent".to_string(), "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36" .to_string(), ), ("Referer".to_string(), "https://spankbang.com/".to_string()), ] } fn extract_stream_data(text: &str) -> Option<&str> { let marker = "var stream_data = "; let start = text.find(marker)? + marker.len(); let rest = &text[start..]; let end = rest.find("};")?; Some(&rest[..=end]) } fn extract_first_stream_url(stream_data: &str, key: &str) -> Option { let pattern = format!(r"'{}'\s*:\s*\[\s*'([^']+)'", regex::escape(key)); let regex = Regex::new(&pattern).ok()?; regex .captures(stream_data) .and_then(|captures| captures.get(1)) .map(|value| value.as_str().to_string()) } fn select_best_stream_url(stream_data: &str) -> Option { for key in [ "m3u8", "4k", "1080p", "720p", "480p", "320p", "240p", "main", ] { if let Some(url) = Self::extract_first_stream_url(stream_data, key) { return Some(url); } } None } pub async fn get_video_url( &self, url: String, requester: web::types::State, ) -> String { let mut requester = requester.get_ref().clone(); let url = format!("https://spankbang.com/{}", url.trim_start_matches('/')); let text = requester .get_with_headers(&url, Self::request_headers(), Some(Version::HTTP_2)) .await .unwrap_or_default(); if text.is_empty() { return String::new(); } let Some(stream_data) = Self::extract_stream_data(&text) else { return String::new(); }; Self::select_best_stream_url(stream_data).unwrap_or_default() } } #[cfg(test)] mod tests { use super::SpankbangProxy; #[test] fn prefers_m3u8_when_present() { assert_eq!( SpankbangProxy::request_headers(), vec![ ( "accept".to_string(), "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8" .to_string(), ), ("accept-language".to_string(), "en-US,en;q=0.6".to_string()), ("cache-control".to_string(), "no-cache".to_string()), ("pragma".to_string(), "no-cache".to_string()), ("priority".to_string(), "u=0, i".to_string()), ( "sec-ch-ua".to_string(), r#""Chromium";v="146", "Not-A.Brand";v="24", "Brave";v="146""#.to_string(), ), ("sec-ch-ua-mobile".to_string(), "?0".to_string()), ("sec-ch-ua-platform".to_string(), "\"Linux\"".to_string()), ("sec-fetch-dest".to_string(), "document".to_string()), ("sec-fetch-mode".to_string(), "navigate".to_string()), ("sec-fetch-site".to_string(), "none".to_string()), ("sec-fetch-user".to_string(), "?1".to_string()), ("sec-gpc".to_string(), "1".to_string()), ( "upgrade-insecure-requests".to_string(), "1".to_string(), ), ( "user-agent".to_string(), "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36" .to_string(), ), ("Referer".to_string(), "https://spankbang.com/".to_string()), ] ); let data = r#" var stream_data = {'240p': ['https://cdn.example/240.mp4'], '720p': ['https://cdn.example/720.mp4'], 'm3u8': ['https://cdn.example/master.m3u8'], 'main': ['https://cdn.example/720.mp4']}; "#; let stream_data = SpankbangProxy::extract_stream_data(data).unwrap(); assert_eq!( SpankbangProxy::select_best_stream_url(stream_data).as_deref(), Some("https://cdn.example/master.m3u8") ); } #[test] fn falls_back_to_highest_quality_mp4() { let data = r#" var stream_data = {'240p': ['https://cdn.example/240.mp4'], '480p': ['https://cdn.example/480.mp4'], '720p': ['https://cdn.example/720.mp4'], '1080p': [], '4k': [], 'm3u8': [], 'main': ['https://cdn.example/480.mp4']}; "#; let stream_data = SpankbangProxy::extract_stream_data(data).unwrap(); assert_eq!( SpankbangProxy::select_best_stream_url(stream_data).as_deref(), Some("https://cdn.example/720.mp4") ); } }