diff --git a/Cargo.toml b/Cargo.toml index c0b49d3..f200bc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +cute = "0.3.0" diesel = { version = "2.2.10", features = ["sqlite", "r2d2"] } dotenvy = "0.15.7" env_logger = "0.11.8" @@ -16,4 +17,4 @@ serde = "1.0.219" serde_json = "1.0.140" tokio = { version = "1", features = ["full"] } wreq = { version = "5", features = ["full"] } -wreq-util = "2" \ No newline at end of file +wreq-util = "2" diff --git a/src/api.rs b/src/api.rs index 8a1477a..78fa3f2 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,31 +1,30 @@ -use std::cmp::Ordering; use ntex::http::header; use ntex::web; use ntex::web::HttpRequest; -use tokio::{task}; +use std::cmp::Ordering; +use tokio::task; use crate::providers::hanime::HanimeProvider; use crate::providers::perverzija::PerverzijaProvider; +use crate::providers::pmvhaven::PmvhavenProvider; use crate::providers::pornhub::PornhubProvider; use crate::providers::spankbang::SpankbangProvider; use crate::util::cache::VideoCache; -use crate::{providers::*, status::*, videos::*, DbPool}; +use crate::{DbPool, providers::*, status::*, videos::*}; #[derive(Debug)] -struct ClientVersion{ +struct ClientVersion { version: u32, subversion: u32, name: String, } - impl ClientVersion { - - pub fn new(version: u32, subversion: u32, name: String) -> ClientVersion{ - ClientVersion{ + pub fn new(version: u32, subversion: u32, name: String) -> ClientVersion { + ClientVersion { version, subversion, - name + name, } } @@ -37,20 +36,21 @@ impl ClientVersion { let name = name_version[1]; // Extract version and optional subversion - let (version, subversion) = if let Some((v, c)) = name.split_at(name.len().saturating_sub(1)).into() { - match v.parse::() { - Ok(ver) => (ver, c.chars().next().map(|ch| ch as u32).unwrap_or(0)), - Err(_) => { - // Try parsing whole string if no subversion exists - match name.parse::() { - Ok(ver) => (ver, 0), - Err(_) => return None, + let (version, subversion) = + if let Some((v, c)) = name.split_at(name.len().saturating_sub(1)).into() { + match v.parse::() { + Ok(ver) => (ver, c.chars().next().map(|ch| ch as u32).unwrap_or(0)), + Err(_) => { + // Try parsing whole string if no subversion exists + match name.parse::() { + Ok(ver) => (ver, 0), + Err(_) => return None, + } } } - } - } else { - return None; - }; + } else { + return None; + }; return Some(ClientVersion { version: version, @@ -99,16 +99,15 @@ pub fn config(cfg: &mut web::ServiceConfig) { } async fn status(req: HttpRequest) -> Result { - - let clientversion: ClientVersion = match req.headers().get("User-Agent"){ - Some(v) => match v.to_str(){ - Ok(useragent) => ClientVersion::parse(useragent).unwrap_or_else(|| ClientVersion::new(999, 0, "999".to_string())), - Err(_) => ClientVersion::new(999, 0, "999".to_string()) + let clientversion: ClientVersion = match req.headers().get("User-Agent") { + Some(v) => match v.to_str() { + Ok(useragent) => ClientVersion::parse(useragent) + .unwrap_or_else(|| ClientVersion::new(999, 0, "999".to_string())), + Err(_) => ClientVersion::new(999, 0, "999".to_string()), }, - _=> ClientVersion::new(999, 0, "999".to_string()) + _ => ClientVersion::new(999, 0, "999".to_string()), }; - let host = req .headers() .get(header::HOST) @@ -116,7 +115,7 @@ async fn status(req: HttpRequest) -> Result { .unwrap_or_default() .to_string(); let mut status = Status::new(); - status.add_channel(Channel { + status.add_channel(Channel { id: "pornhub".to_string(), name: "Pornhub".to_string(), description: "Pornhub Free Videos".to_string(), @@ -127,14 +126,26 @@ async fn status(req: HttpRequest) -> Result { options: vec![], nsfw: true, }); - if clientversion >= ClientVersion::new(22,97,"22a".to_string()){ + status.add_channel(Channel { + id: "pmvhaven".to_string(), + name: "Pmvhaven".to_string(), + description: "Explore a curated collection of captivating PMV".to_string(), + premium: false, + favicon: "https://www.google.com/s2/favicons?sz=64&domain=pmvhaven.com".to_string(), + status: "active".to_string(), + categories: vec![], + options: vec![], + nsfw: true, + }); + if clientversion >= ClientVersion::new(22, 97, "22a".to_string()) { //add perverzija status.add_channel(Channel { id: "perverzija".to_string(), name: "Perverzija".to_string(), description: "Free videos from Perverzija".to_string(), premium: false, - favicon: "https://www.google.com/s2/favicons?sz=64&domain=tube.perverzija.com".to_string(), + favicon: "https://www.google.com/s2/favicons?sz=64&domain=tube.perverzija.com" + .to_string(), status: "active".to_string(), categories: vec![], options: vec![ @@ -204,58 +215,56 @@ async fn status(req: HttpRequest) -> Result { favicon: "https://www.google.com/s2/favicons?sz=64&domain=hanime.tv".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: "created_at_unix.desc".to_string(), - title: "Recent Upload".to_string(), - }, - FilterOption { - id: "created_at_unix.asc".to_string(), - title: "Old Upload".to_string(), - }, - FilterOption { - id: "views.desc".to_string(), - title: "Most Views".to_string(), - }, - FilterOption { - id: "views.asc".to_string(), - title: "Least Views".to_string(), - }, - FilterOption { - id: "likes.desc".to_string(), - title: "Most Likes".to_string(), - }, - FilterOption { - id: "likes.asc".to_string(), - title: "Least Likes".to_string(), - }, - FilterOption { - id: "released_at_unix.desc".to_string(), - title: "New".to_string(), - }, - FilterOption { - id: "released_at_unix.asc".to_string(), - title: "Old".to_string(), - }, - FilterOption { - id: "title_sortable.asc".to_string(), - title: "A - Z".to_string(), - }, - FilterOption { - id: "title_sortable.desc".to_string(), - title: "Z - A".to_string(), - }, - ], - multiSelect: false, - } - ], + 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: "created_at_unix.desc".to_string(), + title: "Recent Upload".to_string(), + }, + FilterOption { + id: "created_at_unix.asc".to_string(), + title: "Old Upload".to_string(), + }, + FilterOption { + id: "views.desc".to_string(), + title: "Most Views".to_string(), + }, + FilterOption { + id: "views.asc".to_string(), + title: "Least Views".to_string(), + }, + FilterOption { + id: "likes.desc".to_string(), + title: "Most Likes".to_string(), + }, + FilterOption { + id: "likes.asc".to_string(), + title: "Least Likes".to_string(), + }, + FilterOption { + id: "released_at_unix.desc".to_string(), + title: "New".to_string(), + }, + FilterOption { + id: "released_at_unix.asc".to_string(), + title: "Old".to_string(), + }, + FilterOption { + id: "title_sortable.asc".to_string(), + title: "A - Z".to_string(), + }, + FilterOption { + id: "title_sortable.desc".to_string(), + title: "Z - A".to_string(), + }, + ], + multiSelect: false, + }], nsfw: true, }); status.add_channel(Channel { @@ -266,29 +275,28 @@ async fn status(req: HttpRequest) -> Result { favicon: "https://www.google.com/s2/favicons?sz=64&domain=spankbang.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: "trending_videos".to_string(), - title: "Trending".to_string(), - }, - FilterOption { - id: "new_videos".to_string(), - title: "New".to_string(), - }, - FilterOption { - id: "most_popular".to_string(), - title: "Popular".to_string(), - }], - multiSelect: false, - } - ], + 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: "trending_videos".to_string(), + title: "Trending".to_string(), + }, + FilterOption { + id: "new_videos".to_string(), + title: "New".to_string(), + }, + FilterOption { + id: "most_popular".to_string(), + title: "Popular".to_string(), + }, + ], + multiSelect: false, + }], nsfw: true, }); status.iconUrl = format!("http://{}/favicon.ico", host).to_string(); @@ -331,11 +339,31 @@ async fn videos_post( .to_string() .parse() .unwrap(); - let featured = video_request.featured.as_deref().unwrap_or("all").to_string(); + let featured = video_request + .featured + .as_deref() + .unwrap_or("all") + .to_string(); let provider = get_provider(channel.as_str()) .ok_or_else(|| web::error::ErrorBadRequest("Invalid channel".to_string()))?; + let category = video_request + .category + .as_deref() + .unwrap_or("all") + .to_string(); + let video_items = provider - .get_videos(cache.get_ref().clone(), pool.get_ref().clone(), channel.clone(), sort.clone(), query.clone(), page.to_string(), perPage.to_string(), featured.clone()) + .get_videos( + cache.get_ref().clone(), + pool.get_ref().clone(), + channel.clone(), + sort.clone(), + query.clone(), + page.to_string(), + perPage.to_string(), + featured.clone(), + category.clone() + ) .await; videos.items = video_items.clone(); if video_items.len() == 0 { @@ -344,7 +372,7 @@ async fn videos_post( resultsPerPage: 10, } } -//### + //### let next_page = page.to_string().parse::().unwrap_or(1) + 1; let provider_clone = provider.clone(); let cache_clone = cache.get_ref().clone(); @@ -354,10 +382,11 @@ async fn videos_post( let query_clone = query.clone(); let per_page_clone = perPage.to_string(); let featured_clone = featured.clone(); + let category_clone = category.clone(); task::spawn_local(async move { // if let AnyProvider::Spankbang(_) = provider_clone { // // Spankbang has a delay for the next page - // ntex::time::sleep(ntex::time::Seconds(80)).await; + // ntex::time::sleep(ntex::time::Seconds(80)).await; // } let _ = provider_clone .get_videos( @@ -369,10 +398,11 @@ async fn videos_post( next_page.to_string(), per_page_clone, featured_clone, + category_clone, ) .await; }); -//### + //### Ok(web::HttpResponse::Ok().json(&videos)) } @@ -382,6 +412,7 @@ pub fn get_provider(channel: &str) -> Option { "hanime" => Some(AnyProvider::Hanime(HanimeProvider::new())), "spankbang" => Some(AnyProvider::Spankbang(SpankbangProvider::new())), "pornhub" => Some(AnyProvider::Pornhub(PornhubProvider::new())), + "pmvhaven" => Some(AnyProvider::Pmvhaven(PmvhavenProvider::new())), _ => Some(AnyProvider::Perverzija(PerverzijaProvider::new())), } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 856d49d..185f5db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![warn(unused_extern_crates)] #![allow(non_snake_case)] + use diesel::{r2d2::{self, ConnectionManager}, SqliteConnection}; use dotenvy::dotenv; use ntex_files as fs; diff --git a/src/providers/hanime.rs b/src/providers/hanime.rs index 57dcb37..21f8c79 100644 --- a/src/providers/hanime.rs +++ b/src/providers/hanime.rs @@ -1,6 +1,7 @@ use std::vec; use error_chain::error_chain; use futures::future::join_all; +use serde_json::error::Category; use wreq::Client; use wreq_util::Emulation; use crate::db; @@ -264,7 +265,9 @@ impl Provider for HanimeProvider { page: String, per_page: String, featured: String, + category: String, ) -> Vec { + let _ = category; let _ = featured; let _ = per_page; let _ = sort; diff --git a/src/providers/mod.rs b/src/providers/mod.rs index c7a4886..fa2aed6 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -1,12 +1,32 @@ -use crate::{providers::{hanime::HanimeProvider, perverzija::PerverzijaProvider, pornhub::PornhubProvider, spankbang::SpankbangProvider}, util::cache::VideoCache, videos::VideoItem, DbPool}; +use crate::{ + DbPool, + providers::{ + hanime::HanimeProvider, perverzija::PerverzijaProvider, pmvhaven::PmvhavenProvider, + pornhub::PornhubProvider, spankbang::SpankbangProvider, + }, + util::cache::VideoCache, + videos::VideoItem, +}; -pub mod perverzija; pub mod hanime; -pub mod spankbang; +pub mod perverzija; +pub mod pmvhaven; pub mod pornhub; +pub mod spankbang; -pub trait Provider{ - async fn get_videos(&self, cache: VideoCache, pool: DbPool, channel: String, sort: String, query: Option, page: String, per_page: String, featured: String) -> Vec; +pub trait Provider { + async fn get_videos( + &self, + cache: VideoCache, + pool: DbPool, + channel: String, + sort: String, + query: Option, + page: String, + per_page: String, + featured: String, + category: String, + ) -> Vec; } #[derive(Debug, Clone)] @@ -15,18 +35,60 @@ pub enum AnyProvider { Hanime(HanimeProvider), Spankbang(SpankbangProvider), Pornhub(PornhubProvider), + Pmvhaven(PmvhavenProvider), } impl Provider for AnyProvider { - async fn get_videos(&self, cache: VideoCache, pool:DbPool, channel: String, sort: String, query: Option, page: String, per_page: String, featured: String) -> Vec { + async fn get_videos( + &self, + cache: VideoCache, + pool: DbPool, + channel: String, + sort: String, + query: Option, + page: String, + per_page: String, + featured: String, + category: String, + ) -> Vec { println!( "/api/videos: channel={:?}, sort={:?}, query={:?}, page={:?}, per_page={:?}, featured={:?}", channel, sort, query, page, per_page, featured ); match self { - AnyProvider::Perverzija(p) => p.get_videos(cache.clone(), pool.clone(), channel.clone(), sort.clone(), query.clone(), page.clone(), per_page.clone(), featured.clone()).await, - AnyProvider::Hanime(p) => p.get_videos(cache, pool, channel, sort, query, page, per_page, featured).await, - AnyProvider::Spankbang(p) => p.get_videos(cache, pool, channel, sort, query, page, per_page, featured).await, - AnyProvider::Pornhub(p) => p.get_videos(cache, pool, channel, sort, query, page, per_page, featured).await, + AnyProvider::Perverzija(p) => { + p.get_videos( + cache.clone(), + pool.clone(), + channel.clone(), + sort.clone(), + query.clone(), + page.clone(), + per_page.clone(), + featured.clone(), + category.clone(), + ) + .await + } + AnyProvider::Hanime(p) => { + p.get_videos(cache, pool, channel, sort, query, page, per_page, featured, + category.clone(),) + .await + } + AnyProvider::Spankbang(p) => { + p.get_videos(cache, pool, channel, sort, query, page, per_page, featured, + category.clone(),) + .await + } + AnyProvider::Pornhub(p) => { + p.get_videos(cache, pool, channel, sort, query, page, per_page, featured, + category.clone(),) + .await + } + AnyProvider::Pmvhaven(p) => { + p.get_videos(cache, pool, channel, sort, query, page, per_page, featured, + category.clone(),) + .await + } } } } diff --git a/src/providers/perverzija.rs b/src/providers/perverzija.rs index c5080cd..66abb8c 100644 --- a/src/providers/perverzija.rs +++ b/src/providers/perverzija.rs @@ -452,7 +452,9 @@ impl Provider for PerverzijaProvider { page: String, per_page: String, featured: String, + category: String, ) -> Vec { + let _ = category; let _ = per_page; let _ = sort; let videos: std::result::Result, Error> = match query { diff --git a/src/providers/pmvhaven.rs b/src/providers/pmvhaven.rs new file mode 100644 index 0000000..b77a0f5 --- /dev/null +++ b/src/providers/pmvhaven.rs @@ -0,0 +1,422 @@ +use crate::DbPool; +use crate::providers::Provider; +use crate::schema::videos; +use crate::util::cache::VideoCache; +use crate::util::flaresolverr::{FlareSolverrRequest, Flaresolverr}; +use crate::util::parse_abbreviated_number; +use crate::util::time::parse_time_to_seconds; +use crate::videos::VideoItem; +use cute::c; +use error_chain::error_chain; +use htmlentity::entity::{ICodedDataTrait, decode}; +use std::env; +use std::vec; +use wreq::{Client, Proxy}; +use wreq_util::Emulation; + +#[macro_use(c)] + +error_chain! { + foreign_links { + Io(std::io::Error); + HttpRequest(wreq::Error); + } +} + +#[derive(serde::Serialize)] +struct PmvhavenRequest { + all: bool, //true, + pmv: bool, //false, + hmv: bool, //false, + hypno: bool, //false, + tiktok: bool, //false, + koreanbj: bool, //false, + other: bool, // false, + explicitContent: Option, //null, + sameSexContent: Option, //null, + seizureWarning: Option, //null, + tags: Vec, //[], + music: Vec, //[], + stars: Vec, //[], + creators: Vec, //[], + range: Vec, //[0,40], + activeTime: String, //"All time", + activeQuality: String, //"Quality", + aspectRatio: String, //"Aspect Ratio", + activeView: String, //"Newest", + index: u32, //2, + hideUntagged: bool, //true, + showSubscriptionsOnly: bool, //false, + query: String, //"no", + profile: Option, //null +} + +impl PmvhavenRequest { + pub fn new(page: u32) -> Self { + PmvhavenRequest { + all: true, + pmv: false, + hmv: false, + hypno: false, + tiktok: false, + koreanbj: false, + other: false, + explicitContent: None, + sameSexContent: None, + seizureWarning: None, + tags: vec![], + music: vec![], + stars: vec![], + creators: vec![], + range: vec![0, 40], + activeTime: "All time".to_string(), + activeQuality: "Quality".to_string(), + aspectRatio: "Aspect Ratio".to_string(), + activeView: "Newest".to_string(), + index: page, + hideUntagged: true, + showSubscriptionsOnly: false, + query: "no".to_string(), + profile: None, + } + } + fn hypno(&mut self) -> &mut Self { + self.all = false; + self.pmv = false; + self.hmv = false; + self.tiktok = false; + self.koreanbj = false; + self.other = false; + self.hypno = true; + self + } + fn pmv(&mut self) -> &mut Self { + self.all = false; + self.pmv = true; + self.hmv = false; + self.tiktok = false; + self.koreanbj = false; + self.other = false; + self.hypno = false; + self + } + fn hmv(&mut self) -> &mut Self { + self.all = false; + self.pmv = false; + self.hmv = true; + self.tiktok = false; + self.koreanbj = false; + self.other = false; + self.hypno = false; + self + } + fn tiktok(&mut self) -> &mut Self { + self.all = false; + self.pmv = false; + self.hmv = false; + self.tiktok = true; + self.koreanbj = false; + self.other = false; + self.hypno = false; + self + } + fn koreanbj(&mut self) -> &mut Self { + self.all = false; + self.pmv = false; + self.hmv = false; + self.tiktok = false; + self.koreanbj = true; + self.other = false; + self.hypno = false; + self + } + fn other(&mut self) -> &mut Self { + self.all = false; + self.pmv = false; + self.hmv = false; + self.tiktok = false; + self.koreanbj = false; + self.other = true; + self.hypno = false; + self + } +} + +#[derive(serde::Serialize)] +struct PmvhavenSearch { + mode: String, //"DefaultMoreSearch", + data: String, //"pmv", + index: u32, +} + +impl PmvhavenSearch { + fn new(search: String, page: u32) -> PmvhavenSearch { + PmvhavenSearch { + mode: "DefaultMoreSearch".to_string(), + data: search, + index: page, + } + } +} + +#[derive(serde::Deserialize)] +struct PmvhavenVideo { + title: String, //JAV Addiction Therapy", + uploader: Option, //itonlygetsworse", + duration: f32, //259.093333, + width: Option, //3840", + height: Option, //2160", + ratio: Option, //50, + thumbnails: Vec>, //[ + // "placeholder", + // "https://storage.pmvhaven.com/686f24e96f7124f3dfbe90ab/thumbnail/JAV Addiction Therapy_686f24e96f7124f3dfbe90ab.png", + // "https://storage.pmvhaven.com/686f24e96f7124f3dfbe90ab/thumbnail/webp320_686f24e96f7124f3dfbe90ab.webp" + // ], + views: u32, //1971, + url: Option, //https://storage.pmvhaven.com/686f24e96f7124f3dfbe90ab/JAV Addiction Therapy_686f24e96f7124f3dfbe90ab.mp4", + previewUrlCompressed: Option, //https://storage.pmvhaven.com/686f24e96f7124f3dfbe90ab/videoPreview/comus_686f24e96f7124f3dfbe90ab.mp4", + seizureWarning: Option, //false, + isoDate: Option, //2025-07-10T02:52:26.000Z", + gayContent: Option, //false, + transContent: Option, //false, + creator: Option, //itonlygetsworse", + _id: String, //686f2aeade2062f93d72931f", + totalRaters: Option, //42, + rating: Option, //164 +} + +impl PmvhavenVideo { + fn to_videoitem(self) -> VideoItem { + let mut item = VideoItem::new( + self._id.clone(), + self.title.clone(), + format!("https://pmvhaven.com/video/{}_{}", self.title.replace(" ","-"), self._id), + "pmvhaven".to_string(), + self.thumbnails[self.thumbnails.len()-1].clone().unwrap_or("".to_string()), + self.duration as u32, + ) + .views(self.views); + item = match self.creator{ + Some(c) => item.uploader(c), + _ => item, + }; + item = match self.previewUrlCompressed{ + Some(u) => item.preview(u), + _ => item, + }; + + return item; + } +} + +#[derive(serde::Deserialize)] +struct PmvhavenResponse { + httpStatusCode: Option, + data: Vec, + count: Option, +} + +impl PmvhavenResponse { + fn to_videoitems(self) -> Vec { + return c![video.to_videoitem(), for video in self.data]; + } +} + +#[derive(Debug, Clone)] +pub struct PmvhavenProvider { + url: String, +} +impl PmvhavenProvider { + pub fn new() -> Self { + PmvhavenProvider { + url: "https://pmvhaven.com".to_string(), + } + } + async fn get(&self, cache: VideoCache, page: u8, category: String) -> Result> { + let url = format!("{}/api/getmorevideos", self.url); + let request = PmvhavenRequest::new(page as u32); + let old_items = match cache.get(&url) { + Some((time, items)) => { + if time.elapsed().unwrap_or_default().as_secs() < 60 * 5 { + println!("Cache hit for URL: {}", url); + return Ok(items.clone()); + } else { + items.clone() + } + } + None => { + vec![] + } + }; + + let proxy = Proxy::all("http://192.168.0.101:8080").unwrap(); + let client = Client::builder() + .cert_verification(false) + .emulation(Emulation::Firefox136) + .build()?; + + let response = client + .post(url.clone()) + .proxy(proxy) + .json(&request) + .header("Content-Type", "text/plain;charset=UTF-8") + .send() + .await?; + if response.status().is_success() { + let videos = match response.json::().await { + Ok(resp) => resp, + Err(e) => { + println!("Failed to parse PmvhavenResponse: {}", e); + return Ok(old_items); + } + }; + let video_items: Vec = videos.to_videoitems(); + if !video_items.is_empty() { + cache.remove(&url); + cache.insert(url.clone(), video_items.clone()); + } else { + return Ok(old_items); + } + return 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: 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(&url); + // cache.insert(url.clone(), video_items.clone()); + // } else { + // return Ok(old_items); + // } + // Ok(video_items) + // } + Err("Failed to get Videos".into()) + } + async fn query(&self, cache: VideoCache, page: u8, query: &str) -> Result> { + let url = format!("{}/api/v2/search", self.url); + let request = PmvhavenSearch::new(query.to_string(),page as u32); + // Check our Video Cache. If the result is younger than 1 hour, we return it. + let old_items = match cache.get(&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.101:8080").unwrap(); + let client = Client::builder() + .cert_verification(false) + .emulation(Emulation::Firefox136) + .build()?; + + let response = client + .post(url.clone()) + .proxy(proxy) + .json(&request) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .send() + .await?; + if response.status().is_success() { + let videos = match response.json::().await { + Ok(resp) => resp, + Err(e) => { + println!("Failed to parse PmvhavenResponse: {}", e); + return Ok(old_items); + } + }; + let video_items: Vec = videos.to_videoitems(); + if !video_items.is_empty() { + cache.remove(&url); + cache.insert(url.clone(), video_items.clone()); + } else { + return Ok(old_items); + } + return 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: 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(&url); + // cache.insert(url.clone(), video_items.clone()); + // } else { + // return Ok(old_items); + // } + // Ok(video_items) + // } + Err("Failed to query Videos".into()) + } +} + +impl Provider for PmvhavenProvider { + async fn get_videos( + &self, + cache: VideoCache, + pool: DbPool, + _channel: String, + sort: String, + query: Option, + page: String, + per_page: String, + featured: String, + category: String, + ) -> Vec { + let _ = per_page; + let _ = sort; + let _ = featured; // Ignored in this implementation + let _ = pool; // Ignored in this implementation + 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), category) + .await + } + }; + match videos { + Ok(v) => v, + Err(e) => { + println!("Error fetching videos: {}", e); + vec![] + } + } + } +} diff --git a/src/providers/pornhub.rs b/src/providers/pornhub.rs index 5a02155..8e8693b 100644 --- a/src/providers/pornhub.rs +++ b/src/providers/pornhub.rs @@ -41,7 +41,7 @@ impl PornhubProvider { let old_items = match cache.get(&url) { Some((time, items)) => { - if time.elapsed().unwrap_or_default().as_secs() < 60 * 60 { + if time.elapsed().unwrap_or_default().as_secs() < 60 * 5 { println!("Cache hit for URL: {}", url); return Ok(items.clone()); } else { @@ -110,7 +110,7 @@ impl PornhubProvider { // Check our Video Cache. If the result is younger than 1 hour, we return it. let old_items = match cache.get(&url) { Some((time, items)) => { - if time.elapsed().unwrap_or_default().as_secs() < 60 * 60 { + if time.elapsed().unwrap_or_default().as_secs() < 60 * 5 { return Ok(items.clone()); } else { let _ = cache.check().await; @@ -251,7 +251,9 @@ impl Provider for PornhubProvider { page: String, per_page: String, featured: String, + category: String, ) -> Vec { + let _ = category; let _ = per_page; let _ = sort; let _ = featured; // Ignored in this implementation diff --git a/src/providers/spankbang.rs b/src/providers/spankbang.rs index b48efb8..212f20a 100644 --- a/src/providers/spankbang.rs +++ b/src/providers/spankbang.rs @@ -358,7 +358,9 @@ impl Provider for SpankbangProvider { page: String, per_page: String, featured: String, + category: String, ) -> Vec { + let _ = category; let _ = per_page; let _ = featured; let _ = pool; diff --git a/src/videos.rs b/src/videos.rs index 4aa2b20..a5405ea 100644 --- a/src/videos.rs +++ b/src/videos.rs @@ -26,6 +26,7 @@ pub struct VideosRequest { // Your server's global options will be sent in the videos request // pub flavor: "mint chocolate chip" pub featured: Option, // "featured", + pub category: Option, // "pmv" } #[derive(serde::Serialize, Debug)] pub struct PageInfo {