hentaihaven workflow update

This commit is contained in:
Simon
2026-05-18 20:42:36 +00:00
committed by ForgeCode
parent b077b24d63
commit bd8382d579

View File

@@ -9,7 +9,7 @@ use crate::{DbPool, db};
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::stream::{self, StreamExt};
use htmlentity::entity::{ICodedDataTrait, decode}; use htmlentity::entity::{ICodedDataTrait, decode};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::vec; use std::vec;
@@ -226,37 +226,39 @@ impl HentaihavenProvider {
} }
}; };
let futures = block let segments: Vec<String> = block.split("id=\"manga-item-").skip(1).map(|el| el.to_string()).collect();
.split("id=\"manga-item-") stream::iter(segments.into_iter().map(|el| {
.skip(1) let pool = pool.clone();
.map(|el| self.get_video_item(el.to_string(), pool.clone(), requester.clone())); let requester = requester.clone();
join_all(futures) let provider = self.clone();
.await async move { provider.get_video_item(el, pool, requester).await }
.into_iter() }))
.inspect(|r| { .buffer_unordered(4)
if let Err(e) = r { .filter_map(|result| async move {
match result {
Ok(item) => Some(item),
Err(e) => {
eprint!("Hentai Haven Provider: Failed to get video item:{}\n", e); eprint!("Hentai Haven Provider: Failed to get video item:{}\n", e);
// Prepare data to move into the background task
let msg = e.to_string(); let msg = e.to_string();
let chain = format_error_chain(&e); let chain = format_error_chain(&e);
// Spawn the report into the background - NO .await here
tokio::spawn(async move { tokio::spawn(async move {
let _ = send_discord_error_report( let _ = send_discord_error_report(
msg, msg,
Some(chain), Some(chain),
Some("Hentai Haven Provider"), Some("Hentai Haven Provider"),
Some("Failed to get video item"), Some("Failed to get video item"),
file!(), // Note: these might report the utility line file!(),
line!(), // better to hardcode or pass from outside line!(),
module_path!(), module_path!(),
) )
.await; .await;
}); });
None
} }
}) }
.filter_map(Result::ok) })
.collect() .collect::<Vec<_>>()
.await
} }
async fn get_video_items_from_html_search( async fn get_video_items_from_html_search(
@@ -292,37 +294,39 @@ impl HentaihavenProvider {
} }
}; };
let futures = block let segments: Vec<String> = block.split("c-tabs-item__content col-6 col-md-12").skip(1).map(|el| el.to_string()).collect();
.split("c-tabs-item__content col-6 col-md-12") stream::iter(segments.into_iter().map(|el| {
.skip(1) let pool = pool.clone();
.map(|el| self.get_video_item(el.to_string(), pool.clone(), requester.clone())); let requester = requester.clone();
join_all(futures) let provider = self.clone();
.await async move { provider.get_video_item(el, pool, requester).await }
.into_iter() }))
.inspect(|r| { .buffer_unordered(4)
if let Err(e) = r { .filter_map(|result| async move {
match result {
Ok(item) => Some(item),
Err(e) => {
eprint!("Hentai Haven Provider: Failed to get video item:{}\n", e); eprint!("Hentai Haven Provider: Failed to get video item:{}\n", e);
// Prepare data to move into the background task
let msg = e.to_string(); let msg = e.to_string();
let chain = format_error_chain(&e); let chain = format_error_chain(&e);
// Spawn the report into the background - NO .await here
tokio::spawn(async move { tokio::spawn(async move {
let _ = send_discord_error_report( let _ = send_discord_error_report(
msg, msg,
Some(chain), Some(chain),
Some("Hentai Haven Provider"), Some("Hentai Haven Provider"),
Some("Failed to get video item"), Some("Failed to get video item"),
file!(), // Note: these might report the utility line file!(),
line!(), // better to hardcode or pass from outside line!(),
module_path!(), module_path!(),
) )
.await; .await;
}); });
None
} }
}) }
.filter_map(Result::ok) })
.collect() .collect::<Vec<_>>()
.await
} }
async fn get_video_item( async fn get_video_item(
@@ -337,45 +341,38 @@ impl HentaihavenProvider {
.and_then(|s| s.split('"').next()) .and_then(|s| s.split('"').next())
.ok_or_else(|| ErrorKind::Parse("video url\n\n{seg}".into()))? .ok_or_else(|| ErrorKind::Parse("video url\n\n{seg}".into()))?
.to_string(); .to_string();
let mut conn = match pool.get() {
Ok(conn) => conn, match self.fetch_video_item(&video_url, &mut requester).await {
Err(e) => { Ok(video_item) => {
let msg = format!("DB pool error: {}", e); if let Ok(mut conn) = pool.get() {
send_discord_error_report( let _ = db::insert_video(
msg.clone(), &mut conn,
None, &video_url,
Some("Hentai Haven Provider"), &serde_json::to_string(&video_item).unwrap_or_default(),
Some("get_video_item.pool_get"), );
file!(), }
line!(), Ok(video_item)
module_path!(),
)
.await;
return Err(msg.into());
} }
}; Err(e) => {
let db_result = db::get_video(&mut conn, video_url.clone()); if let Ok(mut conn) = pool.get() {
drop(conn); if let Ok(Some(cached)) = db::get_video(&mut conn, video_url.clone()) {
match db_result { if let Ok(item) = VideoItem::from(cached) {
Ok(Some(video)) => { return Ok(item);
let video_item = VideoItem::from(video); }
match video_item {
Ok(item) => return Ok(item),
Err(e) => {
eprint!("Failed to convert video from DB result: {}\n", e);
} }
} }
} Err(e)
Ok(None) => {
// continue to fetch and parse the video
}
Err(e) => {
eprint!("Database error: {}\n", e);
// continue to fetch and parse the video even if there's a DB error
} }
} }
}
async fn fetch_video_item(
&self,
video_url: &str,
requester: &mut Requester,
) -> Result<VideoItem> {
let html = requester let html = requester
.get(&video_url, Some(Version::HTTP_2)) .get(video_url, Some(Version::HTTP_2))
.await .await
.map_err(|e| Error::from(format!("Failed to fetch video page: {}", e)))?; .map_err(|e| Error::from(format!("Failed to fetch video page: {}", e)))?;
@@ -383,7 +380,7 @@ impl HentaihavenProvider {
.split("<h1>") .split("<h1>")
.nth(1) .nth(1)
.and_then(|s| s.split("</h1>").next()) .and_then(|s| s.split("</h1>").next())
.ok_or_else(|| ErrorKind::Parse(format!("video title\n\n{seg}").into()))? .ok_or_else(|| ErrorKind::Parse(format!("video title: {video_url}")))?
.trim() .trim()
.to_string(); .to_string();
title = decode(title.as_bytes()) title = decode(title.as_bytes())
@@ -394,7 +391,7 @@ impl HentaihavenProvider {
.split('/') .split('/')
.nth(4) .nth(4)
.and_then(|s| s.split('.').next()) .and_then(|s| s.split('.').next())
.ok_or_else(|| ErrorKind::Parse("video id\n\n{seg}".into()))? .ok_or_else(|| ErrorKind::Parse(format!("video id: {video_url}")))?
.to_string(); .to_string();
let thumb = html let thumb = html
.split("og:image\" content=\"") .split("og:image\" content=\"")
@@ -495,43 +492,19 @@ impl HentaihavenProvider {
formats.push(format); formats.push(format);
} }
if formats.is_empty() { if formats.is_empty() {
let e = Error::from(format!("No formats found for video URL: {}", video_url)); return Err(Error::from(format!("No formats found for video URL: {}", video_url)));
return Err(e);
} }
if formats.len() > 1 { if formats.len() > 1 {
title = format!("{} ({} Episodes)", title, formats.len()); title = format!("{} ({} Episodes)", title, formats.len());
} }
let video_item = Ok(
VideoItem::new(id, title, video_url.clone(), "hentaihaven".into(), thumb, 0) VideoItem::new(id, title, video_url.to_string(), "hentaihaven".into(), thumb, 0)
.formats(formats) .formats(formats)
.tags(tags) .tags(tags)
.views(views) .views(views)
.aspect_ratio(0.715); .aspect_ratio(0.715),
)
match pool.get() {
Ok(mut conn) => {
let _ = db::insert_video(
&mut conn,
&video_url,
&serde_json::to_string(&video_item).unwrap_or_default(),
);
}
Err(e) => {
send_discord_error_report(
format!("DB pool error: {}", e),
None,
Some("Hentai Haven Provider"),
Some("get_video_item.insert_video.pool_get"),
file!(),
line!(),
module_path!(),
)
.await;
}
}
Ok(video_item)
} }
} }