thepornbunny: resolve pornstar/studio/tag names; get_probe treats redirect-away as a miss
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
use crate::DbPool;
|
use crate::DbPool;
|
||||||
use crate::api::ClientVersion;
|
use crate::api::ClientVersion;
|
||||||
use crate::providers::{
|
use crate::providers::{
|
||||||
Provider, build_proxy_url, report_provider_error, requester_or_default,
|
Provider, build_proxy_url, probe_first_archive, report_provider_error, requester_or_default,
|
||||||
|
slug_dashed,
|
||||||
};
|
};
|
||||||
use crate::status::*;
|
use crate::status::*;
|
||||||
use crate::util::cache::VideoCache;
|
use crate::util::cache::VideoCache;
|
||||||
@@ -367,6 +368,26 @@ impl ThepornbunnyProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A free-form query that names a pornstar, studio or tag goes to that
|
||||||
|
/// archive (probed on the site; unknown names redirect home), else keyword
|
||||||
|
/// search.
|
||||||
|
async fn resolve_keyword(query: &str, options: &ServerOptions) -> Option<Target> {
|
||||||
|
let slug = slug_dashed(query);
|
||||||
|
if slug.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let urls: Vec<String> = ["pornstar", "studio", "tag"]
|
||||||
|
.iter()
|
||||||
|
.map(|kind| format!("{BASE_URL}/{kind}/{slug}/"))
|
||||||
|
.collect();
|
||||||
|
let mut requester = requester_or_default(options, CHANNEL_ID, "resolve_keyword");
|
||||||
|
let (index, _) = probe_first_archive(&mut requester, &urls, &format!("{BASE_URL}/"), |body| {
|
||||||
|
body.contains("th-description")
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
Some(Target::Archive(urls[index].trim_end_matches('/').to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_option_target(options: &ServerOptions, sort: &str) -> Target {
|
fn resolve_option_target(options: &ServerOptions, sort: &str) -> Target {
|
||||||
if let Some(cat) = options.categories.as_deref() {
|
if let Some(cat) = options.categories.as_deref() {
|
||||||
if cat.starts_with(BASE_URL) && cat != "all" {
|
if cat.starts_with(BASE_URL) && cat != "all" {
|
||||||
@@ -476,10 +497,15 @@ impl Provider for ThepornbunnyProvider {
|
|||||||
let page = page.parse::<u16>().unwrap_or(1).max(1);
|
let page = page.parse::<u16>().unwrap_or(1).max(1);
|
||||||
let per_page_limit = per_page.parse::<usize>().unwrap_or(10).clamp(1, 60);
|
let per_page_limit = per_page.parse::<usize>().unwrap_or(10).clamp(1, 60);
|
||||||
|
|
||||||
let target = match query {
|
let mut target = match query {
|
||||||
Some(ref q) if !q.trim().is_empty() => Self::resolve_query_target(q.trim()),
|
Some(ref q) if !q.trim().is_empty() => Self::resolve_query_target(q.trim()),
|
||||||
_ => Self::resolve_option_target(&options, &sort),
|
_ => Self::resolve_option_target(&options, &sort),
|
||||||
};
|
};
|
||||||
|
if let Target::Search(query) = &target {
|
||||||
|
if let Some(found) = Self::resolve_keyword(query, &options).await {
|
||||||
|
target = found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.fetch_target(cache, target, page, per_page_limit, options)
|
.fetch_target(cache, target, page, per_page_limit, options)
|
||||||
|
|||||||
@@ -661,6 +661,10 @@ impl Requester {
|
|||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path_of(url: &str) -> Option<String> {
|
||||||
|
url::Url::parse(url).ok().map(|u| u.path().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
/// Existence probe for archive pages (pornstar / studio / tag / category).
|
/// Existence probe for archive pages (pornstar / studio / tag / category).
|
||||||
///
|
///
|
||||||
/// Reads the raw response so a real 404/410 answers immediately with
|
/// Reads the raw response so a real 404/410 answers immediately with
|
||||||
@@ -678,6 +682,14 @@ impl Requester {
|
|||||||
match self.get_raw_with_headers(url, headers.clone()).await {
|
match self.get_raw_with_headers(url, headers.clone()).await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let status = response.status().as_u16();
|
let status = response.status().as_u16();
|
||||||
|
// An unknown archive often redirects to the home page or a
|
||||||
|
// search page instead of answering 404: a redirect that
|
||||||
|
// lands on a different path is a miss.
|
||||||
|
let redirected_away = Self::path_of(url)
|
||||||
|
.zip(Some(response.uri().path().to_string()))
|
||||||
|
.is_some_and(|(asked, landed)| {
|
||||||
|
!asked.trim_end_matches('/').eq_ignore_ascii_case(landed.trim_end_matches('/'))
|
||||||
|
});
|
||||||
let body = response.text().await.unwrap_or_default();
|
let body = response.text().await.unwrap_or_default();
|
||||||
if status == 404 || status == 410 {
|
if status == 404 || status == 410 {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -688,6 +700,9 @@ impl Requester {
|
|||||||
{
|
{
|
||||||
return self.get_with_headers(url, headers, None).await.map(Some);
|
return self.get_with_headers(url, headers, None).await.map(Some);
|
||||||
}
|
}
|
||||||
|
if redirected_away {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
if status >= 500 || status == 429 {
|
if status >= 500 || status == 429 {
|
||||||
last_err = format!("HTTP {status}");
|
last_err = format!("HTTP {status}");
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
Reference in New Issue
Block a user