hanime work in progress...
This commit is contained in:
@@ -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 {
|
||||||
@@ -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,7 +75,6 @@ impl HanimeSearchRequest {
|
|||||||
self.page = page;
|
self.page = page;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
||||||
@@ -83,7 +83,7 @@ struct HanimeSearchResponse{
|
|||||||
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)]
|
||||||
@@ -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,12 +184,47 @@ 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>>() {
|
||||||
@@ -165,18 +234,38 @@ impl HanimeProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
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(
|
||||||
|
VideoItem::new(id, title, url_vec[0].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)
|
||||||
.formats(vec![videos::VideoFormat::new(url_vec[0].clone(), "1080".to_string(), "m3u8".to_string())]))
|
.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(),
|
||||||
@@ -188,11 +277,10 @@ impl HanimeProvider {
|
|||||||
};
|
};
|
||||||
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,9 +296,10 @@ impl HanimeProvider {
|
|||||||
.ordering(ordering);
|
.ordering(ordering);
|
||||||
|
|
||||||
let mut requester = options.requester.clone().unwrap();
|
let mut requester = options.requester.clone().unwrap();
|
||||||
let response = requester.post_json("https://search.htv-services.com/search", &search, vec![]).await.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,12 +311,11 @@ 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());
|
||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user