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 {
|
||||||
@@ -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 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,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,
|
||||||
|
|||||||
Reference in New Issue
Block a user