Fix hypnotube search sessions; check.py confirms HEAD failures with GET

The site serves one results page per searchgate POST, so later searches
and page 2 returned nothing on the shared cookie session. Use an isolated
session per (query, page). check.py now retries a refused HEAD (403/404/
405/501) with a ranged GET before reporting a URL as unreachable.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUYdCh2nCVsQYgJAT7gwSi
This commit is contained in:
Simon
2026-10-10 08:24:15 +00:00
parent 78acac2150
commit a1e6021766
3 changed files with 50 additions and 30 deletions

View File

@@ -14,7 +14,11 @@ use htmlentity::entity::{ICodedDataTrait, decode};
use std::sync::{Arc, RwLock};
use std::{thread, vec};
use titlecase::Titlecase;
use wreq::Client;
use wreq::Version;
use wreq::cookie::Jar;
use wreq::redirect::Policy;
use wreq_util::Emulation;
pub const CHANNEL_METADATA: crate::providers::ProviderChannelMetadata =
crate::providers::ProviderChannelMetadata {
@@ -246,6 +250,43 @@ impl HypnotubeProvider {
video_items
}
/// Fetches one page of search results.
///
/// The site hands out exactly one results page per `searchgate.php` POST
/// (a second GET of the same search, or any later search on the same
/// session, comes back empty), so every (query, page) uses its own
/// isolated cookie session instead of the shared requester jar. Page 1 is
/// the redirect target of the POST; page N>1 is fetched directly.
async fn fetch_search_html(&self, query: &str, page: u8) -> Result<String> {
let client = Client::builder()
.tls_cert_verification(false)
.emulation(Emulation::Firefox136)
.cookie_provider(Arc::new(Jar::default()))
.redirect(if page <= 1 { Policy::default() } else { Policy::none() })
.build()?;
let body = format!("q={}&type=videos", query.trim().replace(' ', "+"));
let gate = client
.post(format!("{}/searchgate.php", self.url))
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Referer", format!("{}/", self.url))
.body(body)
.send()
.await?;
if page <= 1 {
return Ok(gate.text().await?);
}
let slug = query.trim().replace(' ', "%20");
let page_url = format!("{}/search/videos/{}/page{}.html", self.url, slug, page);
let response = client
.get(page_url)
.header("Referer", format!("{}/search/videos/{}/", self.url, slug))
.send()
.await?;
Ok(response.text().await?)
}
async fn query(
&self,
cache: VideoCache,
@@ -281,41 +322,18 @@ impl HypnotubeProvider {
}
};
let mut requester =
crate::providers::requester_or_default(&options, module_path!(), "missing_requester");
let post_response = match requester
.post(
format!("{}/searchgate.php", self.url).as_str(),
format!("q={}&type=videos", query.replace(" ", "+")).as_str(),
vec![("Content-Type", "application/x-www-form-urlencoded")],
)
.await
{
Ok(response) => response,
let text = match self.fetch_search_html(query, page).await {
Ok(text) => text,
Err(e) => {
crate::providers::report_provider_error(
"hypnotube",
"query.search_post",
"query.search",
&format!("url={video_url}; error={e}"),
)
.await;
return old_items;
}
};
let text = match post_response.text().await {
Ok(t) => t,
Err(e) => {
eprint!("Hypnotube search POST request failed: {}", e);
crate::providers::report_provider_error_background(
"hypnotube",
"query.search_post.text",
&e.to_string(),
);
return vec![];
}
};
// println!("Hypnotube search POST response status: {}", p.text().await.unwrap_or_default());
// let text = requester.get(&video_url, Some(Version::HTTP_11)).await.unwrap();
if text.contains("Sorry, no results were found.") {
return vec![];
}