From a0e0a8e4b1aec632306d76564fe149fe0739374c Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 17 Oct 2025 08:19:32 +0000 Subject: [PATCH] hanime updates --- src/providers/hanime.rs | 38 +++++++++------------------- src/proxies/hanimecdn.rs | 54 ++++++++++++++++++++++++++++++++++++++++ src/proxies/mod.rs | 2 +- src/proxy.rs | 6 ++++- src/util/requester.rs | 23 +++++++++++++++++ 5 files changed, 95 insertions(+), 28 deletions(-) create mode 100644 src/proxies/hanimecdn.rs diff --git a/src/providers/hanime.rs b/src/providers/hanime.rs index 51b5090..fbaf737 100644 --- a/src/providers/hanime.rs +++ b/src/providers/hanime.rs @@ -126,13 +126,13 @@ impl HanimeProvider { } } - async fn get_video_item(&self, hit: HanimeSearchResult, pool: DbPool) -> Result { + async fn get_video_item(&self, hit: HanimeSearchResult, pool: DbPool, options: ServerOptions) -> Result { let mut conn = pool.get().expect("couldn't get db connection from pool"); let db_result = db::get_video(&mut conn,format!("https://h.freeanimehentai.net/api/v8/video?id={}&", hit.slug.clone())); drop(conn); let id = hit.id.to_string(); let title = hit.name; - let thumb = hit.poster_url; + let thumb = hit.cover_url.replace("hanime-cdn.com", "hottub.spacemoehre.de"); let duration = (hit.duration_in_ms / 1000) as u32; // Convert ms to seconds let channel = "hanime".to_string(); // Placeholder, adjust as needed match db_result { @@ -142,6 +142,7 @@ impl HanimeProvider { .uploader(hit.brand) .views(hit.views as u32) .rating((hit.likes as f32 / (hit.likes + hit.dislikes)as f32) * 100 as f32) + .aspect_ratio(0.68) .formats(vec![videos::VideoFormat::new(video_url.clone(), "1080".to_string(), "m3u8".to_string())])); } Ok(None) => (), @@ -150,22 +151,10 @@ impl HanimeProvider { // return Err(format!("Error fetching video from database: {}", e).into()); } } - - let client = Client::builder() - .emulation(Emulation::Firefox136) - .build()?; let url = format!("https://h.freeanimehentai.net/api/v8/video?id={}&", hit.slug); - let response = client.get(url).send().await?; - let text = match response.status().is_success() { - true => { - response.text().await? - }, - false => { - print!("Failed to fetch video item: {}\n\n", response.status()); - return Err(format!("Failed to fetch video item: {}", response.status()).into()); - } - }; + let mut requester = options.requester.clone().unwrap(); + let text = requester.get(&url).await.unwrap(); let urls = text.split("\"servers\"").collect::>()[1]; let mut url_vec = vec![]; @@ -188,7 +177,7 @@ impl HanimeProvider { } - async fn get(&self, cache: VideoCache, pool: DbPool, page: u8, query: String, sort:String) -> Result> { + async fn get(&self, cache: VideoCache, pool: DbPool, page: u8, query: String, sort:String, options: ServerOptions) -> Result> { let index = format!("hanime:{}:{}:{}", query, page, sort); let order_by = match sort.contains("."){ true => sort.split(".").collect::>()[0].to_string(), @@ -218,12 +207,9 @@ impl HanimeProvider { .search_text(query.clone()) .order_by(order_by) .ordering(ordering); - let client = Client::builder() - .emulation(Emulation::Firefox136) - .build()?; - let response = client.post("https://search.htv-services.com/search") - .json(&search) - .send().await?; + + let mut requester = options.requester.clone().unwrap(); + let response = requester.post("https://search.htv-services.com/search", &search, vec![]).await.unwrap(); @@ -237,7 +223,7 @@ impl HanimeProvider { let hits_json: Vec = serde_json::from_str(hits.as_str()) .map_err(|e| format!("Failed to parse hits JSON: {}", e))?; // let timeout_duration = Duration::from_secs(120); - let futures = hits_json.into_iter().map(|el| self.get_video_item(el.clone(), pool.clone())); + let futures = hits_json.into_iter().map(|el| self.get_video_item(el.clone(), pool.clone(), options.clone())); let results: Vec> = join_all(futures).await; let video_items: Vec = results .into_iter() @@ -270,8 +256,8 @@ impl Provider for HanimeProvider { let _ = per_page; let _ = sort; let videos: std::result::Result, Error> = match query { - Some(q) => self.get(cache, pool, page.parse::().unwrap_or(1), q, sort).await, - None => self.get(cache, pool, page.parse::().unwrap_or(1), "".to_string(), sort).await, + Some(q) => self.get(cache, pool, page.parse::().unwrap_or(1), q, sort, options).await, + None => self.get(cache, pool, page.parse::().unwrap_or(1), "".to_string(), sort, options).await, }; match videos { Ok(v) => v, diff --git a/src/proxies/hanimecdn.rs b/src/proxies/hanimecdn.rs new file mode 100644 index 0000000..bbfb122 --- /dev/null +++ b/src/proxies/hanimecdn.rs @@ -0,0 +1,54 @@ +use ntex::{ + http::Response, + web::{self, HttpRequest, error}, +}; +use ntex::http::header::{CONTENT_LENGTH, CONTENT_TYPE}; + +use crate::util::requester::Requester; + +pub async fn get_image( + req: HttpRequest, + requester: web::types::State, +) -> Result { + let endpoint = req.match_info().query("endpoint").to_string(); + let image_url = format!("https://hanime-cdn.com/{}", endpoint); + + let upstream = match requester + .get_ref() + .clone() + .get_raw_with_headers( + image_url.as_str(), + vec![("Referer".to_string(), "https://hanime.tv/".to_string())], + ) + .await + { + Ok(response) => response, + Err(_) => return Ok(web::HttpResponse::NotFound().finish()), + }; + + let status = upstream.status(); + let headers = upstream.headers().clone(); + + // Read body from upstream + let bytes = upstream.bytes().await.map_err(error::ErrorBadGateway)?; + + // Build response and forward headers + let mut resp = Response::build(status); + + if let Some(ct) = headers.get(CONTENT_TYPE) { + if let Ok(ct_str) = ct.to_str() { + resp.set_header(CONTENT_TYPE, ct_str); + } + } + if let Some(cl) = headers.get(CONTENT_LENGTH) { + if let Ok(cl_str) = cl.to_str() { + resp.set_header(CONTENT_LENGTH, cl_str); + } + } + + // Either zero-copy to ntex Bytes... + // Ok(resp.body(NtexBytes::from(bytes))) + + // ...or simple & compatible: + Ok(resp.body(bytes.to_vec())) +} diff --git a/src/proxies/mod.rs b/src/proxies/mod.rs index 5e4d877..561aa24 100644 --- a/src/proxies/mod.rs +++ b/src/proxies/mod.rs @@ -3,6 +3,7 @@ use ntex::web; use crate::{proxies::sxyprn::SxyprnProxy, util::requester::Requester}; pub mod sxyprn; +pub mod hanimecdn; #[derive(Debug, Clone)] pub enum AnyProxy { @@ -19,7 +20,6 @@ pub trait Proxy { impl Proxy for AnyProxy { - async fn get_video_url( &self, url: String, diff --git a/src/proxy.rs b/src/proxy.rs index cfe105b..827b534 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -10,6 +10,11 @@ pub fn config(cfg: &mut web::ServiceConfig) { .route(web::post().to(sxyprn)) .route(web::get().to(sxyprn)), ) + .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("/videos") // // .route(web::get().to(videos_get)) @@ -23,7 +28,6 @@ async fn sxyprn(req: HttpRequest, requester: web::types::State,) -> Result { let proxy = get_proxy(req.uri().to_string().split("/").collect::>()[2]).unwrap(); let endpoint = req.match_info().query("endpoint").to_string(); - println!("/proxy/sxyprn: endpoint={:?}", endpoint); let video_url = match proxy.get_video_url(endpoint, requester).await{ url if url != "" => url, _ => "Error".to_string(), diff --git a/src/util/requester.rs b/src/util/requester.rs index 23957f9..c99fc2d 100644 --- a/src/util/requester.rs +++ b/src/util/requester.rs @@ -83,6 +83,29 @@ impl Requester { request.send().await } + pub async fn get_raw_with_headers(&mut self, url: &str, headers: Vec<(String, String)>) -> Result { + let client = Client::builder() + .cert_verification(false) + .emulation(Emulation::Firefox136) + .cookie_store(true) + .build() + .expect("Failed to create HTTP client"); + + let mut request = client.get(url).version(Version::HTTP_11); + + if self.proxy { + if let Ok(proxy_url) = env::var("BURP_URL") { + let proxy = Proxy::all(&proxy_url).unwrap(); + request = request.proxy(proxy); + } + } + // Set custom headers + for (key, value) in headers.iter() { + request = request.header(key, value); + } + request.send().await + } + pub async fn post(&mut self, url: &str, data: &S, headers: Vec<(String, String)>) -> Result where S: Serialize + ?Sized,