Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUYdCh2nCVsQYgJAT7gwSi
312 lines
12 KiB
Rust
312 lines
12 KiB
Rust
use ntex::web::{self, HttpRequest};
|
|
|
|
use crate::proxies::archivebate::ArchivebateProxy;
|
|
use crate::proxies::clapdat::ClapdatProxy;
|
|
use crate::proxies::doodstream::DoodstreamProxy;
|
|
use crate::proxies::heavyfetish::HeavyfetishProxy;
|
|
use crate::proxies::hqporner::HqpornerProxy;
|
|
use crate::proxies::javtiful::JavtifulProxy;
|
|
use crate::proxies::pimpbunny::PimpbunnyProxy;
|
|
use crate::proxies::porndish::PorndishProxy;
|
|
use crate::proxies::pornhd3x::Pornhd3xProxy;
|
|
use crate::proxies::spankbang::SpankbangProxy;
|
|
use crate::proxies::supjav::SupjavProxy;
|
|
use crate::proxies::sxyprn::SxyprnProxy;
|
|
use crate::proxies::tube8::Tube8Proxy;
|
|
use crate::proxies::vjav::VjavProxy;
|
|
use crate::proxies::vidara::VidaraProxy;
|
|
use crate::proxies::lulustream::LulustreamProxy;
|
|
use crate::proxies::thaiporntv::ThaipornTvProxy;
|
|
use crate::proxies::*;
|
|
use crate::util::requester::Requester;
|
|
|
|
/// Proxies that answer with a `302` to the real media instead of streaming it.
|
|
/// The Hot Tub app cannot follow a redirect inside `formats[].url`, so for app
|
|
/// clients a format pointing at one of these is moved to `video.url` (see
|
|
/// `move_redirect_formats_to_url`). Keep in sync with the `proxy2redirect`
|
|
/// routes below; `redirect_proxy_list_matches_routes` enforces it.
|
|
pub const REDIRECT_PROXIES: &[&str] = &[
|
|
"archivebate",
|
|
"clapdat",
|
|
"doodstream",
|
|
"lulustream",
|
|
"sxyprn",
|
|
"javtiful",
|
|
"spankbang",
|
|
"porndish",
|
|
"hqporner",
|
|
"heavyfetish",
|
|
"vjav",
|
|
"pornhd3x",
|
|
"vidara",
|
|
"pimpbunny",
|
|
"thaiporntv",
|
|
"tube8",
|
|
"supjav",
|
|
];
|
|
|
|
/// True for a `/proxy/<redirect proxy>/...` URL (absolute or host-relative).
|
|
pub fn is_redirect_proxy_url(url: &str) -> bool {
|
|
let Some(rest) = url.split_once("/proxy/").map(|(_, rest)| rest) else {
|
|
return false;
|
|
};
|
|
let name = rest.split('/').next().unwrap_or_default();
|
|
REDIRECT_PROXIES.contains(&name)
|
|
}
|
|
|
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
|
cfg.service(
|
|
web::resource("/archivebate/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/clapdat/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/doodstream/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/lulustream/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/sxyprn/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/javtiful/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/spankbang/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/porndish/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/hqporner/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/heavyfetish/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/vjav/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/pornhd3x/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/vidara/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/shooshtime-media/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::shooshtime::serve_media))
|
|
.route(web::get().to(crate::proxies::shooshtime::serve_media)),
|
|
)
|
|
.service(
|
|
web::resource("/pimpbunny/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
)
|
|
.service(
|
|
web::resource("/noodlemagazine/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::noodlemagazine::serve_media))
|
|
.route(web::get().to(crate::proxies::noodlemagazine::serve_media)),
|
|
)
|
|
.service(
|
|
web::resource("/animeidhentai/{endpoint}*")
|
|
.route(web::get().to(crate::proxies::animeidhentai::serve_media))
|
|
.route(web::post().to(crate::proxies::animeidhentai::serve_media))
|
|
.route(web::head().to(crate::proxies::animeidhentai::serve_media)),
|
|
)
|
|
.service(
|
|
web::resource("/hentaitv/{endpoint}*")
|
|
.route(web::get().to(crate::proxies::hentaitv::serve_media))
|
|
.route(web::post().to(crate::proxies::hentaitv::serve_media))
|
|
.route(web::head().to(crate::proxies::hentaitv::serve_media)),
|
|
)
|
|
.service(
|
|
web::resource("/noodlemagazine-thumb/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::noodlemagazine::get_image))
|
|
.route(web::get().to(crate::proxies::noodlemagazine::get_image)),
|
|
)
|
|
.service(
|
|
web::resource("/fikfap-thumb/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::fikfapthumb::get_image))
|
|
.route(web::get().to(crate::proxies::fikfapthumb::get_image)),
|
|
)
|
|
.service(
|
|
web::resource("/hanime-cdn/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::hanimecdn::get_image))
|
|
.route(web::get().to(crate::proxies::hanimecdn::get_image)),
|
|
)
|
|
.service(
|
|
web::resource("/hanime-thumb/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::hanimethumb::get_image))
|
|
.route(web::get().to(crate::proxies::hanimethumb::get_image)),
|
|
)
|
|
.service(
|
|
web::resource("/hqporner-thumb/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::hqpornerthumb::get_image))
|
|
.route(web::get().to(crate::proxies::hqpornerthumb::get_image)),
|
|
)
|
|
.service(
|
|
web::resource("/porndish-thumb/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::porndishthumb::get_image))
|
|
.route(web::get().to(crate::proxies::porndishthumb::get_image)),
|
|
)
|
|
.service(
|
|
web::resource("/hotbunny-thumb/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::hotbunnythumb::get_image))
|
|
.route(web::get().to(crate::proxies::hotbunnythumb::get_image)),
|
|
);
|
|
cfg.service(
|
|
web::resource("/aylo-thumb/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::aylothumb::get_image))
|
|
.route(web::get().to(crate::proxies::aylothumb::get_image)),
|
|
);
|
|
cfg.service(
|
|
web::resource("/pornhub-thumb/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::pornhubthumb::get_image))
|
|
.route(web::get().to(crate::proxies::pornhubthumb::get_image)),
|
|
);
|
|
cfg.service(
|
|
web::resource("/thaiporntv/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
);
|
|
cfg.service(
|
|
web::resource("/tube8/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect)),
|
|
);
|
|
cfg.service(
|
|
web::resource("/supjav/{endpoint}*")
|
|
.route(web::post().to(proxy2redirect))
|
|
.route(web::get().to(proxy2redirect))
|
|
.route(web::head().to(proxy2redirect)),
|
|
);
|
|
cfg.service(
|
|
web::resource("/jable/{slug}*")
|
|
.route(web::get().to(crate::proxies::jable::redirect_to_page))
|
|
.route(web::head().to(crate::proxies::jable::redirect_to_page)),
|
|
);
|
|
cfg.service(
|
|
web::resource("/thepornbunny/{slug}*")
|
|
.route(web::get().to(crate::proxies::thepornbunny::redirect_to_media))
|
|
.route(web::head().to(crate::proxies::thepornbunny::redirect_to_media)),
|
|
);
|
|
cfg.service(
|
|
web::resource("/aps/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::allpornstream::serve))
|
|
.route(web::get().to(crate::proxies::allpornstream::serve)),
|
|
);
|
|
cfg.service(
|
|
web::resource("/melonstube/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::melonstube::serve))
|
|
.route(web::get().to(crate::proxies::melonstube::serve)),
|
|
);
|
|
cfg.service(
|
|
web::resource("/xiaoshenke/{endpoint}*")
|
|
.route(web::post().to(crate::proxies::xiaoshenke::serve_media))
|
|
.route(web::get().to(crate::proxies::xiaoshenke::serve_media))
|
|
.route(web::head().to(crate::proxies::xiaoshenke::serve_media)),
|
|
);
|
|
}
|
|
|
|
async fn proxy2redirect(
|
|
req: HttpRequest,
|
|
requester: web::types::State<Requester>,
|
|
) -> Result<impl web::Responder, web::Error> {
|
|
let proxy = get_proxy(req.uri().to_string().split("/").collect::<Vec<&str>>()[2]).unwrap();
|
|
let endpoint = req.match_info().query("endpoint").to_string();
|
|
let video_url = proxy.get_video_url(endpoint, requester).await;
|
|
if video_url.is_empty() {
|
|
return Ok(web::HttpResponse::BadGateway().finish());
|
|
}
|
|
Ok(web::HttpResponse::Found()
|
|
.header("Location", video_url)
|
|
.finish())
|
|
}
|
|
|
|
fn get_proxy(proxy: &str) -> Option<AnyProxy> {
|
|
match proxy {
|
|
"archivebate" => Some(AnyProxy::Archivebate(ArchivebateProxy::new())),
|
|
"clapdat" => Some(AnyProxy::Clapdat(ClapdatProxy::new())),
|
|
"doodstream" => Some(AnyProxy::Doodstream(DoodstreamProxy::new())),
|
|
"sxyprn" => Some(AnyProxy::Sxyprn(SxyprnProxy::new())),
|
|
"javtiful" => Some(AnyProxy::Javtiful(JavtifulProxy::new())),
|
|
"hqporner" => Some(AnyProxy::Hqporner(HqpornerProxy::new())),
|
|
"heavyfetish" => Some(AnyProxy::Heavyfetish(HeavyfetishProxy::new())),
|
|
"vjav" => Some(AnyProxy::Vjav(VjavProxy::new())),
|
|
"pornhd3x" => Some(AnyProxy::Pornhd3x(Pornhd3xProxy::new())),
|
|
"vidara" => Some(AnyProxy::Vidara(VidaraProxy::new())),
|
|
"pimpbunny" => Some(AnyProxy::Pimpbunny(PimpbunnyProxy::new())),
|
|
"porndish" => Some(AnyProxy::Porndish(PorndishProxy::new())),
|
|
"spankbang" => Some(AnyProxy::Spankbang(SpankbangProxy::new())),
|
|
"lulustream" => Some(AnyProxy::Lulustream(LulustreamProxy::new())),
|
|
"thaiporntv" => Some(AnyProxy::ThaipornTv(ThaipornTvProxy::new())),
|
|
"tube8" => Some(AnyProxy::Tube8(Tube8Proxy::new())),
|
|
"supjav" => Some(AnyProxy::Supjav(SupjavProxy::new())),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod redirect_proxy_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn recognises_redirect_proxy_urls() {
|
|
assert!(is_redirect_proxy_url("http://h/proxy/archivebate/archivebate.com/watch/1"));
|
|
assert!(is_redirect_proxy_url("/proxy/hqporner/x"));
|
|
assert!(!is_redirect_proxy_url("http://h/proxy/noodlemagazine/x"));
|
|
assert!(!is_redirect_proxy_url("https://cdn.example/video.mp4"));
|
|
}
|
|
|
|
#[test]
|
|
fn redirect_proxy_list_matches_routes() {
|
|
let source = include_str!("proxy.rs");
|
|
let config_body = source
|
|
.split("pub fn config(")
|
|
.nth(1)
|
|
.and_then(|rest| rest.split("async fn proxy2redirect(").next())
|
|
.expect("config body");
|
|
let mut from_routes: Vec<String> = Vec::new();
|
|
for block in config_body.split("web::resource(\"/").skip(1) {
|
|
let name = block.split("/{endpoint}").next().unwrap_or_default();
|
|
let until_next = block.split("web::resource(").next().unwrap_or(block);
|
|
if until_next.contains("proxy2redirect") {
|
|
from_routes.push(name.to_string());
|
|
}
|
|
}
|
|
let mut listed: Vec<String> = REDIRECT_PROXIES.iter().map(|s| s.to_string()).collect();
|
|
from_routes.sort();
|
|
from_routes.dedup();
|
|
listed.sort();
|
|
assert_eq!(listed, from_routes);
|
|
}
|
|
}
|