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:
@@ -344,13 +344,13 @@ impl HentaimamaProvider {
|
|||||||
.collect())
|
.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 document = Html::parse_document(html);
|
||||||
let selector = Self::selector("a.dt-se-item")?;
|
let selector = Self::selector("a.dt-se-item")?;
|
||||||
Ok(document
|
Ok(document
|
||||||
.select(&selector)
|
.select(&selector)
|
||||||
.filter_map(|e: ElementRef| e.value().attr("href").map(|s| s.to_string()))
|
.filter_map(|e: ElementRef| e.value().attr("href").map(|s| s.to_string()))
|
||||||
.last())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn post_id_regex() -> &'static Regex {
|
fn post_id_regex() -> &'static Regex {
|
||||||
@@ -484,13 +484,25 @@ impl HentaimamaProvider {
|
|||||||
Some(item)
|
Some(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search/genre/studio archives only expose series (show) cards, so a match
|
/// Search/genre/studio archives only expose series (show) cards, so each
|
||||||
/// is represented by its most recent episode - the same shape the latest
|
/// match is expanded into every episode of that series - the same shape
|
||||||
/// feed already returns, just reached through one extra hop.
|
/// the latest feed already returns per-episode, just reached through one
|
||||||
async fn resolve_series_to_item(requester: &mut Requester, series_url: &str) -> Option<VideoItem> {
|
/// extra hop.
|
||||||
let html = Self::fetch_html(requester, series_url, BASE_URL).await.ok()?;
|
async fn resolve_series_to_items(requester: &mut Requester, series_url: &str) -> Vec<VideoItem> {
|
||||||
let episode_url = Self::last_episode_url(&html).ok().flatten()?;
|
let Ok(html) = Self::fetch_html(requester, series_url, BASE_URL).await else {
|
||||||
Self::resolve_episode_url(requester, &episode_url).await
|
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> {
|
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)?;
|
let urls = Self::parse_series_card_urls(&html)?;
|
||||||
stream::iter(urls.into_iter().take(per_page.max(1)).map(|url| {
|
stream::iter(urls.into_iter().take(per_page.max(1)).map(|url| {
|
||||||
let mut req = requester.clone();
|
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)
|
.buffer_unordered(4)
|
||||||
.filter_map(|item| async move { item })
|
.flat_map(stream::iter)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -663,9 +663,16 @@ impl HornyleakProvider {
|
|||||||
listing_referer: String,
|
listing_referer: String,
|
||||||
mut requester: Requester,
|
mut requester: Requester,
|
||||||
) -> VideoItem {
|
) -> VideoItem {
|
||||||
// The page URL is yt-dlp-resolvable, so enrichment is only used for tags
|
// yt-dlp's generic extractor on the page URL grabs the site's own
|
||||||
// and uploader fields. The HLS embed chain (kept as a private helper
|
// `728x90-1.mp4` banner-ad `<video>` tag instead of drilling into the
|
||||||
// for future use) is intentionally not invoked here.
|
// `/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
|
let detail = requester
|
||||||
.get_with_headers(
|
.get_with_headers(
|
||||||
&card.page_url,
|
&card.page_url,
|
||||||
@@ -688,6 +695,13 @@ impl HornyleakProvider {
|
|||||||
card.thumb,
|
card.thumb,
|
||||||
card.duration,
|
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 {
|
if let Some(views) = card.views {
|
||||||
item = item.views(views);
|
item = item.views(views);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user