pornhd3x
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
use crate::DbPool;
|
use crate::DbPool;
|
||||||
use crate::api::ClientVersion;
|
use crate::api::ClientVersion;
|
||||||
use crate::providers::{
|
use crate::providers::{
|
||||||
Provider, report_provider_error, report_provider_error_background, requester_or_default,
|
Provider, build_proxy_url, report_provider_error, report_provider_error_background,
|
||||||
|
requester_or_default, strip_url_scheme,
|
||||||
};
|
};
|
||||||
use crate::status::*;
|
use crate::status::*;
|
||||||
use crate::util::cache::VideoCache;
|
use crate::util::cache::VideoCache;
|
||||||
@@ -395,6 +396,39 @@ impl Pornhd3xProvider {
|
|||||||
.map(|value| value.id.clone())
|
.map(|value| value.id.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_allowed_detail_url(&self, value: &str) -> bool {
|
||||||
|
let normalized = self.normalize_url(value);
|
||||||
|
let Some(url) = Url::parse(&normalized).ok() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if url.scheme() != "https" {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let Some(host) = url.host_str() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
(host == "www.pornhd3x.tv" || host == "pornhd3x.tv") && url.path().starts_with("/movies/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proxied_video(
|
||||||
|
&self,
|
||||||
|
options: &ServerOptions,
|
||||||
|
detail_url: &str,
|
||||||
|
quality: Option<&str>,
|
||||||
|
) -> String {
|
||||||
|
if detail_url.is_empty() || !self.is_allowed_detail_url(detail_url) {
|
||||||
|
return detail_url.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut target = strip_url_scheme(detail_url);
|
||||||
|
if let Some(quality) = quality.map(str::trim).filter(|quality| !quality.is_empty()) {
|
||||||
|
target.push_str("/__quality__/");
|
||||||
|
target.push_str(&quality.replace(' ', "%20"));
|
||||||
|
}
|
||||||
|
|
||||||
|
build_proxy_url(options, CHANNEL_ID, &target)
|
||||||
|
}
|
||||||
|
|
||||||
fn filters_need_refresh(&self) -> bool {
|
fn filters_need_refresh(&self) -> bool {
|
||||||
let categories_len = self
|
let categories_len = self
|
||||||
.categories
|
.categories
|
||||||
@@ -967,7 +1001,12 @@ impl Pornhd3xProvider {
|
|||||||
Ok(serde_json::from_str::<Value>(&response)?)
|
Ok(serde_json::from_str::<Value>(&response)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_formats(&self, value: &Value) -> Vec<VideoFormat> {
|
fn build_formats(
|
||||||
|
&self,
|
||||||
|
value: &Value,
|
||||||
|
options: &ServerOptions,
|
||||||
|
detail_url: &str,
|
||||||
|
) -> Vec<VideoFormat> {
|
||||||
let mut formats = Vec::new();
|
let mut formats = Vec::new();
|
||||||
for playlist in value
|
for playlist in value
|
||||||
.get("playlist")
|
.get("playlist")
|
||||||
@@ -994,7 +1033,8 @@ impl Pornhd3xProvider {
|
|||||||
.unwrap_or("HLS")
|
.unwrap_or("HLS")
|
||||||
.to_string();
|
.to_string();
|
||||||
let format_name = if url.contains(".m3u8") { "hls" } else { "mp4" };
|
let format_name = if url.contains(".m3u8") { "hls" } else { "mp4" };
|
||||||
let format = VideoFormat::new(url, quality.clone(), format_name.to_string())
|
let format_url = self.proxied_video(options, detail_url, Some(&quality));
|
||||||
|
let format = VideoFormat::new(format_url, quality.clone(), format_name.to_string())
|
||||||
.format_id(quality.to_ascii_lowercase())
|
.format_id(quality.to_ascii_lowercase())
|
||||||
.format_note(quality);
|
.format_note(quality);
|
||||||
formats.push(format);
|
formats.push(format);
|
||||||
@@ -1035,8 +1075,12 @@ impl Pornhd3xProvider {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut formats = self.build_formats(&source_payload);
|
let mut formats = self.build_formats(&source_payload, options, &stub.detail_url);
|
||||||
let direct_url = formats
|
let proxied_url = self.proxied_video(options, &stub.detail_url, None);
|
||||||
|
let direct_url = if !proxied_url.is_empty() {
|
||||||
|
Some(proxied_url)
|
||||||
|
} else {
|
||||||
|
formats
|
||||||
.first()
|
.first()
|
||||||
.map(|format| format.url.clone())
|
.map(|format| format.url.clone())
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
@@ -1045,7 +1089,8 @@ impl Pornhd3xProvider {
|
|||||||
.or_else(|| source_payload.get("embedUrl"))
|
.or_else(|| source_payload.get("embedUrl"))
|
||||||
.and_then(|value| value.as_str())
|
.and_then(|value| value.as_str())
|
||||||
.map(|value| self.normalize_url(value))
|
.map(|value| self.normalize_url(value))
|
||||||
});
|
})
|
||||||
|
};
|
||||||
|
|
||||||
let Some(url) = direct_url else {
|
let Some(url) = direct_url else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -1295,4 +1340,41 @@ mod tests {
|
|||||||
.is_some()
|
.is_some()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builds_proxied_video_urls() {
|
||||||
|
let provider = Pornhd3xProvider::new_for_tests();
|
||||||
|
let options = ServerOptions {
|
||||||
|
featured: None,
|
||||||
|
category: None,
|
||||||
|
sites: None,
|
||||||
|
filter: None,
|
||||||
|
language: None,
|
||||||
|
public_url_base: Some("https://example.com".to_string()),
|
||||||
|
requester: None,
|
||||||
|
network: None,
|
||||||
|
stars: None,
|
||||||
|
categories: None,
|
||||||
|
duration: None,
|
||||||
|
sort: None,
|
||||||
|
sexuality: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
provider.proxied_video(
|
||||||
|
&options,
|
||||||
|
"https://www.pornhd3x.tv/movies/example-video",
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
"https://example.com/proxy/pornhd3x/www.pornhd3x.tv/movies/example-video"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
provider.proxied_video(
|
||||||
|
&options,
|
||||||
|
"https://www.pornhd3x.tv/movies/example-video",
|
||||||
|
Some("720p"),
|
||||||
|
),
|
||||||
|
"https://example.com/proxy/pornhd3x/www.pornhd3x.tv/movies/example-video/__quality__/720p"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::proxies::doodstream::DoodstreamProxy;
|
use crate::proxies::doodstream::DoodstreamProxy;
|
||||||
|
use crate::proxies::pornhd3x::Pornhd3xProxy;
|
||||||
use ntex::web;
|
use ntex::web;
|
||||||
|
|
||||||
use crate::proxies::pimpbunny::PimpbunnyProxy;
|
use crate::proxies::pimpbunny::PimpbunnyProxy;
|
||||||
@@ -15,6 +16,7 @@ pub mod pimpbunny;
|
|||||||
pub mod pimpbunnythumb;
|
pub mod pimpbunnythumb;
|
||||||
pub mod porndish;
|
pub mod porndish;
|
||||||
pub mod porndishthumb;
|
pub mod porndishthumb;
|
||||||
|
pub mod pornhd3x;
|
||||||
pub mod spankbang;
|
pub mod spankbang;
|
||||||
pub mod sxyprn;
|
pub mod sxyprn;
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ pub enum AnyProxy {
|
|||||||
Doodstream(DoodstreamProxy),
|
Doodstream(DoodstreamProxy),
|
||||||
Sxyprn(SxyprnProxy),
|
Sxyprn(SxyprnProxy),
|
||||||
Javtiful(javtiful::JavtifulProxy),
|
Javtiful(javtiful::JavtifulProxy),
|
||||||
|
Pornhd3x(Pornhd3xProxy),
|
||||||
Pimpbunny(PimpbunnyProxy),
|
Pimpbunny(PimpbunnyProxy),
|
||||||
Porndish(PorndishProxy),
|
Porndish(PorndishProxy),
|
||||||
Spankbang(SpankbangProxy),
|
Spankbang(SpankbangProxy),
|
||||||
@@ -38,6 +41,7 @@ impl Proxy for AnyProxy {
|
|||||||
AnyProxy::Doodstream(p) => p.get_video_url(url, requester).await,
|
AnyProxy::Doodstream(p) => p.get_video_url(url, requester).await,
|
||||||
AnyProxy::Sxyprn(p) => p.get_video_url(url, requester).await,
|
AnyProxy::Sxyprn(p) => p.get_video_url(url, requester).await,
|
||||||
AnyProxy::Javtiful(p) => p.get_video_url(url, requester).await,
|
AnyProxy::Javtiful(p) => p.get_video_url(url, requester).await,
|
||||||
|
AnyProxy::Pornhd3x(p) => p.get_video_url(url, requester).await,
|
||||||
AnyProxy::Pimpbunny(p) => p.get_video_url(url, requester).await,
|
AnyProxy::Pimpbunny(p) => p.get_video_url(url, requester).await,
|
||||||
AnyProxy::Porndish(p) => p.get_video_url(url, requester).await,
|
AnyProxy::Porndish(p) => p.get_video_url(url, requester).await,
|
||||||
AnyProxy::Spankbang(p) => p.get_video_url(url, requester).await,
|
AnyProxy::Spankbang(p) => p.get_video_url(url, requester).await,
|
||||||
|
|||||||
243
src/proxies/pornhd3x.rs
Normal file
243
src/proxies/pornhd3x.rs
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
|
use ntex::web;
|
||||||
|
use regex::Regex;
|
||||||
|
use serde_json::Value;
|
||||||
|
use url::Url;
|
||||||
|
use wreq::Version;
|
||||||
|
|
||||||
|
use crate::util::requester::Requester;
|
||||||
|
|
||||||
|
const BASE_URL: &str = "https://www.pornhd3x.tv";
|
||||||
|
const SOURCE_SECRET: &str = "98126avrbi6m49vd7shxkn985";
|
||||||
|
const SOURCE_COOKIE_PREFIX: &str = "826avrbi6m49vd7shxkn985m";
|
||||||
|
const SOURCE_COOKIE_SUFFIX: &str = "k06twz87wwxtp3dqiicks2df";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Pornhd3xProxy {
|
||||||
|
source_counter: Arc<AtomicU32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pornhd3xProxy {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
source_counter: Arc::new(AtomicU32::new(0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_detail_request(endpoint: &str) -> Option<(String, Option<String>)> {
|
||||||
|
let endpoint = endpoint.trim().trim_start_matches('/');
|
||||||
|
if endpoint.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (detail_part, quality) = match endpoint.split_once("/__quality__/") {
|
||||||
|
Some((detail, quality)) => {
|
||||||
|
(detail, Some(quality.replace("%20", " ").trim().to_string()))
|
||||||
|
}
|
||||||
|
None => (endpoint, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let detail_url =
|
||||||
|
if detail_part.starts_with("http://") || detail_part.starts_with("https://") {
|
||||||
|
detail_part.to_string()
|
||||||
|
} else {
|
||||||
|
format!("https://{}", detail_part.trim_start_matches('/'))
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::is_allowed_detail_url(&detail_url)
|
||||||
|
.then_some((detail_url, quality.filter(|value| !value.is_empty())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_allowed_detail_url(url: &str) -> bool {
|
||||||
|
let Some(url) = Url::parse(url).ok() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if url.scheme() != "https" {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let Some(host) = url.host_str() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
(host == "www.pornhd3x.tv" || host == "pornhd3x.tv") && url.path().starts_with("/movies/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_url(raw: &str) -> String {
|
||||||
|
let value = raw.trim();
|
||||||
|
if value.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
if value.starts_with("//") {
|
||||||
|
return format!("https:{value}");
|
||||||
|
}
|
||||||
|
if value.starts_with('/') {
|
||||||
|
return format!("{BASE_URL}{value}");
|
||||||
|
}
|
||||||
|
if value.starts_with("http://") {
|
||||||
|
return value.replacen("http://", "https://", 1);
|
||||||
|
}
|
||||||
|
value.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_episode_id(html: &str) -> Option<String> {
|
||||||
|
Regex::new(r#"(?is)(?:id=["']uuid["'][^>]*value=["']|episode-id=["'])([A-Za-z0-9]+)"#)
|
||||||
|
.ok()?
|
||||||
|
.captures(html)
|
||||||
|
.and_then(|captures| captures.get(1))
|
||||||
|
.map(|value| value.as_str().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_source_cookie_name(episode_id: &str) -> String {
|
||||||
|
format!("{SOURCE_COOKIE_PREFIX}{episode_id}{SOURCE_COOKIE_SUFFIX}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_source_hash(episode_id: &str, nonce: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"{:x}",
|
||||||
|
md5::compute(format!("{episode_id}{nonce}{SOURCE_SECRET}"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_source_request(&self) -> (u32, String) {
|
||||||
|
let count = self.source_counter.fetch_add(1, Ordering::Relaxed) + 1;
|
||||||
|
let nonce = format!("{:06x}", count % 0xFF_FFFF);
|
||||||
|
(count, nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_sources(
|
||||||
|
&self,
|
||||||
|
requester: &mut Requester,
|
||||||
|
referer: &str,
|
||||||
|
episode_id: &str,
|
||||||
|
) -> Option<Value> {
|
||||||
|
let (count, nonce) = self.next_source_request();
|
||||||
|
let source_url = format!(
|
||||||
|
"{BASE_URL}/ajax/get_sources/{episode_id}/{hash}?count={count}&mobile=true",
|
||||||
|
hash = Self::build_source_hash(episode_id, &nonce),
|
||||||
|
);
|
||||||
|
let existing_cookie = requester.cookie_header_for_url(&source_url);
|
||||||
|
let cookie_value = format!("{}={nonce}", Self::build_source_cookie_name(episode_id));
|
||||||
|
let combined_cookie = match existing_cookie {
|
||||||
|
Some(existing) if !existing.trim().is_empty() => format!("{existing}; {cookie_value}"),
|
||||||
|
_ => cookie_value,
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = requester
|
||||||
|
.get_with_headers(
|
||||||
|
&source_url,
|
||||||
|
vec![
|
||||||
|
("Cookie".to_string(), combined_cookie),
|
||||||
|
("Referer".to_string(), referer.to_string()),
|
||||||
|
("X-Requested-With".to_string(), "XMLHttpRequest".to_string()),
|
||||||
|
(
|
||||||
|
"Accept".to_string(),
|
||||||
|
"application/json, text/javascript, */*; q=0.01".to_string(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
Some(Version::HTTP_11),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
serde_json::from_str::<Value>(&response).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_source_url(payload: &Value, quality: Option<&str>) -> Option<String> {
|
||||||
|
let sources = payload
|
||||||
|
.get("playlist")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.flat_map(|playlist| {
|
||||||
|
playlist
|
||||||
|
.get("sources")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if let Some(quality) = quality {
|
||||||
|
let quality = quality.trim().to_ascii_lowercase();
|
||||||
|
for source in &sources {
|
||||||
|
let label = source
|
||||||
|
.get("label")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.trim()
|
||||||
|
.to_ascii_lowercase();
|
||||||
|
if label == quality {
|
||||||
|
let file = source.get("file").and_then(Value::as_str)?;
|
||||||
|
return Some(Self::normalize_url(file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for source in sources {
|
||||||
|
let Some(file) = source.get("file").and_then(Value::as_str) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let url = Self::normalize_url(file);
|
||||||
|
if !url.is_empty() {
|
||||||
|
return Some(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::proxies::Proxy for Pornhd3xProxy {
|
||||||
|
async fn get_video_url(&self, url: String, requester: web::types::State<Requester>) -> String {
|
||||||
|
let Some((detail_url, quality)) = Self::normalize_detail_request(&url) else {
|
||||||
|
return String::new();
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut requester = requester.get_ref().clone();
|
||||||
|
let detail_html = match requester.get(&detail_url, Some(Version::HTTP_11)).await {
|
||||||
|
Ok(text) => text,
|
||||||
|
Err(_) => return String::new(),
|
||||||
|
};
|
||||||
|
let Some(episode_id) = Self::extract_episode_id(&detail_html) else {
|
||||||
|
return String::new();
|
||||||
|
};
|
||||||
|
let Some(payload) = self
|
||||||
|
.fetch_sources(&mut requester, &detail_url, &episode_id)
|
||||||
|
.await
|
||||||
|
else {
|
||||||
|
return String::new();
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::select_source_url(&payload, quality.as_deref()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Pornhd3xProxy;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalizes_detail_endpoint_and_quality() {
|
||||||
|
let (url, quality) = Pornhd3xProxy::normalize_detail_request(
|
||||||
|
"www.pornhd3x.tv/movies/example-video/__quality__/720p",
|
||||||
|
)
|
||||||
|
.expect("proxy target should parse");
|
||||||
|
|
||||||
|
assert_eq!(url, "https://www.pornhd3x.tv/movies/example-video");
|
||||||
|
assert_eq!(quality.as_deref(), Some("720p"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extracts_episode_id_from_detail_markup() {
|
||||||
|
let html = r#"
|
||||||
|
<input id="uuid" value="49Q27JL3HCPVNJQN">
|
||||||
|
<a class="btn-eps" episode-id="OTHER"></a>
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Pornhd3xProxy::extract_episode_id(html).as_deref(),
|
||||||
|
Some("49Q27JL3HCPVNJQN")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ use crate::proxies::doodstream::DoodstreamProxy;
|
|||||||
use crate::proxies::javtiful::JavtifulProxy;
|
use crate::proxies::javtiful::JavtifulProxy;
|
||||||
use crate::proxies::pimpbunny::PimpbunnyProxy;
|
use crate::proxies::pimpbunny::PimpbunnyProxy;
|
||||||
use crate::proxies::porndish::PorndishProxy;
|
use crate::proxies::porndish::PorndishProxy;
|
||||||
|
use crate::proxies::pornhd3x::Pornhd3xProxy;
|
||||||
use crate::proxies::spankbang::SpankbangProxy;
|
use crate::proxies::spankbang::SpankbangProxy;
|
||||||
use crate::proxies::sxyprn::SxyprnProxy;
|
use crate::proxies::sxyprn::SxyprnProxy;
|
||||||
use crate::proxies::*;
|
use crate::proxies::*;
|
||||||
@@ -35,6 +36,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("/pornhd3x/{endpoint}*")
|
||||||
|
.route(web::post().to(proxy2redirect))
|
||||||
|
.route(web::get().to(proxy2redirect)),
|
||||||
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/pimpbunny/{endpoint}*")
|
web::resource("/pimpbunny/{endpoint}*")
|
||||||
.route(web::post().to(proxy2redirect))
|
.route(web::post().to(proxy2redirect))
|
||||||
@@ -92,6 +98,7 @@ fn get_proxy(proxy: &str) -> Option<AnyProxy> {
|
|||||||
"doodstream" => Some(AnyProxy::Doodstream(DoodstreamProxy::new())),
|
"doodstream" => Some(AnyProxy::Doodstream(DoodstreamProxy::new())),
|
||||||
"sxyprn" => Some(AnyProxy::Sxyprn(SxyprnProxy::new())),
|
"sxyprn" => Some(AnyProxy::Sxyprn(SxyprnProxy::new())),
|
||||||
"javtiful" => Some(AnyProxy::Javtiful(JavtifulProxy::new())),
|
"javtiful" => Some(AnyProxy::Javtiful(JavtifulProxy::new())),
|
||||||
|
"pornhd3x" => Some(AnyProxy::Pornhd3x(Pornhd3xProxy::new())),
|
||||||
"pimpbunny" => Some(AnyProxy::Pimpbunny(PimpbunnyProxy::new())),
|
"pimpbunny" => Some(AnyProxy::Pimpbunny(PimpbunnyProxy::new())),
|
||||||
"porndish" => Some(AnyProxy::Porndish(PorndishProxy::new())),
|
"porndish" => Some(AnyProxy::Porndish(PorndishProxy::new())),
|
||||||
"spankbang" => Some(AnyProxy::Spankbang(SpankbangProxy::new())),
|
"spankbang" => Some(AnyProxy::Spankbang(SpankbangProxy::new())),
|
||||||
|
|||||||
Reference in New Issue
Block a user