dynamic base url

This commit is contained in:
Simon
2026-03-10 18:45:32 +00:00
parent 2ad131f38f
commit 96926563b8
10 changed files with 118 additions and 43 deletions

View File

@@ -119,6 +119,7 @@ async fn status(req: HttpRequest) -> Result<impl web::Responder, web::Error> {
.and_then(|h| h.to_str().ok()) .and_then(|h| h.to_str().ok())
.unwrap_or_default() .unwrap_or_default()
.to_string(); .to_string();
let public_url_base = format!("{}://{}", req.connection_info().scheme(), host);
let mut status = Status::new(); let mut status = Status::new();
for (provider_name, provider) in ALL_PROVIDERS.iter() { for (provider_name, provider) in ALL_PROVIDERS.iter() {
@@ -126,7 +127,12 @@ async fn status(req: HttpRequest) -> Result<impl web::Responder, web::Error> {
provider.get_channel(clientversion.clone()) provider.get_channel(clientversion.clone())
})); }));
match channel_result { match channel_result {
Ok(Some(channel)) => status.add_channel(channel), Ok(Some(mut channel)) => {
if channel.favicon.starts_with('/') {
channel.favicon = format!("{}{}", public_url_base, channel.favicon);
}
status.add_channel(channel)
}
Ok(None) => {} Ok(None) => {}
Err(payload) => { Err(payload) => {
let panic_msg = panic_payload_to_string(payload); let panic_msg = panic_payload_to_string(payload);
@@ -134,7 +140,7 @@ async fn status(req: HttpRequest) -> Result<impl web::Responder, web::Error> {
} }
} }
} }
status.iconUrl = format!("http://{}/favicon.ico", host).to_string(); status.iconUrl = format!("{}/favicon.ico", public_url_base).to_string();
Ok(web::HttpResponse::Ok().json(&status)) Ok(web::HttpResponse::Ok().json(&status))
} }
@@ -245,12 +251,18 @@ async fn videos_post(
.to_string(); .to_string();
let duration = video_request.duration.as_deref().unwrap_or("").to_string(); let duration = video_request.duration.as_deref().unwrap_or("").to_string();
let sexuality = video_request.sexuality.as_deref().unwrap_or("").to_string(); let sexuality = video_request.sexuality.as_deref().unwrap_or("").to_string();
let public_url_base = format!(
"{}://{}",
req.connection_info().scheme(),
req.connection_info().host()
);
let options = ServerOptions { let options = ServerOptions {
featured: Some(featured), featured: Some(featured),
category: Some(category), category: Some(category),
sites: Some(sites), sites: Some(sites),
filter: Some(filter), filter: Some(filter),
language: Some(language), language: Some(language),
public_url_base: Some(public_url_base),
requester: Some(requester), requester: Some(requester),
network: Some(network), network: Some(network),
stars: Some(stars), stars: Some(stars),

View File

@@ -158,7 +158,7 @@ impl Provider for AllProvider {
name: "All".to_string(), name: "All".to_string(),
description: "Query from all sites of this Server".to_string(), description: "Query from all sites of this Server".to_string(),
premium: false, premium: false,
favicon: "https://hottub.spacemoehre.de/favicon.ico".to_string(), favicon: "/favicon.ico".to_string(),
status: "active".to_string(), status: "active".to_string(),
categories: vec![], categories: vec![],
options: vec![ChannelOption { options: vec![ChannelOption {

View File

@@ -213,9 +213,10 @@ impl HanimeProvider {
drop(conn); drop(conn);
let id = hit.id.to_string(); let id = hit.id.to_string();
let title = hit.name; let title = hit.name;
let thumb = hit.cover_url.replace( let thumb = crate::providers::build_proxy_url(
"https://hanime-cdn.com", &options,
"https://hottub.spacemoehre.de/proxy/hanime-cdn", "hanime-cdn",
&crate::providers::strip_url_scheme(&hit.cover_url),
); );
let duration = (hit.duration_in_ms / 1000) as u32; // Convert ms to seconds let duration = (hit.duration_in_ms / 1000) as u32; // Convert ms to seconds
let channel = "hanime".to_string(); // Placeholder, adjust as needed let channel = "hanime".to_string(); // Placeholder, adjust as needed

View File

@@ -188,7 +188,7 @@ impl HqpornerProvider {
.await .await
.map_err(|e| Error::from(format!("Request failed: {}", e)))?; .map_err(|e| Error::from(format!("Request failed: {}", e)))?;
let video_items = self.get_video_items_from_html(text, &mut requester).await; let video_items = self.get_video_items_from_html(text, &mut requester, &options).await;
if !video_items.is_empty() { if !video_items.is_empty() {
cache.insert(video_url, video_items.clone()); cache.insert(video_url, video_items.clone());
} }
@@ -234,7 +234,7 @@ impl HqpornerProvider {
.await .await
.map_err(|e| Error::from(format!("Request failed: {}", e)))?; .map_err(|e| Error::from(format!("Request failed: {}", e)))?;
let video_items = self.get_video_items_from_html(text, &mut requester).await; let video_items = self.get_video_items_from_html(text, &mut requester, &options).await;
if !video_items.is_empty() { if !video_items.is_empty() {
cache.insert(video_url, video_items.clone()); cache.insert(video_url, video_items.clone());
} }
@@ -245,6 +245,7 @@ impl HqpornerProvider {
&self, &self,
html: String, html: String,
requester: &mut Requester, requester: &mut Requester,
options: &ServerOptions,
) -> Vec<VideoItem> { ) -> Vec<VideoItem> {
if html.is_empty() || html.contains("404 Not Found") { if html.is_empty() || html.contains("404 Not Found") {
return vec![]; return vec![];
@@ -273,7 +274,7 @@ impl HqpornerProvider {
let Some(seg) = iter.next() else { let Some(seg) = iter.next() else {
break; break;
}; };
in_flight.push(self.get_video_item(seg, requester.clone())); in_flight.push(self.get_video_item(seg, requester.clone(), options));
} }
let Some(result) = in_flight.next().await else { let Some(result) = in_flight.next().await else {
@@ -312,7 +313,12 @@ impl HqpornerProvider {
items items
} }
async fn get_video_item(&self, seg: String, mut requester: Requester) -> Result<VideoItem> { async fn get_video_item(
&self,
seg: String,
mut requester: Requester,
options: &ServerOptions,
) -> Result<VideoItem> {
let video_url = format!( let video_url = format!(
"{}{}", "{}{}",
self.url, self.url,
@@ -351,7 +357,7 @@ impl HqpornerProvider {
format!("https://{}", thumb_raw.trim_start_matches('/')) format!("https://{}", thumb_raw.trim_start_matches('/'))
}; };
let thumb = match thumb_abs.strip_prefix("https://") { let thumb = match thumb_abs.strip_prefix("https://") {
Some(path) => format!("https://hottub.spacemoehre.de/proxy/hqporner-thumb/{path}"), Some(path) => crate::providers::build_proxy_url(options, "hqporner-thumb", path),
None => thumb_abs, None => thumb_abs,
}; };
let raw_duration = seg let raw_duration = seg

View File

@@ -155,7 +155,7 @@ impl JavtifulProvider {
return Ok(vec![]); return Ok(vec![]);
} }
let video_items: Vec<VideoItem> = self let video_items: Vec<VideoItem> = self
.get_video_items_from_html(text.clone(), &mut requester) .get_video_items_from_html(text.clone(), &mut requester, &options)
.await; .await;
if !video_items.is_empty() { if !video_items.is_empty() {
cache.remove(&video_url); cache.remove(&video_url);
@@ -223,7 +223,7 @@ impl JavtifulProvider {
return Ok(vec![]); return Ok(vec![]);
} }
let video_items: Vec<VideoItem> = self let video_items: Vec<VideoItem> = self
.get_video_items_from_html(text.clone(), &mut requester) .get_video_items_from_html(text.clone(), &mut requester, &options)
.await; .await;
if !video_items.is_empty() { if !video_items.is_empty() {
cache.remove(&video_url); cache.remove(&video_url);
@@ -238,6 +238,7 @@ impl JavtifulProvider {
&self, &self,
html: String, html: String,
requester: &mut Requester, requester: &mut Requester,
options: &ServerOptions,
) -> Vec<VideoItem> { ) -> Vec<VideoItem> {
if html.is_empty() || html.contains("404 Not Found") { if html.is_empty() || html.contains("404 Not Found") {
return vec![]; return vec![];
@@ -269,7 +270,7 @@ impl JavtifulProvider {
.split("card ") .split("card ")
.skip(1) .skip(1)
.filter(|seg| !seg.contains("SPONSOR")) .filter(|seg| !seg.contains("SPONSOR"))
.map(|el| self.get_video_item(el.to_string(), requester.clone())); .map(|el| self.get_video_item(el.to_string(), requester.clone(), options));
join_all(futures) join_all(futures)
.await .await
@@ -300,7 +301,12 @@ impl JavtifulProvider {
.collect() .collect()
} }
async fn get_video_item(&self, seg: String, mut requester: Requester) -> Result<VideoItem> { async fn get_video_item(
&self,
seg: String,
mut requester: Requester,
options: &ServerOptions,
) -> Result<VideoItem> {
let video_url = seg let video_url = seg
.split(" href=\"") .split(" href=\"")
.nth(1) .nth(1)
@@ -350,7 +356,7 @@ impl JavtifulProvider {
.unwrap_or("") .unwrap_or("")
.to_string(); .to_string();
let duration = parse_time_to_seconds(&raw_duration).unwrap_or(0) as u32; let duration = parse_time_to_seconds(&raw_duration).unwrap_or(0) as u32;
let (tags, formats, views) = self.extract_media(&video_url, &mut requester).await?; let (tags, formats, views) = self.extract_media(&video_url, &mut requester, options).await?;
if preview.len() == 0 { if preview.len() == 0 {
preview = format!("https://trailers.jav.si/preview/{id}.mp4"); preview = format!("https://trailers.jav.si/preview/{id}.mp4");
@@ -367,6 +373,7 @@ impl JavtifulProvider {
&self, &self,
url: &str, url: &str,
requester: &mut Requester, requester: &mut Requester,
options: &ServerOptions,
) -> Result<(Vec<String>, Vec<VideoFormat>, u32)> { ) -> Result<(Vec<String>, Vec<VideoFormat>, u32)> {
let text = requester let text = requester
.get(url, Some(Version::HTTP_2)) .get(url, Some(Version::HTTP_2))
@@ -413,7 +420,11 @@ impl JavtifulProvider {
.unwrap_or(0); .unwrap_or(0);
let quality = "1080p".to_string(); let quality = "1080p".to_string();
let video_url = url.replace("javtiful.com", "hottub.spacemoehre.de/proxy/javtiful"); let video_url = crate::providers::build_proxy_url(
options,
"javtiful",
&crate::providers::strip_url_scheme(url),
);
Ok(( Ok((
tags, tags,
vec![VideoFormat::new(video_url, quality, "video/mp4".into())], vec![VideoFormat::new(video_url, quality, "video/mp4".into())],

View File

@@ -286,6 +286,25 @@ pub fn requester_or_default(
} }
} }
pub fn strip_url_scheme(url: &str) -> String {
url.strip_prefix("https://")
.or_else(|| url.strip_prefix("http://"))
.unwrap_or(url)
.trim_start_matches('/')
.to_string()
}
pub fn build_proxy_url(options: &ServerOptions, proxy: &str, target: &str) -> String {
let target = target.trim_start_matches('/');
let base = options.public_url_base.as_deref().unwrap_or("").trim_end_matches('/');
if base.is_empty() {
format!("/proxy/{proxy}/{target}")
} else {
format!("{base}/proxy/{proxy}/{target}")
}
}
#[async_trait] #[async_trait]
pub trait Provider: Send + Sync { pub trait Provider: Send + Sync {
async fn get_videos( async fn get_videos(

View File

@@ -80,7 +80,8 @@ impl NoodlemagazineProvider {
.await .await
.unwrap_or_default(); .unwrap_or_default();
let items = self.get_video_items_from_html(text); let proxy_base_url = options.public_url_base.as_deref().unwrap_or_default();
let items = self.get_video_items_from_html(text, proxy_base_url);
if items.is_empty() { if items.is_empty() {
Ok(old_items) Ok(old_items)
@@ -117,7 +118,8 @@ impl NoodlemagazineProvider {
.await .await
.unwrap_or_default(); .unwrap_or_default();
let items = self.get_video_items_from_html(text); let proxy_base_url = options.public_url_base.as_deref().unwrap_or_default();
let items = self.get_video_items_from_html(text, proxy_base_url);
if items.is_empty() { if items.is_empty() {
Ok(old_items) Ok(old_items)
@@ -128,7 +130,7 @@ impl NoodlemagazineProvider {
} }
} }
fn get_video_items_from_html(&self, html: String) -> Vec<VideoItem> { fn get_video_items_from_html(&self, html: String, proxy_base_url: &str) -> Vec<VideoItem> {
if html.is_empty() || html.contains("404 Not Found") { if html.is_empty() || html.contains("404 Not Found") {
return vec![]; return vec![];
} }
@@ -148,21 +150,29 @@ impl NoodlemagazineProvider {
list.split("<div class=\"item\">") list.split("<div class=\"item\">")
.skip(1) .skip(1)
.filter_map(|segment| self.get_video_item(segment.to_string()).ok()) .filter_map(|segment| self.get_video_item(segment.to_string(), proxy_base_url).ok())
.collect() .collect()
} }
fn proxy_url(&self, video_url: &str) -> String { fn proxy_url(&self, proxy_base_url: &str, video_url: &str) -> String {
let target = video_url let target = video_url
.strip_prefix("https://") .strip_prefix("https://")
.or_else(|| video_url.strip_prefix("http://")) .or_else(|| video_url.strip_prefix("http://"))
.unwrap_or(video_url) .unwrap_or(video_url)
.trim_start_matches('/'); .trim_start_matches('/');
format!("https://hottub.spacemoehre.de/proxy/noodlemagazine/{target}") if proxy_base_url.is_empty() {
return format!("/proxy/noodlemagazine/{target}");
}
format!(
"{}/proxy/noodlemagazine/{}",
proxy_base_url.trim_end_matches('/'),
target
)
} }
fn get_video_item(&self, video_segment: String) -> Result<VideoItem> { fn get_video_item(&self, video_segment: String, proxy_base_url: &str) -> Result<VideoItem> {
let href = video_segment let href = video_segment
.split("<a href=\"") .split("<a href=\"")
.nth(1) .nth(1)
@@ -212,7 +222,7 @@ impl NoodlemagazineProvider {
.and_then(|s| s.split('<').next()) .and_then(|s| s.split('<').next())
.and_then(|v| parse_abbreviated_number(v.trim())) .and_then(|v| parse_abbreviated_number(v.trim()))
.unwrap_or(0); .unwrap_or(0);
let proxy_url = self.proxy_url(&video_url); let proxy_url = self.proxy_url(proxy_base_url, &video_url);
Ok(VideoItem::new( Ok(VideoItem::new(
id, id,
@@ -273,8 +283,11 @@ mod tests {
let provider = NoodlemagazineProvider::new(); let provider = NoodlemagazineProvider::new();
assert_eq!( assert_eq!(
provider.proxy_url("https://noodlemagazine.com/watch/-123_456"), provider.proxy_url(
"https://hottub.spacemoehre.de/proxy/noodlemagazine/noodlemagazine.com/watch/-123_456" "https://example.com",
"https://noodlemagazine.com/watch/-123_456"
),
"https://example.com/proxy/noodlemagazine/noodlemagazine.com/watch/-123_456"
); );
} }
@@ -294,12 +307,12 @@ mod tests {
>Show more</div> >Show more</div>
"#; "#;
let items = provider.get_video_items_from_html(html.to_string()); let items = provider.get_video_items_from_html(html.to_string(), "https://example.com");
assert_eq!(items.len(), 1); assert_eq!(items.len(), 1);
assert_eq!( assert_eq!(
items[0].url, items[0].url,
"https://hottub.spacemoehre.de/proxy/noodlemagazine/noodlemagazine.com/watch/-123_456" "https://example.com/proxy/noodlemagazine/noodlemagazine.com/watch/-123_456"
); );
assert_eq!(items[0].formats.as_ref().map(|f| f.len()), Some(1)); assert_eq!(items[0].formats.as_ref().map(|f| f.len()), Some(1));
} }

View File

@@ -172,12 +172,18 @@ impl SpankbangProvider {
format!("{}/{}", self.url, url.trim_start_matches("./")) format!("{}/{}", self.url, url.trim_start_matches("./"))
} }
fn proxy_url(&self, url: &str) -> String { fn proxy_url(&self, proxy_base_url: &str, url: &str) -> String {
let path = url let path = url
.strip_prefix(&self.url) .strip_prefix(&self.url)
.unwrap_or(url) .unwrap_or(url)
.trim_start_matches('/'); .trim_start_matches('/');
format!("https://hottub.spacemoehre.de/proxy/spankbang/{path}") if proxy_base_url.is_empty() {
return format!("/proxy/spankbang/{path}");
}
format!(
"{}/proxy/spankbang/{path}",
proxy_base_url.trim_end_matches('/')
)
} }
fn decode_html(text: &str) -> String { fn decode_html(text: &str) -> String {
@@ -258,6 +264,7 @@ impl SpankbangProvider {
views_selector: &Selector, views_selector: &Selector,
rating_selector: &Selector, rating_selector: &Selector,
meta_link_selector: &Selector, meta_link_selector: &Selector,
proxy_base_url: &str,
) -> Option<VideoItem> { ) -> Option<VideoItem> {
let card_html = card.html(); let card_html = card.html();
let card_text = Self::collapse_whitespace(&card.text().collect::<Vec<_>>().join(" ")); let card_text = Self::collapse_whitespace(&card.text().collect::<Vec<_>>().join(" "));
@@ -312,7 +319,7 @@ impl SpankbangProvider {
let mut item = VideoItem::new( let mut item = VideoItem::new(
id, id,
title, title,
self.proxy_url(&href), self.proxy_url(proxy_base_url, &href),
"spankbang".to_string(), "spankbang".to_string(),
thumb, thumb,
duration, duration,
@@ -344,7 +351,7 @@ impl SpankbangProvider {
Some(item) Some(item)
} }
fn get_video_items_from_html(&self, html: String) -> Vec<VideoItem> { fn get_video_items_from_html(&self, html: String, proxy_base_url: &str) -> Vec<VideoItem> {
let document = Html::parse_document(&html); let document = Html::parse_document(&html);
let video_list_selector = Selector::parse(r#"[data-testid="video-list"]"#).unwrap(); let video_list_selector = Selector::parse(r#"[data-testid="video-list"]"#).unwrap();
let card_selector = Selector::parse(r#"[data-testid="video-item"]"#).unwrap(); let card_selector = Selector::parse(r#"[data-testid="video-item"]"#).unwrap();
@@ -378,6 +385,7 @@ impl SpankbangProvider {
&views_selector, &views_selector,
&rating_selector, &rating_selector,
&meta_link_selector, &meta_link_selector,
proxy_base_url,
) { ) {
items.push(item); items.push(item);
} }
@@ -432,7 +440,8 @@ impl SpankbangProvider {
return Ok(old_items); return Ok(old_items);
} }
let video_items = self.get_video_items_from_html(text); let proxy_base_url = options.public_url_base.as_deref().unwrap_or_default();
let video_items = self.get_video_items_from_html(text, proxy_base_url);
if !video_items.is_empty() { if !video_items.is_empty() {
cache.remove(&video_url); cache.remove(&video_url);
cache.insert(video_url.clone(), video_items.clone()); cache.insert(video_url.clone(), video_items.clone());
@@ -489,7 +498,8 @@ impl SpankbangProvider {
return Ok(old_items); return Ok(old_items);
} }
let video_items = self.get_video_items_from_html(text); let proxy_base_url = options.public_url_base.as_deref().unwrap_or_default();
let video_items = self.get_video_items_from_html(text, proxy_base_url);
if !video_items.is_empty() { if !video_items.is_empty() {
cache.remove(&video_url); cache.remove(&video_url);
cache.insert(video_url.clone(), video_items.clone()); cache.insert(video_url.clone(), video_items.clone());
@@ -634,13 +644,13 @@ mod tests {
</div> </div>
"#; "#;
let items = provider.get_video_items_from_html(html.to_string()); let items = provider.get_video_items_from_html(html.to_string(), "https://example.com");
assert_eq!(items.len(), 1); assert_eq!(items.len(), 1);
assert_eq!(items[0].id, "6597754"); assert_eq!(items[0].id, "6597754");
assert_eq!(items[0].title, "Adriana's Fleshlight Insertion"); assert_eq!(items[0].title, "Adriana's Fleshlight Insertion");
assert_eq!( assert_eq!(
items[0].url, items[0].url,
"https://hottub.spacemoehre.de/proxy/spankbang/3xeuy/video/adriana+s+fleshlight+insertion" "https://example.com/proxy/spankbang/3xeuy/video/adriana+s+fleshlight+insertion"
); );
assert_eq!( assert_eq!(
items[0].thumb, items[0].thumb,
@@ -691,7 +701,7 @@ mod tests {
</div> </div>
"#; "#;
let items = provider.get_video_items_from_html(html.to_string()); let items = provider.get_video_items_from_html(html.to_string(), "https://example.com");
assert_eq!(items.len(), 1); assert_eq!(items.len(), 1);
assert_eq!(items[0].id, "2"); assert_eq!(items[0].id, "2");
assert_eq!(items[0].title, "Free video"); assert_eq!(items[0].title, "Free video");
@@ -728,7 +738,7 @@ mod tests {
</div> </div>
"#; "#;
let items = provider.get_video_items_from_html(html.to_string()); let items = provider.get_video_items_from_html(html.to_string(), "https://example.com");
assert_eq!(items.len(), 1); assert_eq!(items.len(), 1);
assert_eq!(items[0].id, "222"); assert_eq!(items[0].id, "222");
assert_eq!(items[0].title, "Right result"); assert_eq!(items[0].title, "Right result");

View File

@@ -162,7 +162,7 @@ impl SxyprnProvider {
}; };
// Pass a reference to options if needed, or reconstruct as needed // Pass a reference to options if needed, or reconstruct as needed
let video_items = match self let video_items = match self
.get_video_items_from_html(text.clone(), pool, requester) .get_video_items_from_html(text.clone(), pool, requester, &options)
.await .await
{ {
Ok(items) => items, Ok(items) => items,
@@ -247,7 +247,7 @@ impl SxyprnProvider {
}; };
let video_items = match self let video_items = match self
.get_video_items_from_html(text.clone(), pool, requester) .get_video_items_from_html(text.clone(), pool, requester, &options)
.await .await
{ {
Ok(items) => items, Ok(items) => items,
@@ -284,6 +284,7 @@ impl SxyprnProvider {
html: String, html: String,
_pool: DbPool, _pool: DbPool,
_requester: Requester, _requester: Requester,
options: &ServerOptions,
) -> Result<Vec<VideoItem>> { ) -> Result<Vec<VideoItem>> {
if html.is_empty() { if html.is_empty() {
return Ok(vec![]); return Ok(vec![]);
@@ -313,7 +314,8 @@ impl SxyprnProvider {
.ok_or_else(|| ErrorKind::Parse("failed to extract /post/ url".into()))? .ok_or_else(|| ErrorKind::Parse("failed to extract /post/ url".into()))?
.to_string(); .to_string();
let video_url = format!("https://hottub.spacemoehre.de/proxy/sxyprn/post/{}", url); let video_url =
crate::providers::build_proxy_url(options, "sxyprn", &format!("post/{}", url));
// title parts // title parts
let title_parts = video_segment let title_parts = video_segment
@@ -421,7 +423,7 @@ impl SxyprnProvider {
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let video_item_url = stream_urls.first().cloned().unwrap_or_else(|| { let video_item_url = stream_urls.first().cloned().unwrap_or_else(|| {
format!("https://hottub.spacemoehre.de/proxy/sxyprn/post/{}", id) crate::providers::build_proxy_url(options, "sxyprn", &format!("post/{}", id))
}); });
let mut video_item = VideoItem::new( let mut video_item = VideoItem::new(

View File

@@ -60,6 +60,7 @@ pub struct ServerOptions {
pub sites: Option<String>, // pub sites: Option<String>, //
pub filter: Option<String>, pub filter: Option<String>,
pub language: Option<String>, // "en" pub language: Option<String>, // "en"
pub public_url_base: Option<String>,
pub requester: Option<Requester>, pub requester: Option<Requester>,
pub network: Option<String>, // pub network: Option<String>, //
pub stars: Option<String>, // pub stars: Option<String>, //