From 44cfb1f20882a01fa2bb464abfed0cc2b16ce822 Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 8 Feb 2026 19:38:03 +0000 Subject: [PATCH] hanime work in progress... --- src/providers/hanime.rs | 216 ++++++++++++++++++++++++++++++---------- 1 file changed, 162 insertions(+), 54 deletions(-) diff --git a/src/providers/hanime.rs b/src/providers/hanime.rs index 6901985..5b4d7b2 100644 --- a/src/providers/hanime.rs +++ b/src/providers/hanime.rs @@ -1,13 +1,14 @@ -use std::vec; use async_trait::async_trait; use error_chain::error_chain; use futures::future::join_all; +use serde_json::json; +use std::vec; +use crate::DbPool; use crate::db; use crate::providers::Provider; use crate::util::cache::VideoCache; use crate::videos::{self, ServerOptions, VideoItem}; -use crate::DbPool; error_chain! { foreign_links { @@ -17,7 +18,7 @@ error_chain! { } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -struct HanimeSearchRequest{ +struct HanimeSearchRequest { search_text: String, tags: Vec, tags_mode: String, @@ -25,7 +26,7 @@ struct HanimeSearchRequest{ blacklist: Vec, order_by: String, ordering: String, - page: u8 + page: u8, } #[allow(dead_code)] @@ -39,7 +40,7 @@ impl HanimeSearchRequest { blacklist: vec![], order_by: "created_at_unix".to_string(), ordering: "desc".to_string(), - page: 0 + page: 0, } } pub fn tags(mut self, tags: Vec) -> Self { @@ -74,20 +75,19 @@ impl HanimeSearchRequest { self.page = page; self } - } #[derive(serde::Serialize, serde::Deserialize, Debug)] -struct HanimeSearchResponse{ +struct HanimeSearchResponse { page: u8, - nbPages:u8, + nbPages: u8, nbHits: u32, hitsPerPage: u8, - hits: String + hits: String, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -struct HanimeSearchResult{ +struct HanimeSearchResult { id: u64, name: String, titles: Vec, @@ -109,7 +109,6 @@ struct HanimeSearchResult{ tags: Vec, created_at: u64, released_at: u64, - } #[derive(Debug, Clone)] @@ -125,24 +124,59 @@ impl HanimeProvider { } } - async fn get_video_item(&self, hit: HanimeSearchResult, pool: DbPool, options: ServerOptions) -> 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())); + 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.cover_url.replace("https://hanime-cdn.com", "https://hottub.spacemoehre.de/proxy/hanime-cdn"); + let thumb = hit.cover_url.replace( + "https://hanime-cdn.com", + "https://hottub.spacemoehre.de/proxy/hanime-cdn", + ); 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 { Ok(Some(video_url)) => { - return Ok(VideoItem::new(id, title, video_url.clone(), channel, thumb, duration) + if video_url != "https://streamable.cloud/hls/stream.m3u8" { + return Ok(VideoItem::new( + id, + title, + video_url.clone(), + channel, + thumb, + duration, + ) .tags(hit.tags) .uploader(hit.brand) .views(hit.views as u32) - .rating((hit.likes as f32 / (hit.likes + hit.dislikes)as f32) * 100 as f32) + .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())])); + .formats(vec![videos::VideoFormat::new( + video_url.clone(), + "1080".to_string(), + "m3u8".to_string(), + )])); + } else { + let _ = db::delete_video( + &mut pool.get().expect("couldn't get db connection from pool"), + format!( + "https://h.freeanimehentai.net/api/v8/video?id={}&", + hit.slug.clone() + ), + ); + } } Ok(None) => (), Err(e) => { @@ -150,49 +184,103 @@ impl HanimeProvider { // return Err(format!("Error fetching video from database: {}", e).into()); } } - let url = format!("https://h.freeanimehentai.net/api/v8/video?id={}&", hit.slug); - + let url = format!( + "https://cached.freeanimehentai.net/api/v8/guest/videos/{}/manifest", + id + ); + let mut requester = options.requester.clone().unwrap(); - let text = requester.get(&url, None).await.unwrap(); - - let urls = text.split("\"servers\"").collect::>()[1]; + let payload = json!({ + "width": 571, "height": 703, "ab": "kh" } + ); + let _ = requester + .post_json( + &format!( + "https://cached.freeanimehentai.net/api/v8/hentai_videos/{}/play", + hit.slug + ), + &payload, + vec![ + ("Origin".to_string(), "https://hanime.tv".to_string()), + ("Referer".to_string(), "https://hanime.tv/".to_string()), + ], + ) + .await; // Initial request to set cookies + ntex::time::sleep(ntex::time::Seconds(1)).await; + let text = requester + .get_raw_with_headers( + &url, + vec![ + ("Origin".to_string(), "https://hanime.tv".to_string()), + ("Referer".to_string(), "https://hanime.tv/".to_string()), + ], + ) + .await + .unwrap() + .text() + .await + .unwrap(); + if text.contains("Unautho") { + println!("Fetched video details for {}: {}", title, text); + return Err(Error::from("Unauthorized")); + } + let urls = text.split("streams").collect::>()[1]; let mut url_vec = vec![]; - for el in urls.split("\"url\":\"").collect::>(){ + for el in urls.split("\"url\":\"").collect::>() { let url = el.split("\"").collect::>()[0]; if !url.is_empty() && url.contains("m3u8") { url_vec.push(url.to_string()); } } let mut conn = pool.get().expect("couldn't get db connection from pool"); - let _ = db::insert_video(&mut conn, &format!("https://h.freeanimehentai.net/api/v8/video?id={}&", hit.slug.clone()), &url_vec[0].clone()); + let _ = db::insert_video( + &mut conn, + &format!( + "https://h.freeanimehentai.net/api/v8/video?id={}&", + hit.slug.clone() + ), + &url_vec[0].clone(), + ); drop(conn); - Ok(VideoItem::new(id, title, url_vec[0].clone(), channel, thumb, duration) - .tags(hit.tags) - .uploader(hit.brand) - .views(hit.views as u32) - .rating((hit.likes as f32 / (hit.likes + hit.dislikes)as f32) * 100 as f32) - .formats(vec![videos::VideoFormat::new(url_vec[0].clone(), "1080".to_string(), "m3u8".to_string())])) - + Ok( + VideoItem::new(id, title, url_vec[0].clone(), channel, thumb, duration) + .tags(hit.tags) + .uploader(hit.brand) + .views(hit.views as u32) + .rating((hit.likes as f32 / (hit.likes + hit.dislikes) as f32) * 100 as f32) + .formats(vec![videos::VideoFormat::new( + url_vec[0].clone(), + "1080".to_string(), + "m3u8".to_string(), + )]), + ) } - async fn get(&self, cache: VideoCache, pool: DbPool, page: u8, query: String, sort:String, options: ServerOptions) -> 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("."){ + let order_by = match sort.contains(".") { true => sort.split(".").collect::>()[0].to_string(), false => "created_at_unix".to_string(), }; - let ordering = match sort.contains("."){ + let ordering = match sort.contains(".") { true => sort.split(".").collect::>()[1].to_string(), false => "desc".to_string(), }; let old_items = match cache.get(&index) { Some((time, items)) => { - if time.elapsed().unwrap_or_default().as_secs() < 60 * 60 * 12 { + if time.elapsed().unwrap_or_default().as_secs() < 1 { //println!("Cache hit for URL: {}", index); return Ok(items.clone()); - } - else{ + } else { items.clone() } } @@ -202,15 +290,16 @@ impl HanimeProvider { }; let search = HanimeSearchRequest::new() - .page(page-1) + .page(page - 1) .search_text(query.clone()) .order_by(order_by) .ordering(ordering); - - let mut requester = options.requester.clone().unwrap(); - let response = requester.post_json("https://search.htv-services.com/search", &search, vec![]).await.unwrap(); - + let mut requester = options.requester.clone().unwrap(); + let response = requester + .post_json("https://search.htv-services.com/search", &search, vec![]) + .await + .unwrap(); let hits = match response.json::().await { Ok(resp) => resp.hits, @@ -222,18 +311,17 @@ 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(), options.clone())); - let results: Vec> = join_all(futures).await; - let video_items: Vec = results + let futures = hits_json .into_iter() - .filter_map(Result::ok) - .collect(); + .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().filter_map(Result::ok).collect(); if !video_items.is_empty() { - cache.remove(&index); - cache.insert(index.clone(), video_items.clone()); - } else { - return Ok(old_items); - } + cache.remove(&index); + cache.insert(index.clone(), video_items.clone()); + } else { + return Ok(old_items); + } Ok(video_items) } @@ -255,8 +343,28 @@ 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, options).await, - None => self.get(cache, pool, page.parse::().unwrap_or(1), "".to_string(), sort, options).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,