freejse searches

This commit is contained in:
Simon
2026-03-31 23:01:51 +00:00
parent 38acb2b5a5
commit e2796bfd71
2 changed files with 79 additions and 26 deletions

View File

@@ -9,10 +9,10 @@ use crate::videos::{ServerOptions, VideoFormat, VideoItem};
use async_trait::async_trait; use async_trait::async_trait;
use error_chain::error_chain; use error_chain::error_chain;
use htmlentity::entity::{ICodedDataTrait, decode}; use htmlentity::entity::{ICodedDataTrait, decode};
use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
use scraper::{Html, Selector}; use scraper::{Html, Selector};
use std::collections::HashSet; use std::collections::HashSet;
use std::vec; use std::vec;
use url::form_urlencoded::Serializer;
pub const CHANNEL_METADATA: crate::providers::ProviderChannelMetadata = pub const CHANNEL_METADATA: crate::providers::ProviderChannelMetadata =
crate::providers::ProviderChannelMetadata { crate::providers::ProviderChannelMetadata {
@@ -151,19 +151,22 @@ impl FreeusepornProvider {
} }
} }
fn build_list_url( fn append_sort_and_page(&self, base_url: &str, sort: &str, page: u8) -> String {
&self, let mut params = vec![format!("o={}", Self::sort_param(sort))];
sort: &str, if page > 1 {
page: u8, params.push(format!("page={page}"));
query: Option<&str>, }
category: Option<&str>,
) -> String { if params.is_empty() {
let path = if let Some(query) = query.map(str::trim).filter(|value| !value.is_empty()) { return base_url.to_string();
format!( }
"/search/videos/{}",
utf8_percent_encode(query, NON_ALPHANUMERIC) let separator = if base_url.contains('?') { "&" } else { "?" };
) format!("{base_url}{separator}{}", params.join("&"))
} else if let Some(category) = category }
fn build_list_url(&self, sort: &str, page: u8, category: Option<&str>) -> String {
let path = if let Some(category) = category
.map(str::trim) .map(str::trim)
.filter(|value| !value.is_empty() && *value != "all") .filter(|value| !value.is_empty() && *value != "all")
{ {
@@ -172,12 +175,34 @@ impl FreeusepornProvider {
"/videos".to_string() "/videos".to_string()
}; };
let mut params = vec![format!("o={}", Self::sort_param(sort))]; let base_url = format!("{}{}", self.url, path);
if page > 1 { self.append_sort_and_page(&base_url, sort, page)
params.push(format!("page={page}")); }
}
format!("{}{}?{}", self.url, path, params.join("&")) fn build_search_request_body(query: &str) -> String {
let mut serializer = Serializer::new(String::new());
serializer.append_pair("search_query", query);
serializer.finish()
}
async fn resolve_search_url(&self, query: &str, options: &ServerOptions) -> Result<String> {
let search_url = format!("{}/search/videos", self.url);
let search_body = Self::build_search_request_body(query);
let referer = format!("{}/videos", self.url);
let mut requester = requester_or_default(options, module_path!(), "missing_requester");
let response = requester
.post(
&search_url,
&search_body,
vec![
("Content-Type", "application/x-www-form-urlencoded"),
("Referer", referer.as_str()),
],
)
.await
.map_err(|error| format!("search submit failed url={search_url}; error={error}"))?;
Ok(response.uri().to_string().trim_end_matches('/').to_string())
} }
fn build_formats(&self, id: &str) -> Vec<VideoFormat> { fn build_formats(&self, id: &str) -> Vec<VideoFormat> {
@@ -380,7 +405,7 @@ impl FreeusepornProvider {
sort: &str, sort: &str,
options: ServerOptions, options: ServerOptions,
) -> Result<Vec<VideoItem>> { ) -> Result<Vec<VideoItem>> {
let url = self.build_list_url(sort, page, None, options.category.as_deref()); let url = self.build_list_url(sort, page, options.category.as_deref());
self.fetch_listing(cache, url, options, "get.request").await self.fetch_listing(cache, url, options, "get.request").await
} }
@@ -392,7 +417,19 @@ impl FreeusepornProvider {
sort: &str, sort: &str,
options: ServerOptions, options: ServerOptions,
) -> Result<Vec<VideoItem>> { ) -> Result<Vec<VideoItem>> {
let url = self.build_list_url(sort, page, Some(query), None); let search_base = match self.resolve_search_url(query, &options).await {
Ok(url) => url,
Err(error) => {
report_provider_error(
"freeuseporn",
"query.search_submit",
&error.to_string(),
)
.await;
return Ok(vec![]);
}
};
let url = self.append_sort_and_page(&search_base, sort, page);
self.fetch_listing(cache, url, options, "query.request").await self.fetch_listing(cache, url, options, "query.request").await
} }
} }
@@ -464,16 +501,28 @@ mod tests {
let provider = provider(); let provider = provider();
assert_eq!( assert_eq!(
provider.build_list_url("recent", 1, None, None), provider.build_list_url("recent", 1, None),
"https://www.freeuseporn.com/videos?o=mr" "https://www.freeuseporn.com/videos?o=mr"
); );
assert_eq!( assert_eq!(
provider.build_list_url("viewed", 2, None, Some("mind-control")), provider.build_list_url("viewed", 2, Some("mind-control")),
"https://www.freeuseporn.com/videos/mind-control?o=mv&page=2" "https://www.freeuseporn.com/videos/mind-control?o=mv&page=2"
); );
assert_eq!( assert_eq!(
provider.build_list_url("favorites", 3, Some("mind control"), None), provider.append_sort_and_page(
"https://www.freeuseporn.com/search/videos/mind%20control?o=tf&page=3" "https://www.freeuseporn.com/search/videos/Nicole-Kitt",
"favorites",
3
),
"https://www.freeuseporn.com/search/videos/Nicole-Kitt?o=tf&page=3"
);
}
#[test]
fn builds_search_request_body_with_form_encoding() {
assert_eq!(
FreeusepornProvider::build_search_request_body("Nicole Kitt & Cory Chase"),
"search_query=Nicole+Kitt+%26+Cory+Chase"
); );
} }

View File

@@ -375,7 +375,11 @@ impl Requester {
Ok(response) Ok(response)
} }
#[cfg(any(not(hottub_single_provider), hottub_provider = "hypnotube"))] #[cfg(any(
not(hottub_single_provider),
hottub_provider = "hypnotube",
hottub_provider = "freeuseporn",
))]
pub async fn post( pub async fn post(
&mut self, &mut self,
url: &str, url: &str,