This commit is contained in:
Simon
2026-04-05 20:31:38 +00:00
parent 9773590f64
commit 7b464fe796
10 changed files with 815 additions and 565 deletions

View File

@@ -9,6 +9,8 @@ use crate::videos::{ServerOptions, VideoItem};
use async_trait::async_trait;
use error_chain::error_chain;
use htmlentity::entity::{ICodedDataTrait, decode};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::vec;
pub const CHANNEL_METADATA: crate::providers::ProviderChannelMetadata =
@@ -27,14 +29,66 @@ error_chain! {
#[derive(Debug, Clone)]
pub struct PornhatProvider {
url: String,
tag_map: Arc<RwLock<HashMap<String, String>>>,
}
impl PornhatProvider {
pub fn new() -> Self {
PornhatProvider {
url: "https://www.pornhat.com".to_string(),
tag_map: Arc::new(RwLock::new(HashMap::new())),
}
}
fn normalize_key(value: &str) -> String {
value
.trim()
.to_ascii_lowercase()
.replace(['_', '-'], " ")
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
}
fn humanize_slug(value: &str) -> String {
value
.trim_matches('/')
.replace('-', " ")
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
}
fn insert_tag_mapping(&self, kind: &str, slug: &str, title: Option<&str>) {
let slug = slug.trim().trim_matches('/');
if slug.is_empty() {
return;
}
let path = format!("{kind}/{slug}");
if let Ok(mut map) = self.tag_map.write() {
map.insert(Self::normalize_key(slug), path.clone());
let normalized_title = Self::normalize_key(title.unwrap_or(slug));
if !normalized_title.is_empty() {
map.insert(normalized_title, path);
}
}
}
fn resolve_query_path(&self, query: &str) -> Option<String> {
let trimmed = query.trim().trim_start_matches('@');
if let Some((kind, raw_value)) = trimmed.split_once(':') {
let kind = kind.trim().to_ascii_lowercase();
let value = raw_value.trim().trim_matches('/').replace(' ', "-");
if !value.is_empty() && matches!(kind.as_str(), "sites" | "models") {
return Some(format!("{kind}/{value}"));
}
}
let normalized = Self::normalize_key(trimmed);
if normalized.is_empty() {
return None;
}
self.tag_map.read().ok()?.get(&normalized).cloned()
}
fn build_channel(&self, _clientversion: ClientVersion) -> Channel {
Channel {
id: "pornhat".to_string(),
@@ -127,16 +181,8 @@ impl PornhatProvider {
) -> Result<Vec<VideoItem>> {
let search_string = query.to_lowercase().trim().replace(" ", "-");
let mut video_url = format!("{}/search/{}/{}/", self.url, search_string, page);
if search_string.starts_with("@") {
let url_part = search_string
.split("@")
.collect::<Vec<&str>>()
.get(1)
.copied()
.unwrap_or_default()
.replace(":", "/");
video_url = format!("{}/{}/", self.url, url_part);
if let Some(path) = self.resolve_query_path(query) {
video_url = format!("{}/{}/{}/", self.url, path, page);
}
// Check our Video Cache. If the result is younger than 1 hour, we return it.
let old_items = match cache.get(&video_url) {
@@ -296,7 +342,8 @@ impl PornhatProvider {
.collect::<Vec<String>>();
for tag in raw_tags {
if !tag.is_empty() {
tags.push(format!("@sites:{}", tag));
self.insert_tag_mapping("sites", &tag, None);
tags.push(Self::humanize_slug(&tag));
}
}
}
@@ -316,7 +363,8 @@ impl PornhatProvider {
.collect::<Vec<String>>();
for tag in raw_tags {
if !tag.is_empty() {
tags.push(format!("@models:{}", tag));
self.insert_tag_mapping("models", &tag, None);
tags.push(Self::humanize_slug(&tag));
}
}
}