From 84f6a435d36f5c8237d12b5a7745d1bae767be1c Mon Sep 17 00:00:00 2001 From: DARKDEV <121659506+DARKDEV22@users.noreply.github.com> Date: Fri, 5 Jan 2024 00:56:10 +0700 Subject: [PATCH] initial commit --- Cargo.toml | 10 +++ main.rs | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 Cargo.toml create mode 100644 main.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e139433 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "new-project" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +reqwest = { version = "0.11", features = ["blocking", "json"] } +tokio = { version = "1", features = ["full"] } \ No newline at end of file diff --git a/main.rs b/main.rs new file mode 100644 index 0000000..62cdddc --- /dev/null +++ b/main.rs @@ -0,0 +1,227 @@ +// cargo run --release +use std::io; +use std::fs; +use std::process; +use reqwest::header; +use std::io::Cursor; +extern crate reqwest; +use std::thread; +use std::time::Duration; + +fn get_ts(m3u8_url: &str) -> Result, Box> { + let mut headers = header::HeaderMap::new(); + headers.insert("authority", "live-global-cdn-v02.afreecatv.com".parse().unwrap()); + headers.insert("accept", "*/*".parse().unwrap()); + headers.insert("accept-language", "en-US,en;q=0.8".parse().unwrap()); + headers.insert("origin", "https://play.afreecatv.com".parse().unwrap()); + headers.insert("referer", "https://play.afreecatv.com/".parse().unwrap()); + headers.insert("sec-ch-ua", "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Brave\";v=\"120\"".parse().unwrap()); + headers.insert("sec-ch-ua-mobile", "?1".parse().unwrap()); + headers.insert("sec-ch-ua-platform", "\"Android\"".parse().unwrap()); + headers.insert("sec-fetch-dest", "empty".parse().unwrap()); + headers.insert("sec-fetch-mode", "cors".parse().unwrap()); + headers.insert("sec-fetch-site", "same-site".parse().unwrap()); + headers.insert("sec-gpc", "1".parse().unwrap()); + + let client = reqwest::blocking::Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build() + .unwrap(); + let res = client.get(m3u8_url) + .headers(headers) + .send()? + .text()?; + + let ts : Vec = res + .split("1920x1080") + .skip(1) + .flat_map(|z| z.split("#EXTINF").next()) + .map(|url| url.replace("\r\n", "")) + .collect(); + Ok(ts) +} + + +// fn get_ts(base_url: &str, key: &str) -> Result, Box> { +// let mut headers = header::HeaderMap::new(); +// headers.insert("authority", "live-global-cdn-v02.afreecatv.com".parse().unwrap()); +// headers.insert("accept", "*/*".parse().unwrap()); +// headers.insert("accept-language", "en-US,en;q=0.8".parse().unwrap()); +// headers.insert("origin", "https://play.afreecatv.com".parse().unwrap()); +// headers.insert("referer", "https://play.afreecatv.com/".parse().unwrap()); +// headers.insert("sec-ch-ua", "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Brave\";v=\"120\"".parse().unwrap()); +// headers.insert("sec-ch-ua-mobile", "?1".parse().unwrap()); +// headers.insert("sec-ch-ua-platform", "\"Android\"".parse().unwrap()); +// headers.insert("sec-fetch-dest", "empty".parse().unwrap()); +// headers.insert("sec-fetch-mode", "cors".parse().unwrap()); +// headers.insert("sec-fetch-site", "same-site".parse().unwrap()); +// headers.insert("sec-gpc", "1".parse().unwrap()); +// headers.insert("user-agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36".parse().unwrap()); + +// let client = reqwest::blocking::Client::builder() +// .redirect(reqwest::redirect::Policy::none()) +// .build() +// .unwrap(); +// let res = client.get(&format!("{}?aid={}", base_url, key)) +// .headers(headers) +// .send()? +// .text()?; + +// let ts : Vec = res +// .split("1920x1080") +// .skip(1) +// .flat_map(|z| z.split("#EXTINF").next()) +// .map(|url| url.replace("\r\n", "")) +// .collect(); +// Ok(ts) +// } + +// fn page_source(url: &str) -> Result { +// // Replace the URL with the actual URL of the page you want to retrieve +// // Send an HTTP GET request +// let response = reqwest::blocking::get(&format!("https://play.afreecatv.com/{}", url))?; +// let html = response.text().unwrap(); +// let s: Vec<_> = html.split("liveimg.afreecatv.com/m/").collect(); +// let res: Vec<_> = s[1].split("?").collect(); +// let number = res[0]; + +// Ok(number.to_string()) +// } + +// fn page_response(bj_id: &str, number_id: &str) -> Result> { +// let mut headers = header::HeaderMap::new(); +// headers.insert("authority", "api.m.afreecatv.com".parse().unwrap()); +// headers.insert("accept", "*/*".parse().unwrap()); +// headers.insert("accept-language", "en-US,en;q=0.8".parse().unwrap()); +// headers.insert("content-type", "application/x-www-form-urlencoded".parse().unwrap()); +// headers.insert("origin", "https://m.afreecatv.com".parse().unwrap()); +// headers.insert("referer", "https://m.afreecatv.com/".parse().unwrap()); +// headers.insert("sec-ch-ua", "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Brave\";v=\"120\"".parse().unwrap()); +// headers.insert("sec-ch-ua-mobile", "?1".parse().unwrap()); +// headers.insert("sec-ch-ua-platform", "\"Android\"".parse().unwrap()); +// headers.insert("sec-fetch-dest", "empty".parse().unwrap()); +// headers.insert("sec-fetch-mode", "cors".parse().unwrap()); +// headers.insert("sec-fetch-site", "same-site".parse().unwrap()); +// headers.insert("sec-gpc", "1".parse().unwrap()); +// // headers.insert("user-agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36".parse().unwrap()); + +// let client = reqwest::blocking::Client::builder() +// .redirect(reqwest::redirect::Policy::none()) +// .build() +// .unwrap(); +// let res = client.post(&format!("https://api.m.afreecatv.com/broad/a/watch?bjid={}", bj_id)) +// .headers(headers) +// .body(format!("bj_id={}&broad_no={}&agent=web&confirm_adult=false&player_type=webm&mode=live", bj_id, number_id)) +// .send()? +// .text()?; +// // let key: Vec<_> = res.split("hls_authentication_key").collect(); +// // let s: Vec<_> = key[1].split(",").collect(); +// // let keya: Vec<_> = s[1].split("\"").collect(); +// // Ok(keya[3].to_string()) +// let key = res +// .split("hls_authentication_key") +// .nth(1) +// .and_then(|s| s.split(",").nth(1)) +// .and_then(|key| key.split("\"").nth(3)) +// .map(|key| key.to_string()); + +// Ok(key.unwrap_or_else(|| String::new())) + +// } + +// fn get_base_url(number_id: &str) -> Result> { +// let mut headers = header::HeaderMap::new(); +// headers.insert("authority", "livestream-manager.afreecatv.com".parse().unwrap()); +// headers.insert("accept", "*/*".parse().unwrap()); +// headers.insert("accept-language", "en-US,en;q=0.8".parse().unwrap()); +// headers.insert("content-type", "application/x-www-form-urlencoded".parse().unwrap()); +// headers.insert("origin", "https://m.afreecatv.com".parse().unwrap()); +// headers.insert("referer", "https://m.afreecatv.com/".parse().unwrap()); +// headers.insert("sec-ch-ua", "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Brave\";v=\"120\"".parse().unwrap()); +// headers.insert("sec-ch-ua-mobile", "?1".parse().unwrap()); +// headers.insert("sec-ch-ua-platform", "\"Android\"".parse().unwrap()); +// headers.insert("sec-fetch-dest", "empty".parse().unwrap()); +// headers.insert("sec-fetch-mode", "cors".parse().unwrap()); +// headers.insert("sec-fetch-site", "same-site".parse().unwrap()); +// headers.insert("sec-gpc", "1".parse().unwrap()); +// // headers.insert("user-agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36".parse().unwrap()); + +// let client = reqwest::blocking::Client::builder() +// .redirect(reqwest::redirect::Policy::none()) +// .build() +// .unwrap(); +// let res = client.get(&format!("https://livestream-manager.afreecatv.com/broad_stream_assign.html?return_type=gcp_cdn&use_cors=true&cors_origin_url=m.afreecatv.com&broad_key={}-common-hd-hls", number_id)) +// .headers(headers) +// .send()? +// .text()?; +// let response: ApiResponse = serde_json::from_str(&res)?; +// Ok(response.view_url) +// } + + +async fn download_file(download_url: &str, save_path: &str) -> Result>{ + let response = reqwest::get(download_url).await?; + let file_size = response.content_length().unwrap_or(0); + if file_size > 100000 { + // if it found video not "not found" page + let mut file = std::fs::File::create(save_path)?; + let mut content = Cursor::new(response.bytes().await?); + std::io::copy(&mut content, &mut file)?; + return Ok(file_size); + } + Ok(0) +} + +fn create_dir(dir_name: &str) -> Result<(), Box> { + fs::create_dir(dir_name)?; + Ok(()) +} + +#[tokio::main] +async fn main() { + // let bj_id = "hl6260"; + // let m3u8_url = "https://live-global-cdn-v02.afreecatv.com/live-stm-16/auth_playlist.m3u8?aid=.A32.7bbT56vyHM9fKZk.5J9f_GP2vBGChB5WHsB2EcNkA9M0nZFm2u1eWJ11ZnE3LcLr7iTGUBf-mzVfE85-QqlYdO_nxSzaEoeVd5fN1sXmoBrHKHXkAriuMhQX4mGGYy6FOPOz_jhbxNh8keMa".to_string(); + // let number_id = page_source(bj_id).unwrap(); + // println!("{:?}", number_id); + // let key: String = page_response(bj_id, number_id.as_str()).unwrap(); + // println!("{:?}", key); + // let base_url = get_base_url(number_id.as_str()).unwrap(); + // let base_url = "https://live-global-cdn-v02.afreecatv.com/live-stmc-37/auth_playlist.m3u8"; + // let key: String = ".A32.7bbT56vyHM9fKZk.NmMjFKcd_SLAfN1ablFtbgdMOKZi8vipQYI16ZIHN2r05cUWkBDaMDhuMdGrnHwlnH0NuP4i9PaJdwBHpmGLQlnLjYJkOlQ4htew3t96WurhwdIoRbKeCFlvo85X2mMU".to_string(); + + // get m3u8 url, now just loop til stream end. + + println!("\nAFSR: AfreecaTV shadow recorder. [Github: Darkdev22]"); + let mut bj_id = String::new(); + println!("\nEnter a Bj_id:"); + io::stdin().read_line(&mut bj_id).expect("Failed to read line"); + let bj_id = bj_id.trim(); + + let mut m3u8_url = String::new(); + println!("\nEnter m3u8_url:"); + io::stdin().read_line(&mut m3u8_url).expect("Failed to read line"); + let m3u8_url = m3u8_url.replace("\r\n", ""); + + match get_ts(m3u8_url.as_str()) { + Ok(_) => (), + Err(_) => { + println!("[Failed] Bj_id or m3u8_url is wrong!"); + thread::sleep(Duration::from_secs(5)); + process::exit(1); + }, + } + + let _ = create_dir(bj_id); + println!("\n\nRecording ... {}", bj_id); + loop { + let ts_urls = get_ts(m3u8_url.as_str()).unwrap(); + let base_url_ts: Vec<_> = m3u8_url.split("auth_playlist.m3u8").collect(); + let base_ts = base_url_ts[0].trim_end_matches('/'); + for url in ts_urls { + let d = format!("{base_ts}{url}"); + let file_name: &str = url.split('_').nth(1).unwrap_or_default(); + println!("[{}] {}", file_name, bj_id); + _ = download_file(&d, &format!("./{}/{}.ts",bj_id, file_name)).await; + } + } +}