javtiful fix

This commit is contained in:
Simon
2026-04-25 15:57:23 +00:00
committed by ForgeCode
parent 6a72f84c17
commit 635c45d2c1
3 changed files with 136 additions and 98 deletions

View File

@@ -1,4 +1,6 @@
use ntex::web;
use serde_json::Value;
use url::Url;
use wreq::Version;
use crate::util::requester::Requester;
@@ -11,59 +13,151 @@ impl JavtifulProxy {
JavtifulProxy {}
}
fn normalize_detail_request(endpoint: &str) -> Option<(String, String)> {
let endpoint = endpoint.trim().trim_start_matches('/');
if endpoint.is_empty() {
return None;
}
let detail_url = if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
endpoint.to_string()
} else if endpoint.starts_with("javtiful.com/") || endpoint.starts_with("www.javtiful.com/")
{
format!("https://{endpoint}")
} else {
format!("https://javtiful.com/{endpoint}")
};
let detail_url = if detail_url.starts_with("http://") {
detail_url.replacen("http://", "https://", 1)
} else {
detail_url
};
if !Self::is_allowed_detail_url(&detail_url) {
return None;
}
let video_id = Url::parse(&detail_url)
.ok()
.and_then(|url| {
let mut segments = url.path_segments()?;
if segments.next()? != "video" {
return None;
}
segments.next().map(ToOwned::to_owned)
})
.filter(|value| value.chars().all(|c| c.is_ascii_digit()) && !value.is_empty())?;
Some((detail_url, video_id))
}
fn is_allowed_detail_url(url: &str) -> bool {
let Some(parsed) = Url::parse(url).ok() else {
return false;
};
if parsed.scheme() != "https" {
return false;
}
let Some(host) = parsed.host_str() else {
return false;
};
(host == "javtiful.com" || host == "www.javtiful.com")
&& parsed.path().starts_with("/video/")
}
fn extract_token(html: &str) -> Option<String> {
html.split("data-csrf-token=\"")
.nth(1)
.and_then(|value| value.split('"').next())
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
}
fn extract_playlist_url(payload: &str) -> Option<String> {
let json = serde_json::from_str::<Value>(payload).ok()?;
json.get("playlist")
.and_then(Value::as_str)
.or_else(|| json.get("playlists").and_then(Value::as_str))
.map(str::trim)
.map(ToOwned::to_owned)
.filter(|value| value.starts_with("https://"))
}
pub async fn get_video_url(
&self,
url: String,
requester: web::types::State<Requester>,
) -> String {
let mut requester = requester.get_ref().clone();
let endpoint = url
.trim_start_matches('/')
.strip_prefix("https://")
.or_else(|| url.trim_start_matches('/').strip_prefix("http://"))
.unwrap_or(url.trim_start_matches('/'))
.trim_start_matches("www.javtiful.com/")
.trim_start_matches("javtiful.com/")
.trim_start_matches('/')
.to_string();
let detail_url = format!("https://javtiful.com/{endpoint}");
let text = requester.get(&detail_url, None).await.unwrap_or_default();
if text.is_empty() {
return "".to_string();
}
let video_id = endpoint.split('/').nth(1).unwrap_or("").to_string();
let Some((detail_url, video_id)) = Self::normalize_detail_request(&url) else {
return String::new();
};
let token = text
.split("data-csrf-token=\"")
.nth(1)
.and_then(|s| s.split('"').next())
.unwrap_or("")
.to_string();
let html = requester.get(&detail_url, Some(Version::HTTP_11)).await;
let Ok(html) = html else {
return String::new();
};
if html.is_empty() {
return String::new();
}
let Some(token) = Self::extract_token(&html) else {
return String::new();
};
let form = wreq::multipart::Form::new()
.text("video_id", video_id.clone())
.text("video_id", video_id)
.text("pid_c", "".to_string())
.text("token", token.clone());
.text("token", token);
let resp = match requester
.post_multipart(
"https://javtiful.com/ajax/get_cdn",
form,
vec![("Referer".to_string(), detail_url)],
vec![
("Referer".to_string(), detail_url),
("Origin".to_string(), "https://javtiful.com".to_string()),
("Accept".to_string(), "*/*".to_string()),
],
Some(Version::HTTP_11),
)
.await
{
Ok(r) => r,
Err(_) => return "".to_string(),
Err(_) => return String::new(),
};
let text = resp.text().await.unwrap_or_default();
let json: serde_json::Value =
serde_json::from_str(&text).unwrap_or(serde_json::Value::Null);
let video_url = json
.get("playlists")
.map(|v| v.to_string().replace("\"", ""))
.unwrap_or_default();
return video_url;
let payload = resp.text().await.unwrap_or_default();
Self::extract_playlist_url(&payload).unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::JavtifulProxy;
#[test]
fn normalizes_detail_request_with_full_url() {
let (url, video_id) =
JavtifulProxy::normalize_detail_request("https://javtiful.com/video/106796/fns-176")
.expect("detail request should parse");
assert_eq!(url, "https://javtiful.com/video/106796/fns-176");
assert_eq!(video_id, "106796");
}
#[test]
fn normalizes_detail_request_with_path_only() {
let (url, video_id) = JavtifulProxy::normalize_detail_request("video/1000/demo")
.expect("detail request should parse");
assert_eq!(url, "https://javtiful.com/video/1000/demo");
assert_eq!(video_id, "1000");
}
#[test]
fn extracts_playlist_from_payload() {
let payload = r#"{"status":"ok","playlist":"https://cdn.example/106796.mp4"}"#;
assert_eq!(
JavtifulProxy::extract_playlist_url(payload).as_deref(),
Some("https://cdn.example/106796.mp4")
);
}
}