vjav proxy

This commit is contained in:
Simon
2026-04-06 06:23:34 +00:00
parent 8d39b3a36f
commit 772835d4d1
3 changed files with 198 additions and 0 deletions

View File

@@ -8,6 +8,7 @@ use crate::proxies::pimpbunny::PimpbunnyProxy;
use crate::proxies::porndish::PorndishProxy; use crate::proxies::porndish::PorndishProxy;
use crate::proxies::shooshtime::ShooshtimeProxy; use crate::proxies::shooshtime::ShooshtimeProxy;
use crate::proxies::spankbang::SpankbangProxy; use crate::proxies::spankbang::SpankbangProxy;
use crate::proxies::vjav::VjavProxy;
use crate::{proxies::sxyprn::SxyprnProxy, util::requester::Requester}; use crate::{proxies::sxyprn::SxyprnProxy, util::requester::Requester};
pub mod doodstream; pub mod doodstream;
@@ -26,6 +27,7 @@ pub mod pornhubthumb;
pub mod shooshtime; pub mod shooshtime;
pub mod spankbang; pub mod spankbang;
pub mod sxyprn; pub mod sxyprn;
pub mod vjav;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum AnyProxy { pub enum AnyProxy {
@@ -39,6 +41,7 @@ pub enum AnyProxy {
Shooshtime(ShooshtimeProxy), Shooshtime(ShooshtimeProxy),
Hqporner(HqpornerProxy), Hqporner(HqpornerProxy),
Heavyfetish(HeavyfetishProxy), Heavyfetish(HeavyfetishProxy),
Vjav(VjavProxy),
} }
pub trait Proxy { pub trait Proxy {
@@ -58,6 +61,7 @@ impl Proxy for AnyProxy {
AnyProxy::Shooshtime(p) => p.get_video_url(url, requester).await, AnyProxy::Shooshtime(p) => p.get_video_url(url, requester).await,
AnyProxy::Hqporner(p) => p.get_video_url(url, requester).await, AnyProxy::Hqporner(p) => p.get_video_url(url, requester).await,
AnyProxy::Heavyfetish(p) => p.get_video_url(url, requester).await, AnyProxy::Heavyfetish(p) => p.get_video_url(url, requester).await,
AnyProxy::Vjav(p) => p.get_video_url(url, requester).await,
} }
} }
} }

187
src/proxies/vjav.rs Normal file
View File

@@ -0,0 +1,187 @@
use base64::{Engine as _, engine::general_purpose::STANDARD};
use ntex::web;
use serde::Deserialize;
use url::Url;
use crate::util::requester::Requester;
const BASE_URL: &str = "https://vjav.com";
#[derive(Debug, Clone)]
pub struct VjavProxy {}
#[derive(Debug, Deserialize, Clone, Default)]
struct VideofileEntry {
#[serde(default)]
video_url: String,
#[serde(default)]
is_default: i32,
}
impl VjavProxy {
pub fn new() -> Self {
Self {}
}
fn normalize_detail_url(endpoint: &str) -> Option<String> {
let endpoint = endpoint.trim().trim_start_matches('/');
if endpoint.is_empty() {
return None;
}
let detail_url = if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
endpoint.to_string()
} else {
format!("https://{}", endpoint.trim_start_matches('/'))
};
Self::is_allowed_detail_url(&detail_url).then_some(detail_url)
}
fn is_allowed_detail_url(url: &str) -> bool {
let Some(parsed) = Url::parse(url).ok() else {
return false;
};
if parsed.scheme() != "https" {
return false;
}
let Some(host) = parsed.host_str() else {
return false;
};
if host != "vjav.com" && host != "www.vjav.com" {
return false;
}
let Some(video_id) = Self::extract_video_id(parsed.path()) else {
return false;
};
!video_id.is_empty()
}
fn extract_video_id(path: &str) -> Option<String> {
let mut segments = path.split('/').filter(|segment| !segment.is_empty());
let first = segments.next()?;
let second = segments.next()?;
if first != "videos" {
return None;
}
second
.chars()
.all(|value| value.is_ascii_digit())
.then_some(second.to_string())
}
fn decode_obfuscated_base64(value: &str) -> String {
value
.chars()
.map(|character| match character {
'А' => 'A',
'В' => 'B',
'Е' => 'E',
'К' => 'K',
'М' => 'M',
'Н' => 'H',
'О' => 'O',
'Р' => 'P',
'С' => 'C',
'Т' => 'T',
'Х' => 'X',
'а' => 'a',
'е' => 'e',
'о' => 'o',
'р' => 'p',
'с' => 'c',
'у' => 'y',
'х' => 'x',
'к' => 'k',
'м' => 'm',
'і' => 'i',
'І' => 'I',
_ => character,
})
.collect()
}
fn decode_base64ish(value: &str) -> Option<String> {
let mut normalized = value.trim().replace('~', "=");
while normalized.len() % 4 != 0 {
normalized.push('=');
}
let bytes = STANDARD.decode(normalized).ok()?;
String::from_utf8(bytes).ok()
}
fn decode_video_url(value: &str) -> Option<String> {
let normalized = Self::decode_obfuscated_base64(value);
if normalized.contains(',') {
let mut parts = normalized.split(',');
let path_part = parts.next()?;
let query_part = parts.next()?;
let path = Self::decode_base64ish(path_part)?;
let query = Self::decode_base64ish(query_part)?;
let separator = if path.contains('?') { "&" } else { "?" };
return Some(format!("{BASE_URL}{path}{separator}{query}&f=video.m3u8"));
}
let decoded = Self::decode_base64ish(&normalized)?;
if decoded.starts_with("http://") || decoded.starts_with("https://") {
return Some(decoded);
}
if decoded.starts_with('/') {
return Some(format!("{BASE_URL}{decoded}"));
}
None
}
}
impl crate::proxies::Proxy for VjavProxy {
async fn get_video_url(&self, url: String, requester: web::types::State<Requester>) -> String {
let Some(detail_url) = Self::normalize_detail_url(&url) else {
return String::new();
};
let Some(video_id) = Url::parse(&detail_url)
.ok()
.and_then(|value| Self::extract_video_id(value.path()))
else {
return String::new();
};
let api_url = format!("{BASE_URL}/api/videofile.php?video_id={video_id}&lifetime=8640000");
let mut requester = requester.get_ref().clone();
let text = requester.get(&api_url, None).await.unwrap_or_default();
if text.is_empty() {
return String::new();
}
let Ok(entries) = serde_json::from_str::<Vec<VideofileEntry>>(&text) else {
return String::new();
};
let mut fallback = String::new();
for entry in entries {
if entry.video_url.trim().is_empty() {
continue;
}
let Some(decoded) = Self::decode_video_url(&entry.video_url) else {
continue;
};
if entry.is_default == 1 {
return decoded;
}
if fallback.is_empty() {
fallback = decoded;
}
}
fallback
}
}

View File

@@ -10,6 +10,7 @@ use crate::proxies::pornhd3x::Pornhd3xProxy;
use crate::proxies::shooshtime::ShooshtimeProxy; use crate::proxies::shooshtime::ShooshtimeProxy;
use crate::proxies::spankbang::SpankbangProxy; use crate::proxies::spankbang::SpankbangProxy;
use crate::proxies::sxyprn::SxyprnProxy; use crate::proxies::sxyprn::SxyprnProxy;
use crate::proxies::vjav::VjavProxy;
use crate::proxies::*; use crate::proxies::*;
use crate::util::requester::Requester; use crate::util::requester::Requester;
@@ -49,6 +50,11 @@ pub fn config(cfg: &mut web::ServiceConfig) {
.route(web::post().to(proxy2redirect)) .route(web::post().to(proxy2redirect))
.route(web::get().to(proxy2redirect)), .route(web::get().to(proxy2redirect)),
) )
.service(
web::resource("/vjav/{endpoint}*")
.route(web::post().to(proxy2redirect))
.route(web::get().to(proxy2redirect)),
)
.service( .service(
web::resource("/pornhd3x/{endpoint}*") web::resource("/pornhd3x/{endpoint}*")
.route(web::post().to(proxy2redirect)) .route(web::post().to(proxy2redirect))
@@ -128,6 +134,7 @@ fn get_proxy(proxy: &str) -> Option<AnyProxy> {
"javtiful" => Some(AnyProxy::Javtiful(JavtifulProxy::new())), "javtiful" => Some(AnyProxy::Javtiful(JavtifulProxy::new())),
"hqporner" => Some(AnyProxy::Hqporner(HqpornerProxy::new())), "hqporner" => Some(AnyProxy::Hqporner(HqpornerProxy::new())),
"heavyfetish" => Some(AnyProxy::Heavyfetish(HeavyfetishProxy::new())), "heavyfetish" => Some(AnyProxy::Heavyfetish(HeavyfetishProxy::new())),
"vjav" => Some(AnyProxy::Vjav(VjavProxy::new())),
"pornhd3x" => Some(AnyProxy::Pornhd3x(Pornhd3xProxy::new())), "pornhd3x" => Some(AnyProxy::Pornhd3x(Pornhd3xProxy::new())),
"shooshtime" => Some(AnyProxy::Shooshtime(ShooshtimeProxy::new())), "shooshtime" => Some(AnyProxy::Shooshtime(ShooshtimeProxy::new())),
"pimpbunny" => Some(AnyProxy::Pimpbunny(PimpbunnyProxy::new())), "pimpbunny" => Some(AnyProxy::Pimpbunny(PimpbunnyProxy::new())),