1use reqrio::{json, HlsError, Method, ReqExt, ScReq, Timeout, ALPN};
2use reqtls::Cipher;
3use std::fs::File;
4use std::io::Write;
5
6struct M3u8DownEngine {
7 req: ScReq,
8 key_url: String,
9 sequence: u128,
10 index_url: String,
11 ts_urls: Vec<String>,
12 cipher: Cipher,
13}
14
15impl M3u8DownEngine {
16 fn new(index: impl ToString) -> M3u8DownEngine {
17 let mut req = ScReq::new().with_alpn(ALPN::Http20); req.set_headers_json(json::object! {
19 "Host": "xgsammkk2.wkwnts.cn",
20 "sec-ch-ua-platform": "Android",
21 "user-agent": "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36",
22 "sec-ch-ua": r#""Android WebView";v="135", "Not-A.Brand";v="8", "Chromium";v="135""#,
23 "sec-ch-ua-mobile": "?1",
24 "accept": "*/*",
25 "origin": "https://jxirlhni27kv.shv19sglxg.cc",
26 "x-requested-with": "mark.via",
27 "sec-fetch-site": "cross-site",
28 "sec-fetch-mode": "cors",
29 "sec-fetch-dest": "empty",
30 "referer": "https://jxirlhni27kv.shv19sglxg.cc/poster.html?viewkey=ea03b3f85e7c9b3caa4e3f31d8a99f2d&spm=1769954023644.1769953313",
31 "accept-encoding": "gzip, deflate, br, zstd",
32 "accept-language": "en,zh-CN;q=0.9,zh;q=0.8,en-US;q=0.7",
33 "priority": "u=1, i"
34 }).unwrap();
35 M3u8DownEngine {
36 req,
37 index_url: index.to_string(),
38 sequence: 0,
39 key_url: "".to_string(),
40 ts_urls: vec![],
41 cipher: Cipher::aes_128_ecb(),
42 }
43 }
44
45 fn down_ts(&mut self) -> Result<(), HlsError> {
46 let mut file = File::create("4.mp4")?;
47 for (index, ts_url) in self.ts_urls.iter().enumerate() {
48 println!("Downloading: {:3}/{}; url:{}", index, self.ts_urls.len(), ts_url);
49 self.req.set_url(ts_url)?;
50 let body = self.req.get()?.bytes()?;
51 file.write_all(&if self.key_url.is_empty() { body } else { self.cipher.decrypt(body)? })?;
52 }
53 Ok(())
54 }
55
56 fn get_key(&mut self) -> Result<(), HlsError> {
57 println!("key url: {}", self.key_url);
58 self.req.set_url(self.key_url.as_str())?;
59 let key = self.req.send_check(Method::GET)?.text()?;
60 println!("key: {}; sequence: {}", key, self.sequence);
61 self.cipher.set_secret_key(key.into_bytes(), Some(self.sequence.to_be_bytes().to_vec()));
62 Ok(())
63 }
64
65 fn download(&mut self) -> Result<(), HlsError> {
66 self.req.set_url(self.index_url.as_str())?;
67 let body = self.req.send_check(Method::GET)?.text()?;
68 for line in body.split("\n") {
70 if line.starts_with("#EXT-X-MEDIA-SEQUENCE:") {
71 self.sequence = line.trim().replace("#EXT-X-MEDIA-SEQUENCE:", "").parse()?;
72 continue;
73 }
74 if line.starts_with("#EXT-X-KEY") {
75 let pos = line.find("URI=\"");
76 if let Some(pos) = pos {
77 self.key_url = line[pos + 4..].trim().replace("\"", "");
78 }
79 if line.contains("=AES-128,") {
80 self.cipher = Cipher::aes_128_cbc();
81 }
82 continue;
83 }
84 if line.starts_with("http") {
85 self.ts_urls.push(line.trim().to_string());
86 }
87 }
88 let mut timeout = Timeout::new();
89 timeout.set_read(5000);
90 timeout.set_write(5000);
91 timeout.set_connect(5000);
92 timeout.set_handle(30000);
93 timeout.set_handle_times(10);
94 self.req.set_timeout(timeout);
95 if !self.key_url.is_empty() { self.get_key()?; }
96 self.down_ts()?;
97 Ok(())
98 }
99}
100
101
102fn main() {
103 let index = "";
104 let mut engine = M3u8DownEngine::new(index);
105 engine.download().unwrap();
106 println!("{:?} {:?}", engine.key_url, engine.ts_urls);
107}