From 37d11034d82fafd9a3918c391c6737a182a2b8d4 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 Oct 2025 19:28:41 +0000 Subject: [PATCH] pornzog --- src/api.rs | 46 ++++++++++ src/providers/mod.rs | 6 ++ src/providers/pornzog.rs | 189 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 src/providers/pornzog.rs diff --git a/src/api.rs b/src/api.rs index c89be24..db9a604 100644 --- a/src/api.rs +++ b/src/api.rs @@ -310,6 +310,50 @@ async fn status(req: HttpRequest) -> Result { }); } + // pornzog + status.add_channel(Channel { + id: "pornzog".to_string(), + name: "Pornzog".to_string(), + description: "Watch free porn videos at PornZog Free Porn Clips. More than 1 million videos, watch for free now!".to_string(), + premium: false, + favicon: "https://www.google.com/s2/favicons?sz=64&domain=pornzog.com".to_string(), + status: "active".to_string(), + categories: vec![], + options: vec![ChannelOption { + id: "sort".to_string(), + title: "Sort".to_string(), + description: "Sort the Videos".to_string(), //"Sort the videos by Date or Name.".to_string(), + systemImage: "list.number".to_string(), + colorName: "blue".to_string(), + options: vec![ + FilterOption { + id: "recent".to_string(), + title: "Recent".to_string(), + }, + FilterOption { + id: "relevance".to_string(), + title: "Relevance".to_string(), + }, + FilterOption { + id: "viewed".to_string(), + title: "Most Viewed".to_string(), + }, + FilterOption { + id: "rated".to_string(), + title: "Most Rated".to_string(), + }, + FilterOption { + id: "longest".to_string(), + title: "Longest".to_string(), + } + ], + multiSelect: false, + }], + nsfw: true, + cacheDuration: None, + }); + + // Hanime status.add_channel(Channel { id: "hanime".to_string(), name: "Hanime".to_string(), @@ -406,6 +450,7 @@ async fn status(req: HttpRequest) -> Result { //cacheDuration: Some(1800), // }); + // rule34video status.add_channel(Channel { id: "rule34video".to_string(), name: "Rule34Video".to_string(), @@ -1261,6 +1306,7 @@ pub fn get_provider(channel: &str) -> Option { "paradisehill" => Some(AnyProvider::Paradisehill( crate::providers::paradisehill::ParadisehillProvider::new(), )), + "pornzog" => Some(AnyProvider::Pornzog(crate::providers::pornzog::PornzogProvider::new())), _ => Some(AnyProvider::Perverzija(PerverzijaProvider::new())), } } diff --git a/src/providers/mod.rs b/src/providers/mod.rs index fbe12bd..e5fcc94 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -26,6 +26,7 @@ pub mod porn00; pub mod freshporno; pub mod youjizz; pub mod paradisehill; +pub mod pornzog; pub trait Provider { @@ -65,6 +66,7 @@ pub enum AnyProvider { Freshporno(crate::providers::freshporno::FreshpornoProvider), Youjizz(crate::providers::youjizz::YoujizzProvider), Paradisehill(crate::providers::paradisehill::ParadisehillProvider), + Pornzog(crate::providers::pornzog::PornzogProvider), } impl Provider for AnyProvider { @@ -179,6 +181,10 @@ impl Provider for AnyProvider { p.get_videos(cache, pool, sort, query, page, per_page, options,) .await } + AnyProvider::Pornzog(p) => { + p.get_videos(cache, pool, sort, query, page, per_page, options,) + .await + } } } } diff --git a/src/providers/pornzog.rs b/src/providers/pornzog.rs new file mode 100644 index 0000000..ea5071b --- /dev/null +++ b/src/providers/pornzog.rs @@ -0,0 +1,189 @@ +use crate::DbPool; +use crate::providers::Provider; +use crate::util::cache::VideoCache; +use crate::util::parse_abbreviated_number; +use crate::util::time::parse_time_to_seconds; +use crate::videos::{ServerOptions, VideoItem}; +use error_chain::error_chain; +use htmlentity::entity::{ICodedDataTrait, decode}; +use std::vec; + +error_chain! { + foreign_links { + Io(std::io::Error); + HttpRequest(wreq::Error); + } +} + +#[derive(Debug, Clone)] +pub struct PornzogProvider { + url: String, +} +impl PornzogProvider { + pub fn new() -> Self { + PornzogProvider { + url: "https://pornzog.com".to_string(), + } + } + async fn query( + &self, + cache: VideoCache, + page: u8, + query: &str, + sort: String, + options: ServerOptions, + ) -> Result> { + let mut search_params = vec![format!("page={}", page), "site=hdzog".to_string()]; + if !query.is_empty() { + search_params.push(format!("s={}", query.replace(" ", "+"))); + } + let sort_string = match sort.as_str() { + "relevance" => "o=relevance", + "viewed" => "o=viewed", + "rated" => "o=rated", + "longest" => "o=longest", + _ => "o=recent", + }; + search_params.push(format!("{}", &sort_string)); + + let video_url = format!("{}/search/?{}", self.url, search_params.join("&")); + // Check our Video Cache. If the result is younger than 1 hour, we return it. + let old_items = match cache.get(&video_url) { + Some((time, items)) => { + if time.elapsed().unwrap_or_default().as_secs() < 60 * 5 { + return Ok(items.clone()); + } else { + let _ = cache.check().await; + return Ok(items.clone()); + } + } + None => { + vec![] + } + }; + + let mut requester = options.requester.clone().unwrap(); + println!("Fetching URL: {}", video_url); + let text = requester.get(&video_url).await.unwrap(); + let video_items: Vec = self.get_video_items_from_html(text.clone()); + if !video_items.is_empty() { + cache.remove(&video_url); + cache.insert(video_url.clone(), video_items.clone()); + } else { + return Ok(old_items); + } + Ok(video_items) + } + + fn get_video_items_from_html(&self, html: String) -> Vec { + if html.is_empty() { + println!("HTML is empty"); + return vec![]; + } + let mut items: Vec = Vec::new(); + let raw_videos = html.split("class=\"paginator\"").collect::>()[0] + .split("class=\"thumb-video ") + .collect::>()[1..] + .to_vec(); + for video_segment in &raw_videos { + // let vid = video_segment.split("\n").collect::>(); + // for (index, line) in vid.iter().enumerate() { + // println!("Line {}: {}", index, line); + // } + let mut video_url: String = video_segment.split("href=\"").collect::>()[1] + .split("\"") + .collect::>()[0] + .to_string(); + if video_url.starts_with("/") { + video_url = format!("{}{}", self.url, video_url); + } + let mut title = video_segment.split("alt=\"").collect::>()[1] + .split("\"") + .collect::>()[0] + .to_string(); + // html decode + title = decode(title.as_bytes()).to_string().unwrap_or(title); + let id = video_url.split("/").collect::>()[4].to_string(); + + let thumb = format!( + "{}", + video_segment.split(">()[1] + .split("data-original=\"") + .collect::>()[1] + .split("\"") + .collect::>()[0] + .to_string() + ); + let raw_duration = video_segment + .split("class=\"duration\">") + .collect::>()[1] + .split("<") + .collect::>()[0] + .to_string(); + let duration = parse_time_to_seconds(raw_duration.as_str()).unwrap_or(0) as u32; + // let uploader = video_segment.split("class=\"source\">").collect::>()[1] + // .split(">").collect::>()[1] + // .split("<").collect::>()[0] + // .to_string(); + + let tags = video_segment.split("class=\"tags\"").collect::>()[1] + .split("

") + .collect::>()[0] + .split(">()[1..] + .iter() + .map(|el| { + el.split(">").collect::>()[1] + .split("<") + .collect::>()[0] + .to_string() + }) + .collect::>(); + + let video_item = VideoItem::new( + id, + title, + video_url.to_string(), + "pornzog".to_string(), + thumb, + duration, + ) + // .uploader(uploader) + .tags(tags); + items.push(video_item); + } + return items; + } +} + +impl Provider for PornzogProvider { + async fn get_videos( + &self, + cache: VideoCache, + pool: DbPool, + sort: String, + query: Option, + page: String, + per_page: String, + options: ServerOptions, + ) -> Vec { + let _ = per_page; + let _ = pool; + let videos: std::result::Result, Error> = self + .query( + cache, + page.parse::().unwrap_or(1), + query.unwrap_or("".to_string()).as_str(), + sort, + options, + ) + .await; + match videos { + Ok(v) => v, + Err(e) => { + println!("Error fetching videos: {}", e); + vec![] + } + } + } +}