Fix hornyleak/hentaimama providers returning wrong media

hornyleak: yt-dlp's generic extractor on the detail page picked up the
site's own 728x90 banner-ad <video> tag instead of following the real
/embed/{id}/ iframe, so every item streamed an ad. Now resolve the
actual HLS master playlist via the existing embed -> hdplayer.gives
chain and ship it as an explicit format.

hentaimama: search/genre/studio results are series cards, and
resolving them only followed the *last* episode link, so a 4-episode
series only ever surfaced 1 item. Now expands every series match into
all of its episodes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017haiuheHbREQdnh3B5rqgc
This commit is contained in:
Simon
2026-09-08 16:37:58 +00:00
parent e50003a7ea
commit 9e3a680ef2
2 changed files with 40 additions and 14 deletions

View File

@@ -344,13 +344,13 @@ impl HentaimamaProvider {
.collect())
}
fn last_episode_url(html: &str) -> Result<Option<String>> {
fn episode_urls(html: &str) -> Result<Vec<String>> {
let document = Html::parse_document(html);
let selector = Self::selector("a.dt-se-item")?;
Ok(document
.select(&selector)
.filter_map(|e: ElementRef| e.value().attr("href").map(|s| s.to_string()))
.last())
.collect())
}
fn post_id_regex() -> &'static Regex {
@@ -484,13 +484,25 @@ impl HentaimamaProvider {
Some(item)
}
/// Search/genre/studio archives only expose series (show) cards, so a match
/// is represented by its most recent episode - the same shape the latest
/// feed already returns, just reached through one extra hop.
async fn resolve_series_to_item(requester: &mut Requester, series_url: &str) -> Option<VideoItem> {
let html = Self::fetch_html(requester, series_url, BASE_URL).await.ok()?;
let episode_url = Self::last_episode_url(&html).ok().flatten()?;
Self::resolve_episode_url(requester, &episode_url).await
/// Search/genre/studio archives only expose series (show) cards, so each
/// match is expanded into every episode of that series - the same shape
/// the latest feed already returns per-episode, just reached through one
/// extra hop.
async fn resolve_series_to_items(requester: &mut Requester, series_url: &str) -> Vec<VideoItem> {
let Ok(html) = Self::fetch_html(requester, series_url, BASE_URL).await else {
return vec![];
};
let Ok(episode_urls) = Self::episode_urls(&html) else {
return vec![];
};
stream::iter(episode_urls.into_iter().map(|episode_url| {
let mut req = requester.clone();
async move { Self::resolve_episode_url(&mut req, &episode_url).await }
}))
.buffer_unordered(4)
.filter_map(|item| async move { item })
.collect::<Vec<_>>()
.await
}
async fn resolve_episode_url(requester: &mut Requester, episode_url: &str) -> Option<VideoItem> {
@@ -574,10 +586,10 @@ impl HentaimamaProvider {
let urls = Self::parse_series_card_urls(&html)?;
stream::iter(urls.into_iter().take(per_page.max(1)).map(|url| {
let mut req = requester.clone();
async move { Self::resolve_series_to_item(&mut req, &url).await }
async move { Self::resolve_series_to_items(&mut req, &url).await }
}))
.buffer_unordered(4)
.filter_map(|item| async move { item })
.flat_map(stream::iter)
.collect::<Vec<_>>()
.await
}

View File

@@ -663,9 +663,16 @@ impl HornyleakProvider {
listing_referer: String,
mut requester: Requester,
) -> VideoItem {
// The page URL is yt-dlp-resolvable, so enrichment is only used for tags
// and uploader fields. The HLS embed chain (kept as a private helper
// for future use) is intentionally not invoked here.
// yt-dlp's generic extractor on the page URL grabs the site's own
// `728x90-1.mp4` banner-ad `<video>` tag instead of drilling into the
// `/embed/{id}/` iframe, so the page URL alone is NOT reliably
// yt-dlp-resolvable to the real content. Resolve the actual HLS
// master playlist via the embed -> hdplayer chain and ship it as an
// explicit format; only fall back to the bare page URL if that fails.
let hls_url = self
.resolve_hls_url(&card.id, &card.page_url, requester.clone())
.await;
let detail = requester
.get_with_headers(
&card.page_url,
@@ -688,6 +695,13 @@ impl HornyleakProvider {
card.thumb,
card.duration,
);
if let Some(hls_url) = hls_url {
item.formats = Some(vec![crate::videos::VideoFormat::m3u8(
hls_url,
"auto".to_string(),
"m3u8".to_string(),
)]);
}
if let Some(views) = card.views {
item = item.views(views);
}