hqporner fix
This commit is contained in:
@@ -6,7 +6,7 @@ use crate::util::cache::VideoCache;
|
|||||||
use crate::util::discord::{format_error_chain, send_discord_error_report};
|
use crate::util::discord::{format_error_chain, send_discord_error_report};
|
||||||
use crate::util::requester::Requester;
|
use crate::util::requester::Requester;
|
||||||
use crate::util::time::parse_time_to_seconds;
|
use crate::util::time::parse_time_to_seconds;
|
||||||
use crate::videos::{ServerOptions, VideoItem};
|
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};
|
||||||
@@ -323,12 +323,40 @@ impl HqpornerProvider {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let duration = parse_time_to_seconds(&raw_duration).unwrap_or(0) as u32;
|
let duration = parse_time_to_seconds(&raw_duration).unwrap_or(0) as u32;
|
||||||
|
|
||||||
|
let stripped_detail_url = crate::providers::strip_url_scheme(&detail_url);
|
||||||
let proxied_url = crate::providers::build_proxy_url(
|
let proxied_url = crate::providers::build_proxy_url(
|
||||||
options,
|
options,
|
||||||
"hqporner",
|
"hqporner",
|
||||||
&crate::providers::strip_url_scheme(&detail_url),
|
&stripped_detail_url,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let quality_target = |quality: &str| -> String {
|
||||||
|
format!("{stripped_detail_url}/__quality__/{quality}")
|
||||||
|
};
|
||||||
|
let formats = vec![
|
||||||
|
VideoFormat::new(
|
||||||
|
crate::providers::build_proxy_url(options, "hqporner", &quality_target("1080")),
|
||||||
|
"1080p".to_string(),
|
||||||
|
"mp4".to_string(),
|
||||||
|
)
|
||||||
|
.format_id("1080p".to_string())
|
||||||
|
.format_note("1080p Full HD".to_string()),
|
||||||
|
VideoFormat::new(
|
||||||
|
crate::providers::build_proxy_url(options, "hqporner", &quality_target("720")),
|
||||||
|
"720p".to_string(),
|
||||||
|
"mp4".to_string(),
|
||||||
|
)
|
||||||
|
.format_id("720p".to_string())
|
||||||
|
.format_note("720p HD".to_string()),
|
||||||
|
VideoFormat::new(
|
||||||
|
crate::providers::build_proxy_url(options, "hqporner", &quality_target("360")),
|
||||||
|
"360p".to_string(),
|
||||||
|
"mp4".to_string(),
|
||||||
|
)
|
||||||
|
.format_id("360p".to_string())
|
||||||
|
.format_note("360p".to_string()),
|
||||||
|
];
|
||||||
|
|
||||||
Ok(VideoItem::new(
|
Ok(VideoItem::new(
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
@@ -336,7 +364,8 @@ impl HqpornerProvider {
|
|||||||
"hqporner".into(),
|
"hqporner".into(),
|
||||||
thumb,
|
thumb,
|
||||||
duration,
|
duration,
|
||||||
))
|
)
|
||||||
|
.formats(formats))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use ntex::web;
|
use ntex::web;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use std::collections::HashMap;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::util::requester::Requester;
|
use crate::util::requester::Requester;
|
||||||
@@ -12,19 +13,33 @@ impl HqpornerProxy {
|
|||||||
Self {}
|
Self {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_detail_url(endpoint: &str) -> Option<String> {
|
fn normalize_detail_request(endpoint: &str) -> Option<(String, Option<u16>)> {
|
||||||
let endpoint = endpoint.trim().trim_start_matches('/');
|
let endpoint = endpoint.trim().trim_start_matches('/');
|
||||||
if endpoint.is_empty() {
|
if endpoint.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let detail_url = if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
|
let (detail_part, quality) = match endpoint.split_once("/__quality__/") {
|
||||||
endpoint.to_string()
|
Some((detail, quality)) => {
|
||||||
} else {
|
let requested = quality
|
||||||
format!("https://{}", endpoint.trim_start_matches('/'))
|
.trim()
|
||||||
|
.trim_end_matches('/')
|
||||||
|
.trim_end_matches('p')
|
||||||
|
.parse::<u16>()
|
||||||
|
.ok();
|
||||||
|
(detail, requested)
|
||||||
|
}
|
||||||
|
None => (endpoint, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
Self::is_allowed_detail_url(&detail_url).then_some(detail_url)
|
let detail_url = if detail_part.starts_with("http://") || detail_part.starts_with("https://")
|
||||||
|
{
|
||||||
|
detail_part.to_string()
|
||||||
|
} else {
|
||||||
|
format!("https://{}", detail_part.trim_start_matches('/'))
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::is_allowed_detail_url(&detail_url).then_some((detail_url, quality))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_allowed_detail_url(url: &str) -> bool {
|
fn is_allowed_detail_url(url: &str) -> bool {
|
||||||
@@ -62,13 +77,10 @@ impl HqpornerProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn extract_player_url(detail_html: &str) -> Option<String> {
|
fn extract_player_url(detail_html: &str) -> Option<String> {
|
||||||
let path = detail_html
|
let pattern = r#"(?is)url\s*:\s*['"](/blocks/(?:altplayer|nativeplayer)\.php\?i=[^'"]+)['"]"#;
|
||||||
.split("url: '/blocks/altplayer.php?i=")
|
let captures = Self::regex(pattern)?.captures(detail_html)?;
|
||||||
.nth(1)
|
let path = captures.get(1)?.as_str();
|
||||||
.and_then(|s| s.split('\'').next())?;
|
Some(Self::normalize_url(path))
|
||||||
Some(Self::normalize_url(&format!(
|
|
||||||
"/blocks/altplayer.php?i={path}"
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_source_url(player_html: &str) -> Option<String> {
|
fn extract_source_url(player_html: &str) -> Option<String> {
|
||||||
@@ -90,6 +102,26 @@ impl HqpornerProxy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let iframe_regexes = [
|
||||||
|
r#"(?is)<iframe[^>]+src="([^"]+)""#,
|
||||||
|
r#"(?is)<iframe[^>]+src='([^']+)'"#,
|
||||||
|
r#"(?is)src=\\\"([^\\"]+)\\\""#,
|
||||||
|
r#"(?is)src=\\'([^\\']+)\\'"#,
|
||||||
|
];
|
||||||
|
for pattern in iframe_regexes {
|
||||||
|
let Some(regex) = Self::regex(pattern) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Some(url) = regex
|
||||||
|
.captures(player_html)
|
||||||
|
.and_then(|caps| caps.get(1))
|
||||||
|
.map(|m| Self::normalize_url(m.as_str()))
|
||||||
|
.filter(|value| !value.is_empty())
|
||||||
|
{
|
||||||
|
return Some(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let source_regex = Self::regex(r#"src=\\\"([^\\"]+)\\\""#)?;
|
let source_regex = Self::regex(r#"src=\\\"([^\\"]+)\\\""#)?;
|
||||||
source_regex
|
source_regex
|
||||||
.captures(player_html)
|
.captures(player_html)
|
||||||
@@ -97,11 +129,63 @@ impl HqpornerProxy {
|
|||||||
.map(|m| Self::normalize_url(m.as_str()))
|
.map(|m| Self::normalize_url(m.as_str()))
|
||||||
.filter(|value| !value.is_empty())
|
.filter(|value| !value.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_quality_urls(video_page_html: &str) -> HashMap<u16, String> {
|
||||||
|
let mut urls = HashMap::new();
|
||||||
|
let Some(regex) =
|
||||||
|
Self::regex(r#"(?i)(?:https?:)?//[^"'\\\s]+/pubs/[A-Za-z0-9._-]+/(360|720|1080)\.mp4"#)
|
||||||
|
else {
|
||||||
|
return urls;
|
||||||
|
};
|
||||||
|
|
||||||
|
for captures in regex.captures_iter(video_page_html) {
|
||||||
|
let Some(full_match) = captures.get(0) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(quality_match) = captures.get(1) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(quality) = quality_match.as_str().parse::<u16>().ok() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let normalized = Self::normalize_url(full_match.as_str());
|
||||||
|
if !normalized.is_empty() {
|
||||||
|
urls.insert(quality, normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
urls
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_quality_url(quality_urls: &HashMap<u16, String>, requested: Option<u16>) -> Option<String> {
|
||||||
|
let fallbacks = match requested.unwrap_or(1080) {
|
||||||
|
1080 => [1080u16, 720, 360].as_slice(),
|
||||||
|
720 => [720u16, 360].as_slice(),
|
||||||
|
360 => [360u16].as_slice(),
|
||||||
|
other if other > 1080 => [1080u16, 720, 360].as_slice(),
|
||||||
|
other if other > 720 => [720u16, 360].as_slice(),
|
||||||
|
_ => [360u16].as_slice(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for quality in fallbacks {
|
||||||
|
if let Some(url) = quality_urls.get(quality) {
|
||||||
|
return Some(url.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(url) = quality_urls.get(&1080) {
|
||||||
|
return Some(url.clone());
|
||||||
|
}
|
||||||
|
if let Some(url) = quality_urls.get(&720) {
|
||||||
|
return Some(url.clone());
|
||||||
|
}
|
||||||
|
quality_urls.get(&360).cloned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::proxies::Proxy for HqpornerProxy {
|
impl crate::proxies::Proxy for HqpornerProxy {
|
||||||
async fn get_video_url(&self, url: String, requester: web::types::State<Requester>) -> String {
|
async fn get_video_url(&self, url: String, requester: web::types::State<Requester>) -> String {
|
||||||
let Some(detail_url) = Self::normalize_detail_url(&url) else {
|
let Some((detail_url, requested_quality)) = Self::normalize_detail_request(&url) else {
|
||||||
return String::new();
|
return String::new();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -116,18 +200,142 @@ impl crate::proxies::Proxy for HqpornerProxy {
|
|||||||
return String::new();
|
return String::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(player_url) = Self::extract_player_url(&detail_html) else {
|
let mut source_page_url = String::new();
|
||||||
return String::new();
|
if let Some(player_url) = Self::extract_player_url(&detail_html) {
|
||||||
};
|
let player_html = requester
|
||||||
|
.get_with_headers(&player_url, headers.clone(), None)
|
||||||
let player_html = requester
|
.await
|
||||||
.get_with_headers(&player_url, headers, None)
|
.unwrap_or_default();
|
||||||
.await
|
if !player_html.is_empty() {
|
||||||
.unwrap_or_default();
|
if let Some(url) = Self::extract_source_url(&player_html) {
|
||||||
if player_html.is_empty() {
|
source_page_url = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if source_page_url.is_empty() {
|
||||||
|
source_page_url = Self::extract_source_url(&detail_html).unwrap_or_default();
|
||||||
|
}
|
||||||
|
if source_page_url.is_empty() {
|
||||||
return String::new();
|
return String::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::extract_source_url(&player_html).unwrap_or_default()
|
let source_page_html = requester
|
||||||
|
.get_with_headers(&source_page_url, headers, None)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
if source_page_html.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let quality_urls = Self::extract_quality_urls(&source_page_html);
|
||||||
|
if quality_urls.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::select_quality_url(&quality_urls, requested_quality).unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::HqpornerProxy;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extract_source_url_supports_iframe_src() {
|
||||||
|
let html = r#"<iframe width="560" height="350" src="//mydaddy.cc/video/f7cbb41e218d3b1dca/&alt" frameborder="0" allowfullscreen=""></iframe>"#;
|
||||||
|
let extracted = HqpornerProxy::extract_source_url(html);
|
||||||
|
assert_eq!(
|
||||||
|
extracted.as_deref(),
|
||||||
|
Some("https://mydaddy.cc/video/f7cbb41e218d3b1dca/&alt")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extract_source_url_supports_source_tag_src() {
|
||||||
|
let html =
|
||||||
|
r#"<video><source src=\"https://cdn.example.com/video.mp4\" type=\"video/mp4\"></video>"#;
|
||||||
|
let extracted = HqpornerProxy::extract_source_url(html);
|
||||||
|
assert_eq!(
|
||||||
|
extracted.as_deref(),
|
||||||
|
Some("https://cdn.example.com/video.mp4")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extract_player_url_supports_altplayer_path() {
|
||||||
|
let html = r#"
|
||||||
|
<script>
|
||||||
|
function altPlayer() {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/blocks/altplayer.php?i=//mydaddy.cc/video/f7cbb41e218d3b1dca/',
|
||||||
|
success: function(data) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
"#;
|
||||||
|
let extracted = HqpornerProxy::extract_player_url(html);
|
||||||
|
assert_eq!(
|
||||||
|
extracted.as_deref(),
|
||||||
|
Some(
|
||||||
|
"https://www.hqporner.com/blocks/altplayer.php?i=//mydaddy.cc/video/f7cbb41e218d3b1dca/"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extract_quality_urls_from_mydaddy_html() {
|
||||||
|
let html = r#"
|
||||||
|
timelinePreview:{file:"//s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/tile.vtt",spriteRelativePath:true,type:"VTT"}
|
||||||
|
<source src="//s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/360.mp4" title="360p" type="video/mp4" />
|
||||||
|
<source src="//s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/720.mp4" title="720p HD" type="video/mp4" />
|
||||||
|
<source src="//s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/1080.mp4" title="1080p Full HD" type="video/mp4" />
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let urls = HqpornerProxy::extract_quality_urls(html);
|
||||||
|
assert_eq!(
|
||||||
|
urls.get(&360).map(String::as_str),
|
||||||
|
Some("https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/360.mp4")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
urls.get(&720).map(String::as_str),
|
||||||
|
Some("https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/720.mp4")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
urls.get(&1080).map(String::as_str),
|
||||||
|
Some("https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/1080.mp4")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_quality_url_falls_back_to_next_lower_quality() {
|
||||||
|
let mut urls = HashMap::new();
|
||||||
|
urls.insert(
|
||||||
|
360,
|
||||||
|
"https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/360.mp4".to_string(),
|
||||||
|
);
|
||||||
|
urls.insert(
|
||||||
|
720,
|
||||||
|
"https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/720.mp4".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let requested_1080 = HqpornerProxy::select_quality_url(&urls, Some(1080));
|
||||||
|
assert_eq!(
|
||||||
|
requested_1080.as_deref(),
|
||||||
|
Some("https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/720.mp4")
|
||||||
|
);
|
||||||
|
|
||||||
|
let requested_720 = HqpornerProxy::select_quality_url(&urls, Some(720));
|
||||||
|
assert_eq!(
|
||||||
|
requested_720.as_deref(),
|
||||||
|
Some("https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/720.mp4")
|
||||||
|
);
|
||||||
|
|
||||||
|
let requested_360 = HqpornerProxy::select_quality_url(&urls, Some(360));
|
||||||
|
assert_eq!(
|
||||||
|
requested_360.as_deref(),
|
||||||
|
Some("https://s43.bigcdn.cc/pubs/69ecfb39b17117.73515587/360.mp4")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user