diff --git a/src/api.rs b/src/api.rs index 24f34ce..7dcbe84 100644 --- a/src/api.rs +++ b/src/api.rs @@ -6,6 +6,7 @@ use tokio::task; use crate::providers::all::AllProvider; use crate::providers::hanime::HanimeProvider; +use crate::providers::okporn::OkpornProvider; use crate::providers::perverzija::PerverzijaProvider; use crate::providers::pmvhaven::PmvhavenProvider; use crate::providers::pornhub::PornhubProvider; @@ -492,6 +493,19 @@ async fn status(req: HttpRequest) -> Result { options: vec![], nsfw: true, }); + + status.add_channel(Channel { + id: "okporn".to_string(), + name: "Ok.porn".to_string(), + description: "Tons of HD porno movies".to_string(), + premium: false, + favicon: "https://www.google.com/s2/favicons?sz=64&domain=ok.porn".to_string(), + status: "active".to_string(), + categories: vec![], + options: vec![], + nsfw: true, + }); + status.iconUrl = format!("http://{}/favicon.ico", host).to_string(); Ok(web::HttpResponse::Ok().json(&status)) } @@ -608,7 +622,7 @@ pub fn get_provider(channel: &str) -> Option { "pmvhaven" => Some(AnyProvider::Pmvhaven(PmvhavenProvider::new())), "rule34video" => Some(AnyProvider::Rule34video(Rule34videoProvider::new())), "redtube" => Some(AnyProvider::Redtube(RedtubeProvider::new())), - + "okporn" => Some(AnyProvider::Okporn(OkpornProvider::new())), _ => Some(AnyProvider::Perverzija(PerverzijaProvider::new())), } } diff --git a/src/providers/all.rs b/src/providers/all.rs index 4ffd987..2d8cb9e 100644 --- a/src/providers/all.rs +++ b/src/providers/all.rs @@ -1,4 +1,4 @@ -use std::vec; +use std::{fs, vec}; use error_chain::error_chain; use futures::future::join_all; use serde_json::error::Category; @@ -44,7 +44,13 @@ impl Provider for AllProvider { ) -> Vec { let mut sites_str = options.clone().sites.unwrap(); if sites_str.is_empty() { - sites_str = "perverzija,hanime,spankbang,pmvhaven,redtube,pornhub,rule34video".to_string(); + let files = fs::read_dir("./src/providers").unwrap(); + let providers = files.map(|entry| entry.unwrap().file_name()) + .filter(|name| name.to_str().unwrap().ends_with(".rs")) + .filter(|name| !name.to_str().unwrap().contains("mod.rs") && !name.to_str().unwrap().contains("all.rs")) + .map(|name| name.to_str().unwrap().replace(".rs", "")) + .collect::>(); + sites_str = providers.join(","); } let sites = sites_str .split(',') diff --git a/src/providers/mod.rs b/src/providers/mod.rs index cd84ff4..14b2d27 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -1,6 +1,6 @@ use crate::{ providers::{ - all::AllProvider, hanime::HanimeProvider, perverzija::PerverzijaProvider, pmvhaven::PmvhavenProvider, pornhub::PornhubProvider, redtube::RedtubeProvider, rule34video::Rule34videoProvider, spankbang::SpankbangProvider + all::AllProvider, hanime::HanimeProvider, okporn::OkpornProvider, perverzija::PerverzijaProvider, pmvhaven::PmvhavenProvider, pornhub::PornhubProvider, redtube::RedtubeProvider, rule34video::Rule34videoProvider, spankbang::SpankbangProvider }, util::cache::VideoCache, videos::{ServerOptions, VideoItem}, DbPool }; @@ -12,6 +12,7 @@ pub mod pornhub; pub mod spankbang; pub mod rule34video; pub mod redtube; +pub mod okporn; pub trait Provider { async fn get_videos( @@ -35,7 +36,8 @@ pub enum AnyProvider { Pornhub(PornhubProvider), Pmvhaven(PmvhavenProvider), Rule34video(Rule34videoProvider), - Redtube(RedtubeProvider), // Assuming Redtube is similar to Rule34video + Redtube(RedtubeProvider), + Okporn(OkpornProvider), } impl Provider for AnyProvider { @@ -94,6 +96,10 @@ impl Provider for AnyProvider { p.get_videos(cache, pool, sort, query, page, per_page, options,) .await } + AnyProvider::Okporn(p) => { + p.get_videos(cache, pool, sort, query, page, per_page, options,) + .await + } } } } diff --git a/src/providers/okporn.rs b/src/providers/okporn.rs new file mode 100644 index 0000000..354bca0 --- /dev/null +++ b/src/providers/okporn.rs @@ -0,0 +1,266 @@ +use crate::schema::videos::url; +use crate::util::parse_abbreviated_number; +use crate::DbPool; +use crate::providers::Provider; +use crate::util::cache::VideoCache; +use crate::util::flaresolverr::{FlareSolverrRequest, Flaresolverr}; +use crate::util::time::parse_time_to_seconds; +use crate::videos::{ServerOptions, VideoItem}; +use error_chain::error_chain; +use futures::stream::SplitSink; +use htmlentity::entity::{ICodedDataTrait, decode}; +use std::env; +use std::vec; +use wreq::{Client, Proxy}; +use wreq_util::Emulation; + +error_chain! { + foreign_links { + Io(std::io::Error); + HttpRequest(wreq::Error); + } +} + +#[derive(Debug, Clone)] +pub struct OkpornProvider { + url: String, +} +impl OkpornProvider { + pub fn new() -> Self { + OkpornProvider { + url: "https://ok.porn".to_string(), + } + } + async fn get( + &self, + cache: VideoCache, + page: u8, + sort: &str, + ) -> Result> { + let video_url = format!("{}/{}/", self.url, page); + let old_items = match cache.get(&video_url) { + Some((time, items)) => { + if time.elapsed().unwrap_or_default().as_secs() < 60 * 5 { + println!("Cache hit for URL: {}", video_url); + return Ok(items.clone()); + } else { + items.clone() + } + } + None => { + vec![] + } + }; + + let proxy = Proxy::all("http://192.168.0.103:8081").unwrap(); + let client = Client::builder().cert_verification(false).emulation(Emulation::Firefox136).build()?; + + let mut response = client.get(video_url.clone()) + .proxy(proxy.clone()) + .send().await?; + if response.status().is_redirection(){ + + response = client.get(self.url.clone() + response.headers()["Location"].to_str().unwrap()) + // .proxy(proxy) + .send().await?; + } + if response.status().is_success() { + let text = response.text().await?; + 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) + } else { + let flare_url = env::var("FLARE_URL").expect("FLARE_URL not set"); + let flare = Flaresolverr::new(flare_url); + let result = flare + .solve(FlareSolverrRequest { + cmd: "request.get".to_string(), + url: video_url.clone(), + maxTimeout: 60000, + }) + .await; + let video_items = match result { + Ok(res) => { + // println!("FlareSolverr response: {}", res); + self.get_video_items_from_html(res.solution.response) + } + Err(e) => { + println!("Error solving FlareSolverr: {}", e); + return Err("Failed to solve FlareSolverr".into()); + } + }; + 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) + } + } + async fn query( + &self, + cache: VideoCache, + page: u8, + query: &str, + ) -> Result> { + let search_string = query.to_lowercase().trim().replace(" ", "-"); + let video_url = format!("{}/search/{}/{}/", self.url, search_string, page); + + // 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 proxy = Proxy::all("http://192.168.0.103:8081").unwrap(); + let client = Client::builder().cert_verification(false).emulation(Emulation::Firefox136).build()?; + + let mut response = client.get(video_url.clone()) + .proxy(proxy.clone()) + .send().await?; + + if response.status().is_redirection(){ + + response = client.get(self.url.clone() + response.headers()["Location"].to_str().unwrap()) + .proxy(proxy) + .send().await?; + } + + if response.status().is_success() { + let text = response.text().await?; + 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) + } else { + let flare_url = env::var("FLARE_URL").expect("FLARE_URL not set"); + let flare = Flaresolverr::new(flare_url); + let result = flare + .solve(FlareSolverrRequest { + cmd: "request.get".to_string(), + url: video_url.clone(), + maxTimeout: 60000, + }) + .await; + let video_items = match result { + Ok(res) => self.get_video_items_from_html(res.solution.response), + Err(e) => { + println!("Error solving FlareSolverr: {}", e); + return Err("Failed to solve FlareSolverr".into()); + } + }; + 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("
>()[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 video_url: String = format!("{}{}", self.url, video_segment.split(">()[1] + .split("\"") + .collect::>()[0]); + let mut title = video_segment.split("\" title=\"").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 raw_duration = video_segment.split("").collect::>()[1] + .split("<") + .collect::>()[0] + .to_string(); + let duration = parse_time_to_seconds(&raw_duration).unwrap_or(0) as u32; + + let thumb = video_segment.split(">()[1].split("data-original=\"").collect::>()[1] + .split("\"") + .collect::>()[0] + .to_string(); + + let video_item = VideoItem::new( + id, + title, + video_url.to_string(), + "okporn".to_string(), + thumb, + duration, + ) + ; + items.push(video_item); + } + return items; + } + + +} + +impl Provider for OkpornProvider { + async fn get_videos( + &self, + cache: VideoCache, + pool: DbPool, + sort: String, + query: Option, + page: String, + per_page: String, + options: ServerOptions, + ) -> Vec { + let _ = options; + let _ = per_page; + let _ = pool; + let videos: std::result::Result, Error> = match query { + Some(q) => { + self.query(cache, page.parse::().unwrap_or(1), &q,) + .await + } + None => { + self.get(cache, page.parse::().unwrap_or(1), &sort) + .await + } + }; + match videos { + Ok(v) => v, + Err(e) => { + println!("Error fetching videos: {}", e); + vec![] + } + } + } +} diff --git a/src/providers/redtube.rs b/src/providers/redtube.rs index dd922f5..aebf90b 100644 --- a/src/providers/redtube.rs +++ b/src/providers/redtube.rs @@ -135,7 +135,7 @@ impl RedtubeProvider { let client = Client::builder().cert_verification(false).emulation(Emulation::Firefox136).build()?; let mut response = client.get(video_url.clone()) - .proxy(proxy.clone()) + //.proxy(proxy.clone()) .send().await?; if response.status().is_redirection(){ diff --git a/src/providers/spankbang.rs b/src/providers/spankbang.rs index 1d26262..0f3091a 100644 --- a/src/providers/spankbang.rs +++ b/src/providers/spankbang.rs @@ -336,7 +336,6 @@ impl SpankbangProvider { return items; } let raw_videos = raw_videos_vec[1..].to_vec(); - println!("Found {} video items", raw_videos.len()); let futures = raw_videos.into_iter().map(|el| self.parse_video_item(el.to_string(), client, cookies.clone(), pool.clone())); let results: Vec> = join_all(futures).await; let video_items: Vec = results