This commit is contained in:
Simon
2025-07-17 18:41:57 +00:00
parent 9d3d8ce67b
commit b4ee574433
10 changed files with 652 additions and 125 deletions

View File

@@ -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::<u32>() {
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::<u32>() {
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::<u32>() {
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::<u32>() {
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<impl web::Responder, web::Error> {
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<impl web::Responder, web::Error> {
.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<impl web::Responder, web::Error> {
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<impl web::Responder, web::Error> {
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<impl web::Responder, web::Error> {
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::<i32>().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<AnyProvider> {
"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())),
}
}
}

View File

@@ -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;

View File

@@ -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<VideoItem> {
let _ = category;
let _ = featured;
let _ = per_page;
let _ = sort;

View File

@@ -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<String>, page: String, per_page: String, featured: String) -> Vec<VideoItem>;
pub trait Provider {
async fn get_videos(
&self,
cache: VideoCache,
pool: DbPool,
channel: String,
sort: String,
query: Option<String>,
page: String,
per_page: String,
featured: String,
category: String,
) -> Vec<VideoItem>;
}
#[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<String>, page: String, per_page: String, featured: String) -> Vec<VideoItem> {
async fn get_videos(
&self,
cache: VideoCache,
pool: DbPool,
channel: String,
sort: String,
query: Option<String>,
page: String,
per_page: String,
featured: String,
category: String,
) -> Vec<VideoItem> {
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
}
}
}
}

View File

@@ -452,7 +452,9 @@ impl Provider for PerverzijaProvider {
page: String,
per_page: String,
featured: String,
category: String,
) -> Vec<VideoItem> {
let _ = category;
let _ = per_page;
let _ = sort;
let videos: std::result::Result<Vec<VideoItem>, Error> = match query {

422
src/providers/pmvhaven.rs Normal file
View File

@@ -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<bool>, //null,
sameSexContent: Option<bool>, //null,
seizureWarning: Option<bool>, //null,
tags: Vec<String>, //[],
music: Vec<String>, //[],
stars: Vec<String>, //[],
creators: Vec<String>, //[],
range: Vec<u32>, //[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<String>, //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<String>, //itonlygetsworse",
duration: f32, //259.093333,
width: Option<String>, //3840",
height: Option<String>, //2160",
ratio: Option<u32>, //50,
thumbnails: Vec<Option<String>>, //[
// "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<String>, //https://storage.pmvhaven.com/686f24e96f7124f3dfbe90ab/JAV Addiction Therapy_686f24e96f7124f3dfbe90ab.mp4",
previewUrlCompressed: Option<String>, //https://storage.pmvhaven.com/686f24e96f7124f3dfbe90ab/videoPreview/comus_686f24e96f7124f3dfbe90ab.mp4",
seizureWarning: Option<bool>, //false,
isoDate: Option<String>, //2025-07-10T02:52:26.000Z",
gayContent: Option<bool>, //false,
transContent: Option<bool>, //false,
creator: Option<String>, //itonlygetsworse",
_id: String, //686f2aeade2062f93d72931f",
totalRaters: Option<u32>, //42,
rating: Option<u32>, //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<u32>,
data: Vec<PmvhavenVideo>,
count: Option<u32>,
}
impl PmvhavenResponse {
fn to_videoitems(self) -> Vec<VideoItem> {
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<Vec<VideoItem>> {
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::<PmvhavenResponse>().await {
Ok(resp) => resp,
Err(e) => {
println!("Failed to parse PmvhavenResponse: {}", e);
return Ok(old_items);
}
};
let video_items: Vec<VideoItem> = 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<Vec<VideoItem>> {
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::<PmvhavenResponse>().await {
Ok(resp) => resp,
Err(e) => {
println!("Failed to parse PmvhavenResponse: {}", e);
return Ok(old_items);
}
};
let video_items: Vec<VideoItem> = 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<String>,
page: String,
per_page: String,
featured: String,
category: String,
) -> Vec<VideoItem> {
let _ = per_page;
let _ = sort;
let _ = featured; // Ignored in this implementation
let _ = pool; // Ignored in this implementation
let videos: std::result::Result<Vec<VideoItem>, Error> = match query {
Some(q) => self.query(cache, page.parse::<u8>().unwrap_or(1), &q).await,
None => {
self.get(cache, page.parse::<u8>().unwrap_or(1), category)
.await
}
};
match videos {
Ok(v) => v,
Err(e) => {
println!("Error fetching videos: {}", e);
vec![]
}
}
}
}

View File

@@ -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<VideoItem> {
let _ = category;
let _ = per_page;
let _ = sort;
let _ = featured; // Ignored in this implementation

View File

@@ -358,7 +358,9 @@ impl Provider for SpankbangProvider {
page: String,
per_page: String,
featured: String,
category: String,
) -> Vec<VideoItem> {
let _ = category;
let _ = per_page;
let _ = featured;
let _ = pool;

View File

@@ -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<String>, // "featured",
pub category: Option<String>, // "pmv"
}
#[derive(serde::Serialize, Debug)]
pub struct PageInfo {