304 lines
9.8 KiB
Rust
304 lines
9.8 KiB
Rust
use std::vec;
|
|
use std::env;
|
|
use error_chain::error_chain;
|
|
use reqwest::{Proxy};
|
|
use futures::future::join_all;
|
|
|
|
use crate::db;
|
|
use crate::providers::Provider;
|
|
use crate::util::cache::VideoCache;
|
|
use crate::videos::{self, VideoItem};
|
|
use crate::DbPool;
|
|
use crate::USER_AGENT; // Make sure Provider trait is imported
|
|
|
|
error_chain! {
|
|
foreign_links {
|
|
Io(std::io::Error);
|
|
HttpRequest(reqwest::Error);
|
|
}
|
|
}
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
|
struct HanimeSearchRequest{
|
|
search_text: String,
|
|
tags: Vec<String>,
|
|
tags_mode: String,
|
|
brands: Vec<String>,
|
|
blacklist: Vec<String>,
|
|
order_by: String,
|
|
ordering: String,
|
|
page: u8
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
impl HanimeSearchRequest {
|
|
pub fn new() -> Self {
|
|
HanimeSearchRequest {
|
|
search_text: "".to_string(),
|
|
tags: vec![],
|
|
tags_mode: "AND".to_string(),
|
|
brands: vec![],
|
|
blacklist: vec![],
|
|
order_by: "created_at_unix".to_string(),
|
|
ordering: "desc".to_string(),
|
|
page: 0
|
|
}
|
|
}
|
|
pub fn tags(mut self, tags: Vec<String>) -> Self {
|
|
self.tags = tags;
|
|
self
|
|
}
|
|
pub fn search_text(mut self, search_text: String) -> Self {
|
|
self.search_text = search_text;
|
|
self
|
|
}
|
|
pub fn tags_mode(mut self, tags_mode: String) -> Self {
|
|
self.tags_mode = tags_mode;
|
|
self
|
|
}
|
|
pub fn brands(mut self, brands: Vec<String>) -> Self {
|
|
self.brands = brands;
|
|
self
|
|
}
|
|
pub fn blacklist(mut self, blacklist: Vec<String>) -> Self {
|
|
self.blacklist = blacklist;
|
|
self
|
|
}
|
|
pub fn order_by(mut self, order_by: String) -> Self {
|
|
self.order_by = order_by;
|
|
self
|
|
}
|
|
pub fn ordering(mut self, ordering: String) -> Self {
|
|
self.ordering = ordering;
|
|
self
|
|
}
|
|
pub fn page(mut self, page: u8) -> Self {
|
|
self.page = page;
|
|
self
|
|
}
|
|
|
|
}
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
|
struct HanimeSearchResponse{
|
|
page: u8,
|
|
nbPages:u8,
|
|
nbHits: u32,
|
|
hitsPerPage: u8,
|
|
hits: String
|
|
}
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
|
struct HanimeSearchResult{
|
|
id: u64,
|
|
name: String,
|
|
titles: Vec<String>,
|
|
slug: String,
|
|
description: String,
|
|
views: u64,
|
|
interests: u64,
|
|
poster_url: String,
|
|
cover_url: String,
|
|
brand: String,
|
|
brand_id: u64,
|
|
duration_in_ms: u32,
|
|
is_censored: bool,
|
|
rating: Option<u32>,
|
|
likes: u64,
|
|
dislikes: u64,
|
|
downloads: u64,
|
|
monthly_ranked: Option<u64>,
|
|
tags: Vec<String>,
|
|
created_at: u64,
|
|
released_at: u64,
|
|
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub struct HanimeProvider {
|
|
url: String,
|
|
}
|
|
|
|
impl HanimeProvider {
|
|
pub fn new() -> Self {
|
|
HanimeProvider {
|
|
url: "https://hanime.tv/".to_string(),
|
|
}
|
|
}
|
|
|
|
async fn get_video_item(&self, hit: HanimeSearchResult, pool: DbPool) -> Result<VideoItem> {
|
|
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 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)
|
|
.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(video_url.clone(), "1080".to_string(), "m3u8".to_string())]));
|
|
}
|
|
Ok(None) => (),
|
|
Err(e) => {
|
|
println!("Error fetching video from database: {}", e);
|
|
// return Err(format!("Error fetching video from database: {}", e).into());
|
|
}
|
|
}
|
|
|
|
let client = match env::var("BURP_URL").as_deref() {
|
|
Ok(burp_url) =>
|
|
reqwest::Client::builder()
|
|
.user_agent(USER_AGENT)
|
|
.proxy(Proxy::https(burp_url).unwrap())
|
|
.danger_accept_invalid_certs(true)
|
|
.build()?,
|
|
Err(_) => reqwest::Client::builder()
|
|
.user_agent(USER_AGENT)
|
|
.danger_accept_invalid_certs(true)
|
|
.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 urls = text.split("\"servers\"").collect::<Vec<&str>>()[1];
|
|
let mut url_vec = vec![];
|
|
|
|
for el in urls.split("\"url\":\"").collect::<Vec<&str>>(){
|
|
let url = el.split("\"").collect::<Vec<&str>>()[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());
|
|
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())]))
|
|
|
|
}
|
|
|
|
async fn get(&self, cache: VideoCache, pool: DbPool, page: u8, query: String, sort:String) -> Result<Vec<VideoItem>> {
|
|
let index = format!("{}:{}:{}", query, page, sort);
|
|
let order_by = match sort.contains("."){
|
|
true => sort.split(".").collect::<Vec<&str>>()[0].to_string(),
|
|
false => "created_at_unix".to_string(),
|
|
};
|
|
let ordering = match sort.contains("."){
|
|
true => sort.split(".").collect::<Vec<&str>>()[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 {
|
|
println!("Cache hit for URL: {}", index);
|
|
return Ok(items.clone());
|
|
}
|
|
else{
|
|
items.clone()
|
|
}
|
|
}
|
|
None => {
|
|
vec![]
|
|
}
|
|
};
|
|
|
|
let search = HanimeSearchRequest::new()
|
|
.page(page-1)
|
|
.search_text(query.clone())
|
|
.order_by(order_by)
|
|
.ordering(ordering);
|
|
let client = match env::var("BURP_URL").as_deref() {
|
|
Ok(burp_url) =>
|
|
reqwest::Client::builder()
|
|
.user_agent(USER_AGENT)
|
|
.proxy(Proxy::https(burp_url).unwrap())
|
|
.danger_accept_invalid_certs(true)
|
|
.build()?,
|
|
Err(_) => reqwest::Client::builder()
|
|
.user_agent(USER_AGENT)
|
|
.danger_accept_invalid_certs(true)
|
|
.build()?,
|
|
};
|
|
let response = client.post("https://search.htv-services.com/search")
|
|
.json(&search)
|
|
.send().await?;
|
|
|
|
|
|
|
|
let hits = match response.json::<HanimeSearchResponse>().await {
|
|
Ok(resp) => resp.hits,
|
|
Err(e) => {
|
|
println!("Failed to parse HanimeSearchResponse: {}", e);
|
|
return Ok(old_items);
|
|
}
|
|
};
|
|
let hits_json: Vec<HanimeSearchResult> = 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 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() {
|
|
cache.remove(&index);
|
|
cache.insert(index.clone(), video_items.clone());
|
|
} else {
|
|
return Ok(old_items);
|
|
}
|
|
|
|
Ok(video_items)
|
|
}
|
|
}
|
|
|
|
impl Provider for HanimeProvider {
|
|
async fn get_videos(
|
|
&self,
|
|
cache: VideoCache,
|
|
pool: DbPool,
|
|
_channel: String,
|
|
sort: String,
|
|
query: Option<String>,
|
|
page: String,
|
|
per_page: String,
|
|
featured: String,
|
|
) -> Vec<VideoItem> {
|
|
let _ = featured;
|
|
let _ = per_page;
|
|
let _ = sort;
|
|
println!("Sort: {:?}", sort);
|
|
let videos: std::result::Result<Vec<VideoItem>, Error> = match query {
|
|
Some(q) => self.get(cache, pool, page.parse::<u8>().unwrap_or(1), q, sort).await,
|
|
None => self.get(cache, pool, page.parse::<u8>().unwrap_or(1), "".to_string(), sort).await,
|
|
};
|
|
match videos {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
println!("Error fetching videos: {}", e);
|
|
vec![]
|
|
}
|
|
}
|
|
}
|
|
}
|