pornhub: probe pornstar/model/channel for unknown free-form queries

This commit is contained in:
Simon
2026-10-17 05:56:31 +00:00
parent e34801f6f8
commit 26ef830f6e

View File

@@ -1,6 +1,9 @@
use crate::DbPool;
use crate::api::ClientVersion;
use crate::providers::{Provider, proxy_aylo_thumb, report_provider_error, requester_or_default};
use crate::providers::{
Provider, probe_first_archive, proxy_aylo_thumb, report_provider_error, requester_or_default,
slug_dashed,
};
use crate::status::*;
use crate::util::cache::VideoCache;
use crate::util::parse_abbreviated_number;
@@ -45,6 +48,8 @@ error_chain! {
pub struct PornhubProvider {
url: String,
tag_map: Arc<RwLock<HashMap<String, TagInfo>>>,
/// Queries already probed on the site that are neither a pornstar, model nor channel.
probe_misses: Arc<RwLock<std::collections::HashSet<String>>>,
}
#[derive(Debug, Clone, Copy)]
@@ -91,6 +96,7 @@ impl PornhubProvider {
let provider = Self {
url: BASE_URL.to_string(),
tag_map: Arc::new(RwLock::new(HashMap::new())),
probe_misses: Arc::new(RwLock::new(std::collections::HashSet::new())),
};
provider.spawn_initial_load();
provider
@@ -588,6 +594,43 @@ impl PornhubProvider {
values.push(normalized.to_string());
}
/// A free-form query that is not in the loaded catalogs may still name a
/// pornstar, model or channel. Probe the site (unknown names 404 or
/// redirect away) and remember the hit in `tag_map` so later pages and
/// queries resolve without another request.
async fn learn_query_target(&self, query: &str, options: &ServerOptions) {
let key = query.trim().to_ascii_lowercase();
let slug = slug_dashed(query);
if slug.is_empty() || self.probe_misses.read().unwrap().contains(&key) {
return;
}
let kinds = [
QueryTargetKind::Pornstar,
QueryTargetKind::Model,
QueryTargetKind::Channel,
];
let urls: Vec<String> = kinds
.iter()
.map(|kind| format!("{}/{}/{slug}/videos", self.url, kind.path_segment()))
.collect();
let mut requester = requester_or_default(options, CHANNEL_ID, "learn_query_target");
let hit = probe_first_archive(&mut requester, &urls, &format!("{}/", self.url), |body| {
body.contains("pcVideoListItem")
})
.await;
match hit {
Some((index, _)) => {
self.tag_map.write().unwrap().insert(
key,
TagInfo { kind: kinds[index], slug, title: query.trim().to_string() },
);
}
None => {
self.probe_misses.write().unwrap().insert(key);
}
}
}
async fn fetch_listing(
&self,
cache: VideoCache,
@@ -596,6 +639,11 @@ impl PornhubProvider {
query: Option<&str>,
options: ServerOptions,
) -> Result<Vec<VideoItem>> {
if let Some(q) = query.map(str::trim).filter(|q| !q.is_empty()) {
if self.parse_query_target(q).is_none() {
self.learn_query_target(q, &options).await;
}
}
if query.is_some() && self.tag_map.read().unwrap().is_empty() {
let _ = Self::load_tags(&self.url, Arc::clone(&self.tag_map)).await;
}