videos fixing protocol
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ntex::web;
|
use ntex::web;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use serde_json::json;
|
use wreq::cookie::Jar;
|
||||||
|
use wreq::redirect::Policy;
|
||||||
|
use wreq_util::Emulation;
|
||||||
|
|
||||||
use crate::util::{dean_edwards, requester::Requester};
|
use crate::util::{dean_edwards, requester::Requester};
|
||||||
|
|
||||||
@@ -20,8 +24,9 @@ impl LulustreamProxy {
|
|||||||
|
|
||||||
let detail_url = if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
|
let detail_url = if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
|
||||||
endpoint.to_string()
|
endpoint.to_string()
|
||||||
} else if endpoint.starts_with("lulustream.com/") || endpoint.starts_with("www.lulustream.com/") ||
|
} else if endpoint.starts_with("lulustream.com/")
|
||||||
endpoint.starts_with("luluvdo.com/")
|
|| endpoint.starts_with("www.lulustream.com/")
|
||||||
|
|| endpoint.starts_with("luluvid.com/")
|
||||||
{
|
{
|
||||||
format!("https://{endpoint}")
|
format!("https://{endpoint}")
|
||||||
} else {
|
} else {
|
||||||
@@ -33,9 +38,7 @@ impl LulustreamProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let parsed = Url::parse(&detail_url).ok()?;
|
let parsed = Url::parse(&detail_url).ok()?;
|
||||||
let video_id = parsed.path_segments()?
|
let video_id = parsed.path_segments()?.last().map(ToOwned::to_owned)?;
|
||||||
.last()
|
|
||||||
.map(ToOwned::to_owned)?;
|
|
||||||
|
|
||||||
Some((detail_url, video_id))
|
Some((detail_url, video_id))
|
||||||
}
|
}
|
||||||
@@ -50,40 +53,82 @@ impl LulustreamProxy {
|
|||||||
let Some(host) = parsed.host_str() else {
|
let Some(host) = parsed.host_str() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
(host == "lulustream.com" || host == "www.lulustream.com" || host == "luluvdo.com")
|
(host == "lulustream.com" || host == "www.lulustream.com" || host == "luluvid.com")
|
||||||
&& !parsed.path().is_empty() && parsed.path() != "/"
|
&& !parsed.path().is_empty()
|
||||||
|
&& parsed.path() != "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_video_url(
|
// Chrome120 emulation bypasses Cloudflare on luluvid.com (Firefox136 gets blocked).
|
||||||
&self,
|
fn build_chrome_client() -> Option<wreq::Client> {
|
||||||
url: String,
|
let jar = Arc::new(Jar::default());
|
||||||
requester: web::types::State<Requester>,
|
wreq::Client::builder()
|
||||||
) -> String {
|
.cert_verification(false)
|
||||||
let mut requester = requester.get_ref().clone();
|
.emulation(Emulation::Chrome120)
|
||||||
let Some((detail_url, video_id)) = Self::normalize_detail_request(&url) else {
|
.cookie_provider(jar)
|
||||||
return String::new();
|
.redirect(Policy::default())
|
||||||
};
|
.build()
|
||||||
println!("LulustreamProxy: Normalized detail URL: {:?}", format!("https://luluvid.com/e/{video_id}"));
|
.ok()
|
||||||
let mut text = requester.get(format!("https://luluvid.com/e/{video_id}").as_str(), None).await.unwrap_or_default();
|
}
|
||||||
if !text.contains("[{file:\"") {
|
|
||||||
let packedtext = text.split("<script type='text/javascript'>").nth(1).and_then(|t| t.split("</script>").next()).unwrap_or_default();
|
fn extract_media_url(html: &str) -> Option<String> {
|
||||||
text = dean_edwards::unpack(&packedtext).unwrap_or_default();
|
// Fast path: file URL present in plain text (no packing)
|
||||||
|
if html.contains("[{file:\"") {
|
||||||
|
let url = html
|
||||||
|
.split("[{file:\"")
|
||||||
|
.nth(1)
|
||||||
|
.and_then(|s| s.split('"').next())?
|
||||||
|
.to_string();
|
||||||
|
if !url.is_empty() {
|
||||||
|
return Some(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let video_url = text.split("[{file:\"")
|
|
||||||
|
// Unpack the Dean Edwards p,a,c,k,e,d script that embeds the player config.
|
||||||
|
// The packed payload encodes the jwplayer setup call; after decoding it contains
|
||||||
|
// `sources:[{file:"https://cdn*.cdn-tnmr.org/hls2/.../master.m3u8?..."}]`.
|
||||||
|
let packed = html
|
||||||
|
.split("<script type='text/javascript'>")
|
||||||
|
.nth(1)
|
||||||
|
.and_then(|t| t.split("</script>").next())?;
|
||||||
|
|
||||||
|
let unpacked = dean_edwards::unpack(packed).ok()?;
|
||||||
|
|
||||||
|
unpacked
|
||||||
|
.split("[{file:\"")
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.and_then(|s| s.split('"').next())
|
.and_then(|s| s.split('"').next())
|
||||||
.unwrap_or_default()
|
.map(|s| s.to_string())
|
||||||
.to_string();
|
.filter(|s| !s.is_empty())
|
||||||
println!("LulustreamProxy: Extracted video URL: {}", video_url);
|
}
|
||||||
let test_request = requester.get_raw_with_headers(video_url.as_str(), vec![
|
|
||||||
("Accept-Language".to_string(), "en-US,en;q=0.9".to_string()),
|
|
||||||
("Referer".to_string(), detail_url.clone()),
|
|
||||||
("User-Agent".to_string(), "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36".to_string())
|
|
||||||
]).await.unwrap();
|
|
||||||
println!("LulustreamProxy: Test request status: {}", test_request.status());
|
|
||||||
|
|
||||||
video_url
|
async fn try_chrome_extraction(embed_url: &str) -> Option<String> {
|
||||||
// return "https://cdn1004.cdn-tnmr.org/hls2/01/03256/cssckmym0ibf_h/master.m3u8?t=Y2jXSIPERwSec0L6RSAOIPFAW53dQ0UgslngqGnF0go&s=1778507711&e=28800&f=16283923&i=0.3&sp=0".to_string();
|
let client = Self::build_chrome_client()?;
|
||||||
|
let response = client.get(embed_url).send().await.ok()?;
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let html = response.text().await.ok()?;
|
||||||
|
Self::extract_media_url(&html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::proxies::Proxy for LulustreamProxy {
|
||||||
|
async fn get_video_url(&self, url: String, requester: web::types::State<Requester>) -> String {
|
||||||
|
let Some((_detail_url, video_id)) = Self::normalize_detail_request(&url) else {
|
||||||
|
return String::new();
|
||||||
|
};
|
||||||
|
|
||||||
|
let embed_url = format!("https://luluvid.com/e/{video_id}");
|
||||||
|
|
||||||
|
// Chrome120 emulation bypasses Cloudflare; try it first.
|
||||||
|
if let Some(media_url) = Self::try_chrome_extraction(&embed_url).await {
|
||||||
|
return media_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: standard requester (Firefox136 + optional FlareSolverr).
|
||||||
|
let mut requester = requester.get_ref().clone();
|
||||||
|
let html = requester.get(&embed_url, None).await.unwrap_or_default();
|
||||||
|
Self::extract_media_url(&html).unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,4 +152,33 @@ mod tests {
|
|||||||
assert_eq!(url, "https://lulustream.com/d/s484n23k8opy");
|
assert_eq!(url, "https://lulustream.com/d/s484n23k8opy");
|
||||||
assert_eq!(video_id, "s484n23k8opy");
|
assert_eq!(video_id, "s484n23k8opy");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalizes_luluvid_url() {
|
||||||
|
let (url, video_id) =
|
||||||
|
LulustreamProxy::normalize_detail_request("https://luluvid.com/e/s484n23k8opy")
|
||||||
|
.expect("detail request should parse");
|
||||||
|
assert_eq!(url, "https://luluvid.com/e/s484n23k8opy");
|
||||||
|
assert_eq!(video_id, "s484n23k8opy");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extracts_media_url_from_plain_html() {
|
||||||
|
let html = r#"[{file:"https://cdn1007.cdn-tnmr.org/hls2/02/02723/abc_h/master.m3u8?t=TOKEN&s=12345&e=28800&f=999&i=0.3&sp=0"}]"#;
|
||||||
|
assert_eq!(
|
||||||
|
LulustreamProxy::extract_media_url(html).as_deref(),
|
||||||
|
Some("https://cdn1007.cdn-tnmr.org/hls2/02/02723/abc_h/master.m3u8?t=TOKEN&s=12345&e=28800&f=999&i=0.3&sp=0")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extracts_media_url_from_packed_script() {
|
||||||
|
// Minimal valid packed script that decodes to a jwplayer sources array.
|
||||||
|
// Original: jwplayer("vplayer").setup({sources:[{file:"https://cdn.example.com/video.m3u8"}]})
|
||||||
|
// We fake it with a trivial packer (base 10, a few words).
|
||||||
|
let fake_packed = r#"eval(function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\b'+c.toString(a)+'\b','g'),k[c]);return p}('0("[{1:\"https://cdn.example.com/video.m3u8\"}]")',10,2,'sources|file'.split('|'),0,{}))"#;
|
||||||
|
let html = format!("<script type='text/javascript'>{fake_packed}</script>");
|
||||||
|
let url = LulustreamProxy::extract_media_url(&html);
|
||||||
|
assert!(url.is_some(), "should extract a URL from packed script");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ impl VideoFormat {
|
|||||||
abr: None,
|
abr: None,
|
||||||
vbr: None,
|
vbr: None,
|
||||||
container: None,
|
container: None,
|
||||||
protocol: Some("m3u8_native".to_string()),
|
protocol: Some("https".to_string()),
|
||||||
audio_ext: Some("none".to_string()),
|
audio_ext: Some("none".to_string()),
|
||||||
video_ext: Some("mp4".to_string()),
|
video_ext: Some("mp4".to_string()),
|
||||||
resolution: None,
|
resolution: None,
|
||||||
|
|||||||
Reference in New Issue
Block a user