This commit is contained in:
Simon
2026-03-30 19:21:42 +00:00
parent 429fb16fbd
commit 01831c70e7
2 changed files with 217 additions and 22 deletions

View File

@@ -8,8 +8,6 @@ use crate::util::requester::Requester;
pub struct DoodstreamProxy {}
impl DoodstreamProxy {
const ROOT_REFERER: &'static str = "https://turboplayers.xyz/";
pub fn new() -> Self {
Self {}
}
@@ -55,23 +53,23 @@ impl DoodstreamProxy {
|| url.path().starts_with("/d/")
}
fn request_origin(detail_url: &str) -> Option<String> {
let parsed = Url::parse(detail_url).ok()?;
let host = parsed.host_str()?;
Some(format!("{}://{}", parsed.scheme(), host))
}
fn request_headers(detail_url: &str) -> Vec<(String, String)> {
let origin = Self::request_origin(detail_url).unwrap_or_else(|| "https://turboplayers.xyz".to_string());
vec![
("Referer".to_string(), Self::ROOT_REFERER.to_string()),
("Origin".to_string(), "https://turboplayers.xyz".to_string()),
("Referer".to_string(), format!("{}/", origin.trim_end_matches('/'))),
("Origin".to_string(), origin),
(
"Accept".to_string(),
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8".to_string(),
),
("Accept-Language".to_string(), "en-US,en;q=0.9".to_string()),
(
"Sec-Fetch-Site".to_string(),
if detail_url.contains("trailerhg.xyz") {
"cross-site".to_string()
} else {
"same-origin".to_string()
},
),
("Sec-Fetch-Site".to_string(), "same-origin".to_string()),
]
}
@@ -223,6 +221,75 @@ impl DoodstreamProxy {
.next()
.or_else(|| Self::extract_literal_url(&unpacked))
}
fn extract_pass_md5_url(text: &str, detail_url: &str) -> Option<String> {
let decoded = text.replace("\\/", "/");
let absolute_regex =
Self::regex(r#"https?://[^\s"'<>]+/pass_md5/[^\s"'<>]+"#)?;
if let Some(url) = absolute_regex.find(&decoded).map(|value| value.as_str().to_string()) {
return Some(url);
}
let relative_regex = Self::regex(r#"/pass_md5/[^\s"'<>]+"#)?;
let relative = relative_regex.find(&decoded)?.as_str();
let origin = Self::request_origin(detail_url)?;
Some(format!("{origin}{relative}"))
}
fn compose_pass_md5_media_url(pass_md5_url: &str, response_body: &str) -> Option<String> {
let raw = response_body
.trim()
.trim_matches('"')
.trim_matches('\'')
.replace("\\/", "/");
if raw.is_empty() {
return None;
}
let mut media_url = if raw.starts_with("https://") || raw.starts_with("http://") {
raw
} else if let Some(rest) = raw.strip_prefix("//") {
format!("https://{rest}")
} else {
let parsed = Url::parse(pass_md5_url).ok()?;
let host = parsed.host_str()?;
format!("{}://{}{}", parsed.scheme(), host, raw)
};
let query = Url::parse(pass_md5_url)
.ok()
.and_then(|url| url.query().map(str::to_string));
if let Some(query) = query {
if !query.is_empty() && !media_url.contains("token=") {
let separator = if media_url.contains('?') { '&' } else { '?' };
media_url.push(separator);
media_url.push_str(&query);
}
}
Some(Self::sanitize_media_url(&media_url))
}
async fn resolve_stream_from_pass_md5(
detail_url: &str,
html: &str,
requester: &mut Requester,
) -> Option<String> {
let pass_md5_url = Self::extract_pass_md5_url(html, detail_url).or_else(|| {
Self::unpack_packer(html).and_then(|unpacked| Self::extract_pass_md5_url(&unpacked, detail_url))
})?;
let headers = vec![
("Referer".to_string(), detail_url.to_string()),
("X-Requested-With".to_string(), "XMLHttpRequest".to_string()),
("Accept".to_string(), "*/*".to_string()),
];
let response = requester
.get_with_headers(&pass_md5_url, headers, None)
.await
.ok()?;
Self::compose_pass_md5_media_url(&pass_md5_url, &response)
}
}
impl crate::proxies::Proxy for DoodstreamProxy {
@@ -240,7 +307,15 @@ impl crate::proxies::Proxy for DoodstreamProxy {
Err(_) => return String::new(),
};
Self::extract_stream_url(&html).unwrap_or_default()
if let Some(url) = Self::extract_stream_url(&html) {
return url;
}
if let Some(url) = Self::resolve_stream_from_pass_md5(&detail_url, &html, &mut requester).await {
return url;
}
String::new()
}
}
@@ -292,4 +367,30 @@ mod tests {
Some("https://cdn.example/master.m3u8?t=1")
);
}
#[test]
fn composes_media_url_from_pass_md5_response() {
let pass_md5_url = "https://trailerhg.xyz/pass_md5/abc123/def456?token=t0k3n&expiry=1775000000";
let body = "https://g4vsrqvtrj.pinebrookproductionlab.shop/1ghkpx2e8jnal/hls3/01/08534/syyzvotfnhaa_l/master.txt";
assert_eq!(
DoodstreamProxy::compose_pass_md5_media_url(pass_md5_url, body).as_deref(),
Some(
"https://g4vsrqvtrj.pinebrookproductionlab.shop/1ghkpx2e8jnal/hls3/01/08534/syyzvotfnhaa_l/master.txt?token=t0k3n&expiry=1775000000"
)
);
}
#[test]
fn extracts_relative_pass_md5_url() {
let html = r#"
<script>
var file = "/pass_md5/abc123/def456?token=t0k3n&expiry=1775000000";
</script>
"#;
assert_eq!(
DoodstreamProxy::extract_pass_md5_url(html, "https://trailerhg.xyz/e/ttdc7a6qpskt")
.as_deref(),
Some("https://trailerhg.xyz/pass_md5/abc123/def456?token=t0k3n&expiry=1775000000")
);
}
}