javtiful fix
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,3 +18,4 @@ Cargo.lock
|
|||||||
*.db
|
*.db
|
||||||
migrations/.keep
|
migrations/.keep
|
||||||
.mcp.json
|
.mcp.json
|
||||||
|
*.mp4*
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
use crate::DbPool;
|
use crate::DbPool;
|
||||||
use crate::api::ClientVersion;
|
use crate::api::ClientVersion;
|
||||||
use crate::providers::Provider;
|
use crate::providers::{Provider, build_proxy_url, strip_url_scheme};
|
||||||
use crate::status::*;
|
use crate::status::*;
|
||||||
use crate::util::cache::VideoCache;
|
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, VideoFormat, VideoItem};
|
use crate::videos::{ServerOptions, VideoItem};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use error_chain::error_chain;
|
use error_chain::error_chain;
|
||||||
@@ -362,20 +362,13 @@ impl JavtifulProvider {
|
|||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string();
|
.to_string();
|
||||||
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 (tags, mut formats, views) = self
|
let (tags, views) = self.extract_media(&video_url, &mut requester).await?;
|
||||||
.extract_media(&video_url, &mut requester, options)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if preview.len() == 0 {
|
if preview.len() == 0 {
|
||||||
preview = format!("https://trailers.jav.si/preview/{id}.mp4");
|
preview = format!("https://trailers.jav.si/preview/{id}.mp4");
|
||||||
}
|
}
|
||||||
if formats.is_empty() && !preview.is_empty() {
|
let proxy_url = build_proxy_url(options, "javtiful", &strip_url_scheme(&video_url));
|
||||||
let mut format = VideoFormat::new(preview.clone(), "preview".to_string(), "video/mp4".to_string());
|
let video_item = VideoItem::new(id, title, proxy_url, "javtiful".into(), thumb, duration)
|
||||||
format.add_http_header("Referer".to_string(), video_url.clone());
|
|
||||||
formats.push(format);
|
|
||||||
}
|
|
||||||
let video_item = VideoItem::new(id, title, video_url, "javtiful".into(), thumb, duration)
|
|
||||||
.formats(formats)
|
|
||||||
.tags(tags)
|
.tags(tags)
|
||||||
.preview(preview)
|
.preview(preview)
|
||||||
.views(views);
|
.views(views);
|
||||||
@@ -386,8 +379,7 @@ impl JavtifulProvider {
|
|||||||
&self,
|
&self,
|
||||||
url: &str,
|
url: &str,
|
||||||
requester: &mut Requester,
|
requester: &mut Requester,
|
||||||
options: &ServerOptions,
|
) -> Result<(Vec<String>, u32)> {
|
||||||
) -> Result<(Vec<String>, Vec<VideoFormat>, u32)> {
|
|
||||||
let text = requester
|
let text = requester
|
||||||
.get(url, Some(Version::HTTP_2))
|
.get(url, Some(Version::HTTP_2))
|
||||||
.await
|
.await
|
||||||
@@ -432,56 +424,7 @@ impl JavtifulProvider {
|
|||||||
.and_then(|s| s.replace(".", "").parse::<u32>().ok())
|
.and_then(|s| s.replace(".", "").parse::<u32>().ok())
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
let quality = "1080p".to_string();
|
Ok((tags, views))
|
||||||
let mut formats = Vec::new();
|
|
||||||
let video_id = url
|
|
||||||
.split("/video/")
|
|
||||||
.nth(1)
|
|
||||||
.and_then(|value| value.split('/').next())
|
|
||||||
.unwrap_or("")
|
|
||||||
.trim();
|
|
||||||
let token = text
|
|
||||||
.split("data-csrf-token=\"")
|
|
||||||
.nth(1)
|
|
||||||
.and_then(|value| value.split('"').next())
|
|
||||||
.unwrap_or("")
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
if !video_id.is_empty() && !token.is_empty() {
|
|
||||||
let form = wreq::multipart::Form::new()
|
|
||||||
.text("video_id", video_id.to_string())
|
|
||||||
.text("pid_c", "".to_string())
|
|
||||||
.text("token", token.to_string());
|
|
||||||
|
|
||||||
if let Ok(response) = requester
|
|
||||||
.post_multipart(
|
|
||||||
"https://javtiful.com/ajax/get_cdn",
|
|
||||||
form,
|
|
||||||
vec![("Referer".to_string(), url.to_string())],
|
|
||||||
Some(Version::HTTP_11),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
let payload = response.text().await.unwrap_or_default();
|
|
||||||
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&payload) {
|
|
||||||
if let Some(cdn_url) = json.get("playlists").and_then(|value| value.as_str()) {
|
|
||||||
if !cdn_url.trim().is_empty() {
|
|
||||||
let mut format = VideoFormat::new(
|
|
||||||
cdn_url.to_string(),
|
|
||||||
quality.clone(),
|
|
||||||
"m3u8".into(),
|
|
||||||
);
|
|
||||||
format.add_http_header("Referer".to_string(), url.to_string());
|
|
||||||
formats.push(format);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = options;
|
|
||||||
|
|
||||||
Ok((tags, formats, views))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use ntex::web;
|
use ntex::web;
|
||||||
|
use serde_json::Value;
|
||||||
|
use url::Url;
|
||||||
use wreq::Version;
|
use wreq::Version;
|
||||||
|
|
||||||
use crate::util::requester::Requester;
|
use crate::util::requester::Requester;
|
||||||
@@ -11,59 +13,151 @@ impl JavtifulProxy {
|
|||||||
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(
|
pub async fn get_video_url(
|
||||||
&self,
|
&self,
|
||||||
url: String,
|
url: String,
|
||||||
requester: web::types::State<Requester>,
|
requester: web::types::State<Requester>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut requester = requester.get_ref().clone();
|
let mut requester = requester.get_ref().clone();
|
||||||
let endpoint = url
|
let Some((detail_url, video_id)) = Self::normalize_detail_request(&url) else {
|
||||||
.trim_start_matches('/')
|
return String::new();
|
||||||
.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 token = text
|
let html = requester.get(&detail_url, Some(Version::HTTP_11)).await;
|
||||||
.split("data-csrf-token=\"")
|
let Ok(html) = html else {
|
||||||
.nth(1)
|
return String::new();
|
||||||
.and_then(|s| s.split('"').next())
|
};
|
||||||
.unwrap_or("")
|
if html.is_empty() {
|
||||||
.to_string();
|
return String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(token) = Self::extract_token(&html) else {
|
||||||
|
return String::new();
|
||||||
|
};
|
||||||
|
|
||||||
let form = wreq::multipart::Form::new()
|
let form = wreq::multipart::Form::new()
|
||||||
.text("video_id", video_id.clone())
|
.text("video_id", video_id)
|
||||||
.text("pid_c", "".to_string())
|
.text("pid_c", "".to_string())
|
||||||
.text("token", token.clone());
|
.text("token", token);
|
||||||
let resp = match requester
|
let resp = match requester
|
||||||
.post_multipart(
|
.post_multipart(
|
||||||
"https://javtiful.com/ajax/get_cdn",
|
"https://javtiful.com/ajax/get_cdn",
|
||||||
form,
|
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),
|
Some(Version::HTTP_11),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(_) => return "".to_string(),
|
Err(_) => return String::new(),
|
||||||
};
|
};
|
||||||
let text = resp.text().await.unwrap_or_default();
|
let payload = resp.text().await.unwrap_or_default();
|
||||||
let json: serde_json::Value =
|
Self::extract_playlist_url(&payload).unwrap_or_default()
|
||||||
serde_json::from_str(&text).unwrap_or(serde_json::Value::Null);
|
}
|
||||||
let video_url = json
|
}
|
||||||
.get("playlists")
|
|
||||||
.map(|v| v.to_string().replace("\"", ""))
|
#[cfg(test)]
|
||||||
.unwrap_or_default();
|
mod tests {
|
||||||
|
use super::JavtifulProxy;
|
||||||
return video_url;
|
|
||||||
|
#[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")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user