hanime work in progress...

This commit is contained in:
Simon
2026-02-08 19:38:03 +00:00
parent 310dfd71e9
commit 44cfb1f208

View File

@@ -1,13 +1,14 @@
use std::vec;
use async_trait::async_trait; use async_trait::async_trait;
use error_chain::error_chain; use error_chain::error_chain;
use futures::future::join_all; use futures::future::join_all;
use serde_json::json;
use std::vec;
use crate::DbPool;
use crate::db; use crate::db;
use crate::providers::Provider; use crate::providers::Provider;
use crate::util::cache::VideoCache; use crate::util::cache::VideoCache;
use crate::videos::{self, ServerOptions, VideoItem}; use crate::videos::{self, ServerOptions, VideoItem};
use crate::DbPool;
error_chain! { error_chain! {
foreign_links { foreign_links {
@@ -17,7 +18,7 @@ error_chain! {
} }
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct HanimeSearchRequest{ struct HanimeSearchRequest {
search_text: String, search_text: String,
tags: Vec<String>, tags: Vec<String>,
tags_mode: String, tags_mode: String,
@@ -25,7 +26,7 @@ struct HanimeSearchRequest{
blacklist: Vec<String>, blacklist: Vec<String>,
order_by: String, order_by: String,
ordering: String, ordering: String,
page: u8 page: u8,
} }
#[allow(dead_code)] #[allow(dead_code)]
@@ -39,7 +40,7 @@ impl HanimeSearchRequest {
blacklist: vec![], blacklist: vec![],
order_by: "created_at_unix".to_string(), order_by: "created_at_unix".to_string(),
ordering: "desc".to_string(), ordering: "desc".to_string(),
page: 0 page: 0,
} }
} }
pub fn tags(mut self, tags: Vec<String>) -> Self { pub fn tags(mut self, tags: Vec<String>) -> Self {
@@ -74,20 +75,19 @@ impl HanimeSearchRequest {
self.page = page; self.page = page;
self self
} }
} }
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Debug)]
struct HanimeSearchResponse{ struct HanimeSearchResponse {
page: u8, page: u8,
nbPages:u8, nbPages: u8,
nbHits: u32, nbHits: u32,
hitsPerPage: u8, hitsPerPage: u8,
hits: String hits: String,
} }
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct HanimeSearchResult{ struct HanimeSearchResult {
id: u64, id: u64,
name: String, name: String,
titles: Vec<String>, titles: Vec<String>,
@@ -109,7 +109,6 @@ struct HanimeSearchResult{
tags: Vec<String>, tags: Vec<String>,
created_at: u64, created_at: u64,
released_at: u64, released_at: u64,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -125,24 +124,59 @@ impl HanimeProvider {
} }
} }
async fn get_video_item(&self, hit: HanimeSearchResult, pool: DbPool, options: ServerOptions) -> Result<VideoItem> { async fn get_video_item(
&self,
hit: HanimeSearchResult,
pool: DbPool,
options: ServerOptions,
) -> Result<VideoItem> {
let mut conn = pool.get().expect("couldn't get db connection from pool"); 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); 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("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 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
match db_result { match db_result {
Ok(Some(video_url)) => { 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) .tags(hit.tags)
.uploader(hit.brand) .uploader(hit.brand)
.views(hit.views as u32) .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) .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) => (), Ok(None) => (),
Err(e) => { Err(e) => {
@@ -150,49 +184,103 @@ impl HanimeProvider {
// return Err(format!("Error fetching video from database: {}", e).into()); // 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 mut requester = options.requester.clone().unwrap();
let text = requester.get(&url, None).await.unwrap(); let payload = json!({
"width": 571, "height": 703, "ab": "kh" }
let urls = text.split("\"servers\"").collect::<Vec<&str>>()[1]; );
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::<Vec<&str>>()[1];
let mut url_vec = vec![]; let mut url_vec = vec![];
for el in urls.split("\"url\":\"").collect::<Vec<&str>>(){ for el in urls.split("\"url\":\"").collect::<Vec<&str>>() {
let url = el.split("\"").collect::<Vec<&str>>()[0]; let url = el.split("\"").collect::<Vec<&str>>()[0];
if !url.is_empty() && url.contains("m3u8") { if !url.is_empty() && url.contains("m3u8") {
url_vec.push(url.to_string()); url_vec.push(url.to_string());
} }
} }
let mut conn = pool.get().expect("couldn't get db connection from pool"); 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); drop(conn);
Ok(VideoItem::new(id, title, url_vec[0].clone(), channel, thumb, duration) Ok(
.tags(hit.tags) VideoItem::new(id, title, url_vec[0].clone(), channel, thumb, duration)
.uploader(hit.brand) .tags(hit.tags)
.views(hit.views as u32) .uploader(hit.brand)
.rating((hit.likes as f32 / (hit.likes + hit.dislikes)as f32) * 100 as f32) .views(hit.views as u32)
.formats(vec![videos::VideoFormat::new(url_vec[0].clone(), "1080".to_string(), "m3u8".to_string())])) .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<Vec<VideoItem>> { async fn get(
&self,
cache: VideoCache,
pool: DbPool,
page: u8,
query: String,
sort: String,
options: ServerOptions,
) -> Result<Vec<VideoItem>> {
let index = format!("hanime:{}:{}:{}", query, page, sort); let index = format!("hanime:{}:{}:{}", query, page, sort);
let order_by = match sort.contains("."){ let order_by = match sort.contains(".") {
true => sort.split(".").collect::<Vec<&str>>()[0].to_string(), true => sort.split(".").collect::<Vec<&str>>()[0].to_string(),
false => "created_at_unix".to_string(), false => "created_at_unix".to_string(),
}; };
let ordering = match sort.contains("."){ let ordering = match sort.contains(".") {
true => sort.split(".").collect::<Vec<&str>>()[1].to_string(), true => sort.split(".").collect::<Vec<&str>>()[1].to_string(),
false => "desc".to_string(), false => "desc".to_string(),
}; };
let old_items = match cache.get(&index) { let old_items = match cache.get(&index) {
Some((time, items)) => { 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); //println!("Cache hit for URL: {}", index);
return Ok(items.clone()); return Ok(items.clone());
} } else {
else{
items.clone() items.clone()
} }
} }
@@ -202,15 +290,16 @@ impl HanimeProvider {
}; };
let search = HanimeSearchRequest::new() let search = HanimeSearchRequest::new()
.page(page-1) .page(page - 1)
.search_text(query.clone()) .search_text(query.clone())
.order_by(order_by) .order_by(order_by)
.ordering(ordering); .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::<HanimeSearchResponse>().await { let hits = match response.json::<HanimeSearchResponse>().await {
Ok(resp) => resp.hits, Ok(resp) => resp.hits,
@@ -222,18 +311,17 @@ impl HanimeProvider {
let hits_json: Vec<HanimeSearchResult> = serde_json::from_str(hits.as_str()) let hits_json: Vec<HanimeSearchResult> = serde_json::from_str(hits.as_str())
.map_err(|e| format!("Failed to parse hits JSON: {}", e))?; .map_err(|e| format!("Failed to parse hits JSON: {}", e))?;
// let timeout_duration = Duration::from_secs(120); // 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 futures = hits_json
let results: Vec<Result<VideoItem>> = join_all(futures).await;
let video_items: Vec<VideoItem> = results
.into_iter() .into_iter()
.filter_map(Result::ok) .map(|el| self.get_video_item(el.clone(), pool.clone(), options.clone()));
.collect(); let results: Vec<Result<VideoItem>> = join_all(futures).await;
let video_items: Vec<VideoItem> = results.into_iter().filter_map(Result::ok).collect();
if !video_items.is_empty() { if !video_items.is_empty() {
cache.remove(&index); cache.remove(&index);
cache.insert(index.clone(), video_items.clone()); cache.insert(index.clone(), video_items.clone());
} else { } else {
return Ok(old_items); return Ok(old_items);
} }
Ok(video_items) Ok(video_items)
} }
@@ -255,8 +343,28 @@ impl Provider for HanimeProvider {
let _ = per_page; let _ = per_page;
let _ = sort; let _ = sort;
let videos: std::result::Result<Vec<VideoItem>, Error> = match query { let videos: std::result::Result<Vec<VideoItem>, Error> = match query {
Some(q) => self.get(cache, pool, page.parse::<u8>().unwrap_or(1), q, sort, options).await, Some(q) => {
None => self.get(cache, pool, page.parse::<u8>().unwrap_or(1), "".to_string(), sort, options).await, self.get(
cache,
pool,
page.parse::<u8>().unwrap_or(1),
q,
sort,
options,
)
.await
}
None => {
self.get(
cache,
pool,
page.parse::<u8>().unwrap_or(1),
"".to_string(),
sort,
options,
)
.await
}
}; };
match videos { match videos {
Ok(v) => v, Ok(v) => v,