Files
hottub/src/api.rs
2026-02-26 10:52:03 +00:00

1176 lines
42 KiB
Rust

use capitalize::Capitalize;
use ntex::http::header;
use ntex::web;
use ntex::web::HttpRequest;
use std::cmp::Ordering;
use std::{fs, io};
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::pornhub::PornhubProvider;
use crate::providers::redtube::RedtubeProvider;
use crate::providers::rule34video::Rule34videoProvider;
// use crate::providers::spankbang::SpankbangProvider;
use crate::providers::{ALL_PROVIDERS, DynProvider};
use crate::util::cache::VideoCache;
use crate::util::discord::send_discord_error_report;
use crate::util::proxy::{Proxy, all_proxies_snapshot};
use crate::util::requester::Requester;
use crate::{DbPool, db, status::*, videos::*};
use cute::c;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct ClientVersion {
version: u32,
subversion: u32,
name: String,
}
impl ClientVersion {
pub fn new(version: u32, subversion: u32, name: String) -> ClientVersion {
ClientVersion {
version,
subversion,
name,
}
}
pub fn parse(input: &str) -> Option<Self> {
// Example input: "Hot%20Tub/22c CFNetwork/1494.0.7 Darwin/23.4.0 0.002478"
let first_part = input.split_whitespace().next()?;
let mut name_version = first_part.splitn(2, '/');
let name = name_version.next()?;
let version_str = name_version.next()?;
// Find the index where the numeric part ends
let split_idx = version_str
.find(|c: char| !c.is_ascii_digit())
.unwrap_or(version_str.len());
let (v_num, v_alpha) = version_str.split_at(split_idx);
// Parse the numeric version
let version = v_num.parse::<u32>().ok()?;
// Convert the first character of the subversion to u32 (ASCII value),
// or 0 if it doesn't exist.
let subversion = v_alpha.chars().next().map(|ch| ch as u32).unwrap_or(0);
Some(Self {
version,
subversion,
name: name.to_string(),
})
}
}
// Implement comparisons
impl PartialEq for ClientVersion {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for ClientVersion {}
impl PartialOrd for ClientVersion {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ClientVersion {
fn cmp(&self, other: &Self) -> Ordering {
self.version
.cmp(&other.version)
.then_with(|| self.subversion.cmp(&other.subversion))
}
}
pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::resource("/status")
.route(web::post().to(status))
.route(web::get().to(status)),
)
.service(
web::resource("/videos")
// .route(web::get().to(videos_get))
.route(web::post().to(videos_post)),
)
.service(web::resource("/test").route(web::get().to(test)))
.service(web::resource("/proxies").route(web::get().to(proxies)));
}
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()),
},
_ => ClientVersion::new(999, 0, "999".to_string()),
};
println!(
"Received status request with client version: {:?}",
clientversion
);
let host = req
.headers()
.get(header::HOST)
.and_then(|h| h.to_str().ok())
.unwrap_or_default()
.to_string();
let mut status = Status::new();
// pronhub
status.add_channel(Channel {
id: "pornhub".to_string(),
name: "Pornhub".to_string(),
description: "Pornhub Free Videos".to_string(),
premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=pornhub.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: "mr".to_string(),
title: "Most Recent".to_string(),
},
FilterOption {
id: "mv".to_string(),
title: "Most Viewed".to_string(),
},
FilterOption {
id: "tr".to_string(),
title: "Top Rated".to_string(),
},
FilterOption {
id: "lg".to_string(),
title: "Longest".to_string(),
},
FilterOption {
id: "cm".to_string(),
title: "Newest".to_string(),
},
],
multiSelect: false,
}],
nsfw: true,
cacheDuration: Some(1800),
});
// 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(),
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: "date".to_string(),
// title: "Date".to_string(),
// },
// FilterOption {
// id: "name".to_string(),
// title: "Name".to_string(),
// },
// ],
// multiSelect: false,
// },
ChannelOption {
id: "featured".to_string(),
title: "Featured".to_string(),
description: "Filter Featured Videos.".to_string(),
systemImage: "star".to_string(),
colorName: "red".to_string(),
options: vec![
FilterOption {
id: "all".to_string(),
title: "No".to_string(),
},
FilterOption {
id: "featured".to_string(),
title: "Yes".to_string(),
},
],
multiSelect: false,
},
// ChannelOption {
// id: "duration".to_string(),
// title: "Duration".to_string(),
// description: "Filter the videos by duration.".to_string(),
// systemImage: "timer".to_string(),
// colorName: "green".to_string(),
// options: vec![
// FilterOption {
// id: "short".to_string(),
// title: "< 1h".to_string(),
// },
// FilterOption {
// id: "long".to_string(),
// title: "> 1h".to_string(),
// },
// ],
// multiSelect: true,
// },
],
nsfw: true,
cacheDuration: None,
});
// 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(),
description: "Free Hentai from Hanime".to_string(),
premium: false,
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,
}],
nsfw: true,
cacheDuration: None,
});
// rule34video
status.add_channel(Channel {
id: "rule34video".to_string(),
name: "Rule34Video".to_string(),
description: "If it exists, there is porn".to_string(),
premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=rule34video.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: "post_date".to_string(),
title: "Newest".to_string(),
},
FilterOption {
id: "video_viewed".to_string(),
title: "Most Viewed".to_string(),
},
FilterOption {
id: "rating".to_string(),
title: "Top Rated".to_string(),
},
FilterOption {
id: "duration".to_string(),
title: "Longest".to_string(),
},
FilterOption {
id: "pseudo_random".to_string(),
title: "Random".to_string(),
},
],
multiSelect: false,
}],
nsfw: true,
cacheDuration: Some(1800),
});
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::<Vec<String>>();
let sites = c![FilterOption {
id: x.to_string(),
title: x.capitalize().to_string(),
}, for x in providers.iter()];
// All
status.add_channel(Channel {
id: "all".to_string(),
name: "All".to_string(),
description: "Query from all sites of this Server".to_string(),
premium: false,
favicon: "https://hottub.spacemoehre.de/favicon.ico".to_string(),
status: "active".to_string(),
categories: vec![],
options: vec![ChannelOption {
id: "sites".to_string(),
title: "Sites".to_string(),
description: "What Sites to use".to_string(), //"Sort the videos by Date or Name.".to_string(),
systemImage: "list.number".to_string(),
colorName: "green".to_string(),
options: sites,
multiSelect: true,
}],
nsfw: true,
cacheDuration: Some(1800),
});
// Redtube
status.add_channel(Channel {
id: "redtube".to_string(),
name: "Redtube".to_string(),
description: "Redtube brings you NEW porn videos every day for free".to_string(),
premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=www.redtube.com".to_string(),
status: "active".to_string(),
categories: vec![],
options: vec![],
nsfw: true,
cacheDuration: Some(1800),
});
// ok.porn
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![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: "new".to_string(),
title: "New".to_string(),
},
FilterOption {
id: "popular".to_string(),
title: "Popular".to_string(),
},
FilterOption {
id: "trending".to_string(),
title: "Trending".to_string(),
},
],
multiSelect: false,
}],
nsfw: true,
cacheDuration: Some(1800),
});
// pornhat
status.add_channel(Channel {
id: "pornhat".to_string(),
name: "Pornhat".to_string(),
description: "free HD porn videos".to_string(),
premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=pornhat.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: "new".to_string(),
title: "New".to_string(),
},
FilterOption {
id: "popular".to_string(),
title: "Popular".to_string(),
},
FilterOption {
id: "trending".to_string(),
title: "Trending".to_string(),
},
],
multiSelect: false,
}],
nsfw: true,
cacheDuration: Some(1800),
});
//perfectgirls
status.add_channel(Channel {
id: "perfectgirls".to_string(),
name: "Perfectgirls".to_string(),
description: "Perfect Girls Tube".to_string(),
premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=perfectgirls.xxx".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: "new".to_string(),
title: "New".to_string(),
},
FilterOption {
id: "popular".to_string(),
title: "Popular".to_string(),
},
FilterOption {
id: "trending".to_string(),
title: "Trending".to_string(),
},
],
multiSelect: false,
}],
nsfw: true,
cacheDuration: Some(1800),
});
// okxxx
status.add_channel(Channel {
id: "okxxx".to_string(),
name: "Ok.xxx".to_string(),
description: "free porn tube!".to_string(),
premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=ok.xxx".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: "new".to_string(),
title: "New".to_string(),
},
FilterOption {
id: "popular".to_string(),
title: "Popular".to_string(),
},
FilterOption {
id: "trending".to_string(),
title: "Trending".to_string(),
},
],
multiSelect: false,
}],
nsfw: true,
cacheDuration: Some(1800),
});
// homoxxx
status.add_channel(Channel {
id: "homoxxx".to_string(),
name: "Homo.xxx".to_string(),
description: "Best Gay Porn".to_string(),
premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=homo.xxx".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: "new".to_string(),
title: "New".to_string(),
},
FilterOption {
id: "popular".to_string(),
title: "Popular".to_string(),
},
FilterOption {
id: "trending".to_string(),
title: "Trending".to_string(),
},
],
multiSelect: false,
}],
nsfw: true,
cacheDuration: Some(1800),
});
// xxthots
status.add_channel(Channel {
id: "xxthots".to_string(),
name: "XXTHOTS".to_string(),
description: "Free XXX Onlyfans Leaks Videos".to_string(),
premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=xxthots.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: "new".to_string(),
title: "New".to_string(),
},
FilterOption {
id: "popular".to_string(),
title: "Popular".to_string(),
},
FilterOption {
id: "top-rated".to_string(),
title: "Top Rated".to_string(),
},
],
multiSelect: false,
}],
nsfw: true,
cacheDuration: Some(1800),
});
// porn00
status.add_channel(Channel {
id: "porn00".to_string(),
name: "Porn00".to_string(),
description: "HD Porn".to_string(),
premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=www.porn00.org".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: "new".to_string(),
title: "New".to_string(),
},
FilterOption {
id: "popular".to_string(),
title: "Popular".to_string(),
},
FilterOption {
id: "top-rated".to_string(),
title: "Top Rated".to_string(),
},
],
multiSelect: false,
}],
nsfw: true,
cacheDuration: Some(1800),
});
// paradisehill
status.add_channel(Channel {
id: "paradisehill".to_string(),
name: "Paradisehill".to_string(),
description: "Porn Movies on Paradise Hill".to_string(),
premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=en.paradisehill.cc".to_string(),
status: "active".to_string(),
categories: vec![],
options: vec![],
nsfw: true,
cacheDuration: None,
});
// youjizz
status.add_channel(Channel {
id: "youjizz".to_string(),
name: "YouJizz".to_string(),
description: "YouJizz Porntube".to_string(),
premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=www.youjizz.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: "new".to_string(),
title: "New".to_string(),
},
FilterOption {
id: "popular".to_string(),
title: "Popular".to_string(),
},
FilterOption {
id: "top-rated".to_string(),
title: "Top Rated".to_string(),
},
FilterOption {
id: "top-rated-week".to_string(),
title: "Top Rated (Week)".to_string(),
},
FilterOption {
id: "top-rated-month".to_string(),
title: "Top Rated (Month)".to_string(),
},
FilterOption {
id: "trending".to_string(),
title: "Trending".to_string(),
},
FilterOption {
id: "random".to_string(),
title: "Random".to_string(),
},
],
multiSelect: false,
}],
nsfw: true,
cacheDuration: None,
});
//missav
status.add_channel(Channel {
id: "missav".to_string(),
name: "MissAV".to_string(),
description: "Watch HD JAV Online".to_string(),
premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=missav.ws".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: "released_at".to_string(),
title: "Release Date".to_string(),
},
FilterOption {
id: "published_at".to_string(),
title: "Recent Update".to_string(),
},
FilterOption {
id: "today_views".to_string(),
title: "Today Views".to_string(),
},
FilterOption {
id: "weekly_views".to_string(),
title: "Weekly Views".to_string(),
},
FilterOption {
id: "monthly_views".to_string(),
title: "Monthly Views".to_string(),
},
FilterOption {
id: "views".to_string(),
title: "Total Views".to_string(),
},
],
multiSelect: false,
},
ChannelOption {
id: "filter".to_string(),
title: "Filter".to_string(),
description: "Filter the Videos".to_string(), //"Sort the videos by Date or Name.".to_string(),
systemImage: "line.horizontal.3.decrease.circle".to_string(),
colorName: "green".to_string(),
options: vec![
FilterOption {
id: "new".to_string(),
title: "Recent update".to_string(),
},
FilterOption {
id: "release".to_string(),
title: "New Releases".to_string(),
},
FilterOption {
id: "uncensored-leak".to_string(),
title: "Uncensored".to_string(),
},
FilterOption {
id: "english-subtitle".to_string(),
title: "English subtitle".to_string(),
},
],
multiSelect: false,
},
ChannelOption {
id: "language".to_string(),
title: "Language".to_string(),
description: "What Language to fetch".to_string(), //"Sort the videos by Date or Name.".to_string(),
systemImage: "flag.fill".to_string(),
colorName: "gray".to_string(),
options: vec![
FilterOption {
id: "en".to_string(),
title: "English".to_string(),
},
FilterOption {
id: "cn".to_string(),
title: "简体中文".to_string(),
},
FilterOption {
id: "ja".to_string(),
title: "日本語".to_string(),
},
FilterOption {
id: "ko".to_string(),
title: "한국의".to_string(),
},
FilterOption {
id: "ms".to_string(),
title: "Melayu".to_string(),
},
FilterOption {
id: "th".to_string(),
title: "ไทย".to_string(),
},
FilterOption {
id: "de".to_string(),
title: "Deutsch".to_string(),
},
FilterOption {
id: "fr".to_string(),
title: "Français".to_string(),
},
FilterOption {
id: "vi".to_string(),
title: "Tiếng Việt".to_string(),
},
FilterOption {
id: "id".to_string(),
title: "Bahasa Indonesia".to_string(),
},
FilterOption {
id: "fil".to_string(),
title: "Filipino".to_string(),
},
FilterOption {
id: "pt".to_string(),
title: "Português".to_string(),
},
],
multiSelect: false,
},
],
nsfw: true,
cacheDuration: None,
});
// if clientversion >= ClientVersion::new(22, 105, "22i".to_string()) {
//sxyprn
status.add_channel(Channel {
id: "sxyprn".to_string(),
name: "SexyPorn".to_string(),
description: "Free Porn Site".to_string(),
premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=sxyprn.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: "latest".to_string(),
title: "Latest".to_string(),
},
FilterOption {
id: "views".to_string(),
title: "Views".to_string(),
},
FilterOption {
id: "rating".to_string(),
title: "Rating".to_string(),
},
FilterOption {
id: "orgasmic".to_string(),
title: "Orgasmic".to_string(),
},
],
multiSelect: false,
},
ChannelOption {
id: "filter".to_string(),
title: "Filter".to_string(),
description: "Filter the Videos".to_string(), //"Sort the videos by Date or Name.".to_string(),
systemImage: "line.horizontal.3.decrease.circle".to_string(),
colorName: "green".to_string(),
options: vec![
FilterOption {
id: "top".to_string(),
title: "Top".to_string(),
},
FilterOption {
id: "other".to_string(),
title: "Other".to_string(),
},
FilterOption {
id: "all".to_string(),
title: "All".to_string(),
},
],
multiSelect: false,
},
],
nsfw: true,
cacheDuration: Some(1800),
});
// }
for provider in ALL_PROVIDERS.values() {
if let Some(channel) = provider.get_channel(clientversion.clone()) {
status.add_channel(channel);
}
}
status.iconUrl = format!("http://{}/favicon.ico", host).to_string();
Ok(web::HttpResponse::Ok().json(&status))
}
async fn videos_post(
mut video_request: web::types::Json<VideosRequest>,
cache: web::types::State<VideoCache>,
pool: web::types::State<DbPool>,
requester: web::types::State<Requester>,
) -> Result<impl web::Responder, web::Error> {
match video_request.query.as_deref() {
Some(query) if query.starts_with("#") => {
video_request.query = Some(query.trim_start_matches("#").to_string());
}
_ => {}
}
let requester = requester.get_ref().clone();
let mut conn = pool.get().expect("couldn't get db connection from pool");
// Ensure "videos" table exists with two string columns
if !(db::has_table(&mut conn, "videos").unwrap()) {
let _ = db::create_table(
&mut conn,
"CREATE TABLE videos (id TEXT NOT NULL, url TEXT NOT NULL);",
);
}
let mut videos = Videos {
pageInfo: PageInfo {
hasNextPage: true,
resultsPerPage: 10,
},
items: vec![],
};
let channel: String = video_request
.channel
.as_deref()
.unwrap_or("all")
.to_string();
let sort: String = video_request.sort.as_deref().unwrap_or("date").to_string();
let mut query: Option<String> = video_request.query.clone();
if video_request.query.as_deref() == Some("") {
query = None;
}
let page: u8 = video_request
.page
.as_ref()
.and_then(|value| value.to_u8())
.unwrap_or(1);
let perPage: u8 = video_request
.perPage
.as_ref()
.and_then(|value| value.to_u8())
.unwrap_or(10);
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 sites = video_request.sites.as_deref().unwrap_or("").to_string();
let filter = video_request.filter.as_deref().unwrap_or("new").to_string();
let language = video_request
.language
.as_deref()
.unwrap_or("en")
.to_string();
let network = video_request.networks.as_deref().unwrap_or("").to_string();
let stars = video_request.stars.as_deref().unwrap_or("").to_string();
let categories = video_request
.categories
.as_deref()
.unwrap_or("")
.to_string();
let duration = video_request.duration.as_deref().unwrap_or("").to_string();
let options = ServerOptions {
featured: Some(featured),
category: Some(category),
sites: Some(sites),
filter: Some(filter),
language: Some(language),
requester: Some(requester),
network: Some(network),
stars: Some(stars),
categories: Some(categories),
duration: Some(duration),
sort: Some(sort.clone()),
};
let video_items = provider
.get_videos(
cache.get_ref().clone(),
pool.get_ref().clone(),
sort.clone(),
query.clone(),
page.to_string(),
perPage.to_string(),
options.clone(),
)
.await;
videos.items = video_items.clone();
if video_items.len() == 0 {
videos.pageInfo = PageInfo {
hasNextPage: false,
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();
let pool_clone = pool.get_ref().clone();
let sort_clone = sort.clone();
let query_clone = query.clone();
let per_page_clone = perPage.to_string();
let options_clone = options.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;
// }
let _ = provider_clone
.get_videos(
cache_clone,
pool_clone,
sort_clone,
query_clone,
next_page.to_string(),
per_page_clone,
options_clone,
)
.await;
});
//###
for video in videos.items.iter_mut() {
if video.duration <= 120 {
let mut preview_url = video.url.clone();
if let Some(x) = &video.formats {
// preview is a String here, so use it directly
preview_url = x[0].url.clone();
}
video.preview = Some(preview_url);
}
}
Ok(web::HttpResponse::Ok().json(&videos))
}
pub fn get_provider(channel: &str) -> Option<DynProvider> {
match channel {
"all" => Some(Arc::new(AllProvider::new())),
"perverzija" => Some(Arc::new(PerverzijaProvider::new())),
"hanime" => Some(Arc::new(HanimeProvider::new())),
"pornhub" => Some(Arc::new(PornhubProvider::new())),
"rule34video" => Some(Arc::new(Rule34videoProvider::new())),
"redtube" => Some(Arc::new(RedtubeProvider::new())),
"okporn" => Some(Arc::new(OkpornProvider::new())),
"pornhat" => Some(Arc::new(crate::providers::pornhat::PornhatProvider::new())),
"perfectgirls" => Some(Arc::new(
crate::providers::perfectgirls::PerfectgirlsProvider::new(),
)),
"okxxx" => Some(Arc::new(crate::providers::okxxx::OkxxxProvider::new())),
"homoxxx" => Some(Arc::new(crate::providers::homoxxx::HomoxxxProvider::new())),
"missav" => Some(Arc::new(crate::providers::missav::MissavProvider::new())),
"xxthots" => Some(Arc::new(crate::providers::xxthots::XxthotsProvider::new())),
"sxyprn" => Some(Arc::new(crate::providers::sxyprn::SxyprnProvider::new())),
"porn00" => Some(Arc::new(crate::providers::porn00::Porn00Provider::new())),
"youjizz" => Some(Arc::new(crate::providers::youjizz::YoujizzProvider::new())),
"paradisehill" => Some(Arc::new(
crate::providers::paradisehill::ParadisehillProvider::new(),
)),
"pornzog" => Some(Arc::new(crate::providers::pornzog::PornzogProvider::new())),
// fallback to the dynamic registry
x => ALL_PROVIDERS.get(x).cloned(),
}
}
pub async fn test() -> Result<impl web::Responder, web::Error> {
// Simply await the function instead of blocking the thread
let e = io::Error::new(io::ErrorKind::Other, "test error");
let _ = send_discord_error_report(
e.to_string(),
Some("chain_str".to_string()),
Some("Context"),
Some("xtra info"),
file!(),
line!(),
module_path!(),
)
.await;
Ok(web::HttpResponse::Ok())
}
pub async fn proxies() -> Result<impl web::Responder, web::Error> {
let proxies = all_proxies_snapshot().await.unwrap_or_default();
let mut by_protocol: std::collections::BTreeMap<String, Vec<Proxy>> =
std::collections::BTreeMap::new();
for proxy in proxies {
by_protocol
.entry(proxy.protocol.clone())
.or_default()
.push(proxy);
}
for proxies in by_protocol.values_mut() {
proxies.sort_by(|a, b| {
a.host
.cmp(&b.host)
.then(a.port.cmp(&b.port))
.then(a.username.cmp(&b.username))
.then(a.password.cmp(&b.password))
});
}
Ok(web::HttpResponse::Ok().json(&by_protocol))
}