From 8ae0fcb544819eb520ed37c9d70c364075fea8ce Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 5 May 2026 08:58:26 +0000 Subject: [PATCH] vidara proxy for sxyprn --- Cargo.toml | 12 ++-- src/api.rs | 3 +- src/providers/sxyprn.rs | 81 ++++++++++++++++++++++---- src/proxies/mod.rs | 4 ++ src/proxies/sxyprn.rs | 40 +------------ src/proxies/vidara.rs | 124 ++++++++++++++++++++++++++++++++++++++++ src/proxy.rs | 7 +++ src/videos.rs | 119 -------------------------------------- 8 files changed, 212 insertions(+), 178 deletions(-) create mode 100644 src/proxies/vidara.rs diff --git a/Cargo.toml b/Cargo.toml index 98f6cb3..74da71a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,25 +15,25 @@ env_logger = "0.11.8" error-chain = "0.12.4" futures = "0.3.31" htmlentity = "1.3.2" -ntex = { version = "2.15.1", features = ["tokio"] } -ntex-files = "2.0.0" +ntex = { version = "3", features = ["tokio"] } +ntex-files = "3" serde = "1.0.228" serde_json = "1.0.145" tokio = { version = "1.49", features = ["full"] } -wreq = { version = "6.0.0-rc.26", features = ["cookies", "multipart", "json"] } -wreq-util = "3.0.0-rc.10" +wreq = { version = "5", features = ["cookies", "multipart", "json"] } +wreq-util = "2" percent-encoding = "2.3.2" capitalize = "0.3.4" url = "2.5.7" base64 = "0.22.1" -scraper = "0.24.0" +scraper = "0.26.0" once_cell = "1.21.3" rustc-hash = "2.1.1" async-trait = "0.1" regex = "1.12.2" titlecase = "3.6.0" dashmap = "6.1.0" -lru = "0.16.3" +lru = "0.18" rand = "0.10.0" chrono = "0.4.44" md5 = "0.8.0" diff --git a/src/api.rs b/src/api.rs index dc7a700..ec14bc2 100644 --- a/src/api.rs +++ b/src/api.rs @@ -599,7 +599,7 @@ async fn videos_post( } for video in video_items.iter_mut() { - if video.duration <= 120 { + if video.duration <= 120 && video.duration > 0{ let mut preview_url = video.url.clone(); if let Some(formats) = &video.formats { if let Some(first) = formats.first() { @@ -657,7 +657,6 @@ async fn videos_post( for video in video_items.iter_mut() { video.id = format!("{}:{}", channel, video.id); - println!("Video ID set to: {}", video.id); } // There is a bug in Hottub38 that makes the client error for a 403-url even though formats work fine diff --git a/src/providers/sxyprn.rs b/src/providers/sxyprn.rs index d981fa4..48b3ece 100644 --- a/src/providers/sxyprn.rs +++ b/src/providers/sxyprn.rs @@ -8,6 +8,7 @@ use crate::util::discord::send_discord_error_report; use crate::util::requester::Requester; use crate::util::time::parse_time_to_seconds; use crate::videos::ServerOptions; +use crate::videos::VideoFormat; use crate::videos::VideoItem; use async_trait::async_trait; use error_chain::error_chain; @@ -353,6 +354,7 @@ impl SxyprnProvider { .replace('\n', "") .replace(" + ", " ") .replace(" ", " ") + .replace("\\", "") .trim() .to_string(); @@ -360,6 +362,29 @@ impl SxyprnProvider { title = title[4..].to_string(); } + // Extract tags from title (words starting with #) + let mut tags = Vec::new(); + let words: Vec<&str> = title.split_whitespace().collect(); + let mut cleaned_words = Vec::new(); + + for word in words { + let raw_tag = word + .trim_end_matches(|c: char| !c.is_alphanumeric() && c != '_' && c != '-') + .to_string(); + + if raw_tag.starts_with('#') && raw_tag.len() > 1 { + let tag = raw_tag[1..].to_string(); + if !tags.contains(&tag) { + tags.push(tag); + } + } else { + cleaned_words.push(word.to_string()); + } + } + + // Reconstruct title without tags + title = cleaned_words.join(" "); + // id (DON'T index [6]) let id = video_url .split('/') @@ -416,8 +441,15 @@ impl SxyprnProvider { let duration = parse_time_to_seconds(&raw_duration).unwrap_or(0) as u32; - // stream urls (your filter condition looks suspicious; leaving as-is) - let stream_urls = video_segment + // stream urls - collect both lulustream and vidara.so URLs + let mut stream_urls = vec![format!( + "{}/proxy/sxyprn/post/{}", + options.public_url_base.as_deref().unwrap_or(""), + id + )]; + + // Also collect and transform vidara.so URLs to proxy format + let vidara_urls: Vec = video_segment .split("extlink_icon extlink") .filter_map(|part| { part.split("href='") @@ -425,27 +457,52 @@ impl SxyprnProvider { .and_then(|s| s.split('\'').next()) .map(|u| u.to_string()) }) - .filter(|url| url.starts_with("https://lulustream.")) - .collect::>(); + .filter(|url| url.contains("vidara.so/v/")) + .filter_map(|url| { + url.split("/v/").last().map(|video_id| { + format!( + "{}/proxy/vidara/e/{}", + options.public_url_base.as_deref().unwrap_or(""), + video_id + ) + }) + }) + .collect(); - let video_item_url = stream_urls.first().cloned().unwrap_or_else(|| { - crate::providers::build_proxy_url(options, "sxyprn", &format!("post/{}", id)) - }); + stream_urls.extend(vidara_urls); + let formats: Vec = stream_urls + .into_iter() + .map(|url| { + VideoFormat::new(url.clone(), "auto".to_string(), "mp4".to_string()) + .format_note( + url.split("/") + .nth(4) + .or_else(|| Some(&url)) + .unwrap_or_default() + .to_string(), + ) + }) + .collect::>(); let mut video_item = VideoItem::new( - id, + id.clone(), title, - video_item_url, + format!("{}/post/{}", self.url, id.clone()), "sxyprn".to_string(), thumb, duration, ) - .views(views.parse::().unwrap_or(0)); + .views(views.parse::().unwrap_or(0)) + .formats(formats); - if let Some(p) = preview { - video_item = video_item.preview(p); + // Add tags if any were found + if !tags.is_empty() { + video_item.tags = Some(tags); } + if preview.is_some() { + video_item.preview = preview; + } items.push(video_item); } diff --git a/src/proxies/mod.rs b/src/proxies/mod.rs index 27b3f54..dc891ec 100644 --- a/src/proxies/mod.rs +++ b/src/proxies/mod.rs @@ -11,6 +11,7 @@ use crate::proxies::shooshtime::ShooshtimeProxy; use crate::proxies::spankbang::SpankbangProxy; use crate::proxies::vjav::VjavProxy; use crate::{proxies::sxyprn::SxyprnProxy, util::requester::Requester}; +use crate::proxies::vidara::VidaraProxy; pub mod archivebate; pub mod doodstream; @@ -28,6 +29,7 @@ pub mod pornhubthumb; pub mod shooshtime; pub mod spankbang; pub mod sxyprn; +pub mod vidara; pub mod vjav; #[derive(Debug, Clone)] @@ -44,6 +46,7 @@ pub enum AnyProxy { Hqporner(HqpornerProxy), Heavyfetish(HeavyfetishProxy), Vjav(VjavProxy), + Vidara(VidaraProxy), } pub trait Proxy { @@ -65,6 +68,7 @@ impl Proxy for AnyProxy { AnyProxy::Hqporner(p) => p.get_video_url(url, requester).await, AnyProxy::Heavyfetish(p) => p.get_video_url(url, requester).await, AnyProxy::Vjav(p) => p.get_video_url(url, requester).await, + AnyProxy::Vidara(p) => p.get_video_url(url, requester).await, } } } diff --git a/src/proxies/sxyprn.rs b/src/proxies/sxyprn.rs index cc8774c..cb76ab8 100644 --- a/src/proxies/sxyprn.rs +++ b/src/proxies/sxyprn.rs @@ -70,46 +70,8 @@ impl SxyprnProxy { println!("tmp: {:?}", tmp); let sxyprn_video_url = format!("https://sxyprn.com{}", tmp.join("/")); println!("sxyprn_video_url: {}", sxyprn_video_url); - // let response = requester.get_raw_with_headers(&sxyprn_video_url, vec![ - // ("Accept".to_string(), "*/*".to_string()), - // // ("Accept-Encoding".to_string(), "identity".to_string()), - // // ("Accept-Language".to_string(), "de,en-US;q=0.9,en;q=0.8".to_string()), - // // ("Cache-Control".to_string(), "no-cache".to_string()), - // // ("Connection".to_string(), "keep-alive".to_string()), - // ("Host".to_string(), "sxyprn.com".to_string()), - // // ("Pragma".to_string(), "no-cache".to_string()), - // // ("Priority".to_string(), "u=4".to_string()), - // // ("Range".to_string(), "bytes=0-".to_string()), - // // ("Referer".to_string(), url.clone()), - // // ("Sec-Fetch-Dest".to_string(), "video".to_string()), - // // ("Sec-Fetch-Mode".to_string(), "no-cors".to_string()), - // // ("Sec-Fetch-Site".to_string(), "same-origin".to_string()), - // // ("Sec-GPC".to_string(), "1".to_string()), - // // ("TE".to_string(), "trailers".to_string()), - // ("User-Agent".to_string(), "curl/8.5.0".to_string()) - // ]) - // .await; - // match response { - // Ok(resp) => { - // println!("Response headers: {:?}", resp.headers()); - // println!("Response status: {}", resp.status()); - // return format!( - // "https:{}", - // resp.headers() - // .get("location") - // .unwrap() - // .to_str() - // .unwrap_or("") - // .to_string() - // ); - // } - // Err(e) => { - // println!("Error fetching video URL: {}", e); - // } - // } - match crate::util::get_redirect_location(&sxyprn_video_url) { - Ok(Some(loc)) => {println!("Redirect target found: {}", loc); return format!("https:{}", loc)}, + Ok(Some(loc)) => {return format!("https:{}", loc)}, Ok(None) => println!("No redirect found for {}", sxyprn_video_url), Err(e) => eprintln!("Request failed: {}", e), } diff --git a/src/proxies/vidara.rs b/src/proxies/vidara.rs new file mode 100644 index 0000000..1f2ceff --- /dev/null +++ b/src/proxies/vidara.rs @@ -0,0 +1,124 @@ +use ntex::web; +use url::Url; +use serde_json::json; + +use crate::util::requester::Requester; + +#[derive(Debug, Clone)] +pub struct VidaraProxy {} + +impl VidaraProxy { + pub fn new() -> Self { + VidaraProxy {} + } + + fn normalize_detail_request(endpoint: &str) -> Option<(String, String)> { + let endpoint = endpoint.trim().trim_start_matches('/'); + if endpoint.is_empty() { + return None; + } + + let detail_url = if endpoint.starts_with("http://") || endpoint.starts_with("https://") { + endpoint.to_string() + } else if endpoint.starts_with("vidara.so/") || endpoint.starts_with("www.vidara.so/") + { + format!("https://{endpoint}") + } else { + format!("https://vidara.so/{endpoint}") + }; + + if !Self::is_allowed_detail_url(&detail_url) { + return None; + } + + let parsed = Url::parse(&detail_url).ok()?; + let video_id = parsed.path_segments()? + .last() + .map(ToOwned::to_owned)?; + + Some((detail_url, video_id)) + } + + fn is_allowed_detail_url(url: &str) -> bool { + let Some(parsed) = Url::parse(url).ok() else { + return false; + }; + if parsed.scheme() != "https" { + return false; + } + let Some(host) = parsed.host_str() else { + return false; + }; + (host == "vidara.so" || host == "www.vidara.so") + && (parsed.path().starts_with("/v/")||parsed.path().starts_with("/e/")) + } + + pub async fn get_video_url( + &self, + url: String, + requester: web::types::State, + ) -> String { + let mut requester = requester.get_ref().clone(); + let Some((detail_url, video_id)) = Self::normalize_detail_request(&url) else { + println!("VidaraProxy: Invalid detail URL: {url}"); + return String::new(); + }; + + let body = json!({ + "filecode": video_id, + "device": "web" + }); + // println!("VidaraProxy: Requesting streaming URL for {detail_url} with body: {body}"); + let response = requester + .post_json( + "https://vidara.so/api/stream", + &body, + vec![ + ("Referer".to_string(), detail_url.clone()) + ], + ) + .await; + + // println!("VidaraProxy: Requested streaming URL for {detail_url}, got response: {:?}", response); + + let Ok(response) = response else { + return String::new(); + }; + let Ok(response_text) = response.text().await else { + return String::new(); + }; + + // println!("VidaraProxy: Response text for {detail_url}: {response_text}"); + + let Ok(json): Result = serde_json::from_str(&response_text) else { + return String::new(); + }; + + json["streaming_url"] + .as_str() + .map(ToOwned::to_owned) + .unwrap_or_default() + } +} + +#[cfg(test)] +mod tests { + use super::VidaraProxy; + + #[test] + fn normalizes_detail_request_with_full_url() { + let (url, video_id) = + VidaraProxy::normalize_detail_request("https://vidara.so/v/eJ9O4QqG1Ln2") + .expect("detail request should parse"); + assert_eq!(url, "https://vidara.so/v/eJ9O4QqG1Ln2"); + assert_eq!(video_id, "eJ9O4QqG1Ln2"); + } + + #[test] + fn normalizes_detail_request_with_path_only() { + let (url, video_id) = VidaraProxy::normalize_detail_request("video/1000/demo") + .expect("detail request should parse"); + assert_eq!(url, "https://vidara.so/video/1000/demo"); + assert_eq!(video_id, "1000"); + } +} diff --git a/src/proxy.rs b/src/proxy.rs index 80982e0..24469f9 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -12,6 +12,7 @@ use crate::proxies::shooshtime::ShooshtimeProxy; use crate::proxies::spankbang::SpankbangProxy; use crate::proxies::sxyprn::SxyprnProxy; use crate::proxies::vjav::VjavProxy; +use crate::proxies::vidara::VidaraProxy; use crate::proxies::*; use crate::util::requester::Requester; @@ -71,6 +72,11 @@ pub fn config(cfg: &mut web::ServiceConfig) { .route(web::post().to(proxy2redirect)) .route(web::get().to(proxy2redirect)), ) + .service( + web::resource("/vidara/{endpoint}*") + .route(web::post().to(proxy2redirect)) + .route(web::get().to(proxy2redirect)), + ) .service( web::resource("/shooshtime-media/{endpoint}*") .route(web::post().to(crate::proxies::shooshtime::serve_media)) @@ -139,6 +145,7 @@ fn get_proxy(proxy: &str) -> Option { "vjav" => Some(AnyProxy::Vjav(VjavProxy::new())), "pornhd3x" => Some(AnyProxy::Pornhd3x(Pornhd3xProxy::new())), "shooshtime" => Some(AnyProxy::Shooshtime(ShooshtimeProxy::new())), + "vidara" => Some(AnyProxy::Vidara(VidaraProxy::new())), "pimpbunny" => Some(AnyProxy::Pimpbunny(PimpbunnyProxy::new())), "porndish" => Some(AnyProxy::Porndish(PorndishProxy::new())), "spankbang" => Some(AnyProxy::Spankbang(SpankbangProxy::new())), diff --git a/src/videos.rs b/src/videos.rs index 2b26ffa..282a2c4 100644 --- a/src/videos.rs +++ b/src/videos.rs @@ -157,114 +157,26 @@ impl VideoItem { self.tags = Some(tags); self } - #[cfg(any( - not(hottub_single_provider), - hottub_provider = "hanime", - hottub_provider = "heavyfetish", - hottub_provider = "porndish", - hottub_provider = "shooshtime", - hottub_provider = "spankbang", - hottub_provider = "chaturbate", - hottub_provider = "porn4fans", - hottub_provider = "xfree", - hottub_provider = "pornhub", - ))] pub fn uploader(mut self, uploader: String) -> Self { self.uploader = Some(uploader); self } - #[cfg(any( - not(hottub_single_provider), - hottub_provider = "heavyfetish", - hottub_provider = "porndish", - hottub_provider = "shooshtime", - hottub_provider = "spankbang", - hottub_provider = "chaturbate", - ))] pub fn uploader_url(mut self, uploader_url: String) -> Self { self.uploaderUrl = Some(uploader_url); self } - #[cfg(any( - not(hottub_single_provider), - hottub_provider = "beeg", - hottub_provider = "chaturbate", - hottub_provider = "freepornvideosxxx", - hottub_provider = "hanime", - hottub_provider = "heavyfetish", - hottub_provider = "hentaihaven", - hottub_provider = "hypnotube", - hottub_provider = "javtiful", - hottub_provider = "noodlemagazine", - hottub_provider = "okxxx", - hottub_provider = "omgxxx", - hottub_provider = "perfectgirls", - hottub_provider = "pimpbunny", - hottub_provider = "pmvhaven", - hottub_provider = "porn00", - hottub_provider = "porn4fans", - hottub_provider = "porndish", - hottub_provider = "pornhat", - hottub_provider = "pornhub", - hottub_provider = "redtube", - hottub_provider = "rule34gen", - hottub_provider = "rule34video", - hottub_provider = "shooshtime", - hottub_provider = "spankbang", - hottub_provider = "sxyprn", - hottub_provider = "tnaflix", - hottub_provider = "tokyomotion", - hottub_provider = "viralxxxporn", - hottub_provider = "xfree", - hottub_provider = "xxthots", - hottub_provider = "yesporn", - hottub_provider = "youjizz", - ))] pub fn views(mut self, views: u32) -> Self { self.views = Some(views); self } - #[cfg(any( - not(hottub_single_provider), - hottub_provider = "beeg", - hottub_provider = "hanime", - hottub_provider = "heavyfetish", - hottub_provider = "hsex", - hottub_provider = "porn4fans", - hottub_provider = "shooshtime", - hottub_provider = "spankbang", - hottub_provider = "tokyomotion", - hottub_provider = "vrporn", - hottub_provider = "yesporn", - ))] pub fn rating(mut self, rating: f32) -> Self { self.rating = Some(rating); self } - #[cfg(any( - not(hottub_single_provider), - hottub_provider = "porndish", - hottub_provider = "shooshtime", - hottub_provider = "heavyfetish", - hottub_provider = "xfree", - ))] pub fn uploaded_at(mut self, uploaded_at: u64) -> Self { self.uploadedAt = Some(uploaded_at); self } - #[cfg(any( - not(hottub_single_provider), - hottub_provider = "hanime", - hottub_provider = "heavyfetish", - hottub_provider = "hentaihaven", - hottub_provider = "hqporner", - hottub_provider = "javtiful", - hottub_provider = "noodlemagazine", - hottub_provider = "pimpbunny", - hottub_provider = "pmvhaven", - hottub_provider = "shooshtime", - hottub_provider = "spankbang", - ))] pub fn formats(mut self, formats: Vec) -> Self { if formats.is_empty() { return self; @@ -272,42 +184,11 @@ impl VideoItem { self.formats = Some(formats); self } - #[cfg(any( - not(hottub_single_provider), - hottub_provider = "freepornvideosxxx", - hottub_provider = "heavyfetish", - hottub_provider = "homoxxx", - hottub_provider = "javtiful", - hottub_provider = "missav", - hottub_provider = "okxxx", - hottub_provider = "omgxxx", - hottub_provider = "perfectgirls", - hottub_provider = "pimpbunny", - hottub_provider = "pmvhaven", - hottub_provider = "pornhat", - hottub_provider = "redtube", - hottub_provider = "rule34gen", - hottub_provider = "shooshtime", - hottub_provider = "spankbang", - hottub_provider = "sxyprn", - hottub_provider = "tnaflix", - hottub_provider = "xfree", - hottub_provider = "xxdbx", - hottub_provider = "yesporn", - ))] pub fn preview(mut self, preview: String) -> Self { self.preview = Some(preview); self } - #[cfg(any( - not(hottub_single_provider), - hottub_provider = "hentaihaven", - hottub_provider = "hanime", - hottub_provider = "heavyfetish", - hottub_provider = "paradisehill", - hottub_provider = "xfree", - ))] pub fn aspect_ratio(mut self, aspect_ratio: f32) -> Self { self.aspectRatio = Some(aspect_ratio); self