From f9009874345906ae0ea759bfe079b8090e278aee Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 19 Oct 2026 07:39:48 +0000 Subject: [PATCH] archivebate: verified app clients get the proxy URL as item url and no formats Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01MUYdCh2nCVsQYgJAT7gwSi --- src/providers/archivebate.rs | 101 +++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 41 deletions(-) diff --git a/src/providers/archivebate.rs b/src/providers/archivebate.rs index d6527c3..6458d76 100644 --- a/src/providers/archivebate.rs +++ b/src/providers/archivebate.rs @@ -815,49 +815,19 @@ impl ArchivebateProvider { item } - /// For Hot Tub app clients: resolve every item's real media URL at the same - /// time (one task per item, no concurrency cap) and hand the app that URL - /// with the headers the CDN is played with, instead of the lazy - /// `/proxy/archivebate` redirect. Items that fail or time out keep the - /// proxied format, which re-resolves at playback time. - async fn resolve_direct_formats( - videos: Vec, - options: &ServerOptions, - ) -> Vec { - let tasks: Vec<_> = videos - .iter() - .map(|video| { - let detail_url = video.url.clone(); - let mut requester = - requester_or_default(options, CHANNEL_ID, "archivebate.resolve_direct"); - tokio::spawn(async move { - timeout( - StdDuration::from_secs(25), - crate::proxies::archivebate::resolve_media(&mut requester, &detail_url), - ) - .await - .ok() - .flatten() - }) - }) - .collect(); - - let resolved = futures::future::join_all(tasks).await; + /// For verified Hot Tub app clients: no `formats` at all. The item's `url` + /// is the lazy `/proxy/archivebate` redirect, which the app plays directly + /// (it resolves the media at playback time). + fn proxy_as_item_url(videos: Vec, options: &ServerOptions) -> Vec { videos .into_iter() - .zip(resolved) - .map(|(mut video, resolved)| match resolved.ok().flatten() { - Some(media) => { - video.formats = Some(vec![Self::video_format_with_headers( - VideoFormat::new(media.media_url, "source".to_string(), "mp4".to_string()), - vec![ - ("Referer".to_string(), media.embed_url), - ("User-Agent".to_string(), FIREFOX_UA.to_string()), - ], - )]); - video + .map(|mut video| { + let proxy_url = Self::proxied_video(options, &video.url); + if !proxy_url.is_empty() { + video.url = proxy_url; } - None => Self::with_default_format(video, options), + video.formats = None; + video }) .collect() } @@ -1261,7 +1231,7 @@ impl Provider for ArchivebateProvider { .map(ClientVersion::is_verified_hottub) .unwrap_or(false); if is_app_client { - return Self::resolve_direct_formats(videos, &options).await; + return Self::proxy_as_item_url(videos, &options); } stream::iter(videos.into_iter().map(|video| { let provider = self.clone(); @@ -1293,3 +1263,52 @@ impl Provider for ArchivebateProvider { Some(self.build_channel(clientversion)) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn options() -> ServerOptions { + ServerOptions { + client_version: None, + featured: None, + category: None, + sites: None, + filter: None, + language: None, + public_url_base: Some("http://hottub.example".to_string()), + requester: None, + network: None, + stars: None, + categories: None, + duration: None, + sort: None, + sexuality: None, + } + } + + #[test] + fn app_clients_get_the_proxy_url_and_no_formats() { + let mut item = VideoItem::new( + "archivebate:1".to_string(), + "t".to_string(), + "https://archivebate.com/watch/1".to_string(), + CHANNEL_ID.to_string(), + "https://cdn.example/t.jpg".to_string(), + 10, + ); + item.formats = Some(vec![VideoFormat::new( + "x".to_string(), + "source".to_string(), + "mp4".to_string(), + )]); + + let out = ArchivebateProvider::proxy_as_item_url(vec![item], &options()); + assert_eq!(out.len(), 1); + assert_eq!( + out[0].url, + "http://hottub.example/proxy/archivebate/archivebate.com/watch/1" + ); + assert!(out[0].formats.is_none()); + } +}