diff --git a/.env b/.env new file mode 100644 index 0000000..c8710cf --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_URL=hottub.db \ No newline at end of file diff --git a/.gitignore b/.gitignore index 62aaaad..5bf9171 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ Cargo.lock # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +hottub.db +migrations/.keep diff --git a/Cargo.toml b/Cargo.toml index 7963a92..272a3f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" [dependencies] async-trait = "0.1.88" awc = "3.7.0" +diesel = { version = "2.2.10", features = ["sqlite", "r2d2"] } +dotenvy = "0.15.7" env_logger = "0.11.8" error-chain = "0.12.4" futures = "0.3.31" diff --git a/Dockerfile b/Dockerfile index 5cec66a..2da0b42 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,7 @@ RUN apt install -yq libssl-dev \ fluxbox \ xvfb \ gnome-screenshot \ + libsqlite3-dev sqlite3 \ && apt-get clean diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..a0d61bf --- /dev/null +++ b/diesel.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" +custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] + +[migrations_directory] +dir = "migrations" diff --git a/migrations/create_videos/down.sql b/migrations/create_videos/down.sql new file mode 100644 index 0000000..0c14f8e --- /dev/null +++ b/migrations/create_videos/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE videos \ No newline at end of file diff --git a/migrations/create_videos/up.sql b/migrations/create_videos/up.sql new file mode 100644 index 0000000..e8d1826 --- /dev/null +++ b/migrations/create_videos/up.sql @@ -0,0 +1,8 @@ +-- Your SQL goes here +CREATE TABLE videos ( + id TEXT NOT NULL PRIMARY KEY, -- like url parts to uniquely identify a video + url TEXT NOT NULL, + views INTEGER, + rating INTEGER, + uploader TEXT +) \ No newline at end of file diff --git a/src/api.rs b/src/api.rs index 32f4752..a183469 100644 --- a/src/api.rs +++ b/src/api.rs @@ -6,7 +6,7 @@ use ntex::web::HttpRequest; use crate::providers::hanime::HanimeProvider; use crate::providers::perverzija::PerverzijaProvider; use crate::util::cache::VideoCache; -use crate::{providers::*, status::*, videos::*}; +use crate::{providers::*, status::*, videos::*, DbPool}; pub fn config(cfg: &mut web::ServiceConfig) { cfg.service( @@ -195,7 +195,8 @@ async fn status(req: HttpRequest) -> Result { async fn videos_post( video_request: web::types::Json, - cache: web::types::State + cache: web::types::State, + pool: web::types::State, ) -> Result { let mut videos = Videos { pageInfo: PageInfo { @@ -232,7 +233,7 @@ async fn videos_post( let provider = get_provider(channel.as_str()) .ok_or_else(|| web::error::ErrorBadRequest("Invalid channel".to_string()))?; let video_items = provider - .get_videos(cache.get_ref().clone(), channel, sort, query, page.to_string(), perPage.to_string(), featured) + .get_videos(cache.get_ref().clone(), pool.get_ref().clone(), channel, sort, query, page.to_string(), perPage.to_string(), featured) .await; videos.items = video_items.clone(); Ok(web::HttpResponse::Ok().json(&videos)) diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..836717b --- /dev/null +++ b/src/db.rs @@ -0,0 +1,27 @@ +use diesel::prelude::*; +use dotenvy::dotenv; +use std::{env, sync::{Arc, Mutex}}; +use crate::models::DBVideo; + + +pub fn get_video(conn: &mut SqliteConnection, video_id: String) -> Result, diesel::result::Error> { + use crate::schema::videos::dsl::*; + let result = videos + .filter(id.eq(video_id)) + .first::(conn) + .optional()?; + match result{ + Some(video) => Ok(Some(video.url)), + None => Ok(None), + } +} + +pub fn insert_video(conn: &mut SqliteConnection, new_id: &str, new_url: &str) -> Result { + use crate::schema::videos::dsl::*; + diesel::insert_into(videos).values(DBVideo{ + id: new_id.to_string(), + url: new_url.to_string(), + }).execute(conn) +} + + diff --git a/src/main.rs b/src/main.rs index 9485837..5d68c6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,26 +1,53 @@ #![allow(non_snake_case)] +#[macro_use] +extern crate diesel; + +use diesel::{r2d2::{self, ConnectionManager}, SqliteConnection}; +use dotenvy::dotenv; use ntex_files as fs; use ntex::web; -mod api; -mod status; -mod videos; -mod providers; -mod util; +mod api; +mod db; +mod models; +mod providers; +mod schema; +mod status; +mod util; +mod videos; + +type DbPool = r2d2::Pool>; #[ntex::main] async fn main() -> std::io::Result<()> { std::env::set_var("RUST_BACKTRACE", "1"); env_logger::init(); // You need this to actually see logs + dotenv().ok(); + + // set up database connection pool + let connspec = std::env::var("DATABASE_URL").expect("DATABASE_URL"); + let manager = ConnectionManager::::new(connspec); + let pool = r2d2::Pool::builder() + .build(manager) + .expect("Failed to create pool."); let cache: util::cache::VideoCache = crate::util::cache::VideoCache::new(); web::HttpServer::new(move || { web::App::new() + .state(pool.clone()) .state(cache.clone()) .wrap(web::middleware::Logger::default()) .service(web::scope("/api").configure(api::config)) - .service(fs::Files::new("/", "static")) + .service( + web::resource("/") + .route(web::get().to(|| async { + web::HttpResponse::Found() + .header("Location", "hottub://source?url=hottub.spacemoehre.de") + .finish() + })) + ) + .service(fs::Files::new("/", "static").index_file("index.html")) }) // .bind_openssl(("0.0.0.0", 18080), builder)? .bind(("0.0.0.0", 18080))? diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..fda1bae --- /dev/null +++ b/src/models.rs @@ -0,0 +1,10 @@ +use diesel::prelude::*; +use serde::{Serialize}; + +#[derive(Debug, Clone, Serialize, Queryable, Insertable)] +#[diesel(table_name = crate::schema::videos)] +#[diesel(check_for_backend(diesel::sqlite::Sqlite))] +pub struct DBVideo { + pub id: String, + pub url: String, +} diff --git a/src/providers/hanime.rs b/src/providers/hanime.rs index 8fced56..d758342 100644 --- a/src/providers/hanime.rs +++ b/src/providers/hanime.rs @@ -6,11 +6,13 @@ use htmlentity::entity::{decode, ICodedDataTrait}; use reqwest::{Proxy}; use futures::future::join_all; +use crate::db; use crate::providers::Provider; use crate::util::cache::VideoCache; use crate::util::flaresolverr::{FlareSolverrRequest, Flaresolverr}; use crate::util::time::parse_time_to_seconds; -use crate::videos::{self, Video_Embed, Video_Item}; // Make sure Provider trait is imported +use crate::videos::{self, Video_Embed, Video_Item}; +use crate::DbPool; // Make sure Provider trait is imported error_chain! { foreign_links { @@ -125,12 +127,30 @@ impl HanimeProvider { } } - async fn get_video_item(&self, hit: HanimeSearchResult) -> Result<(u64,Video_Item)> { + async fn get_video_item(&self, hit: HanimeSearchResult, pool: DbPool) -> Result { + let mut conn = pool.get().expect("couldn't get db connection from pool"); + let db_result = db::get_video(&mut conn,format!("https://h.freeanimehentai.net/api/v8/video?id={}&", hit.slug.clone())); + drop(conn); let id = hit.id.to_string(); let title = hit.name; let thumb = hit.poster_url; let duration = (hit.duration_in_ms / 1000) as u32; // Convert ms to seconds let channel = "hanime".to_string(); // Placeholder, adjust as needed + match db_result { + Ok(Some(video_url)) => { + return Ok(Video_Item::new(id, title, video_url.clone(), channel, thumb, duration) + .tags(hit.tags) + .uploader(hit.brand) + .views(hit.views as u32) + .rating((hit.likes as f32 / (hit.likes + hit.dislikes)as f32) * 100 as f32) + .formats(vec![videos::Video_Format::new(video_url.clone(), "1080".to_string(), "m3u8".to_string())])); + } + Ok(None) => (), + Err(e) => { + println!("Error fetching video from database: {}", e); + // return Err(format!("Error fetching video from database: {}", e).into()); + } + } let client = match env::var("BURP_URL").as_deref() { Ok(burp_url) => @@ -149,11 +169,14 @@ impl HanimeProvider { let text = match response.status().is_success() { true => { - response.text().await?}, + response.text().await? + }, false => { print!("Failed to fetch video item: {}\n\n", response.status()); return Err(format!("Failed to fetch video item: {}", response.status()).into()); - } }; + } + }; + let urls = text.split("\"servers\"").collect::>()[1]; let mut url_vec = vec![]; @@ -163,16 +186,19 @@ impl HanimeProvider { url_vec.push(url.to_string()); } } - Ok((hit.created_at, Video_Item::new(id, title, url_vec[0].clone(), channel, thumb, duration) + let mut conn = pool.get().expect("couldn't get db connection from pool"); + let _ = db::insert_video(&mut conn, &format!("https://h.freeanimehentai.net/api/v8/video?id={}&", hit.slug.clone()), &url_vec[0].clone()); + drop(conn); + Ok(Video_Item::new(id, title, url_vec[0].clone(), channel, thumb, duration) .tags(hit.tags) .uploader(hit.brand) .views(hit.views as u32) .rating((hit.likes as f32 / (hit.likes + hit.dislikes)as f32) * 100 as f32) - .formats(vec![videos::Video_Format::new(url_vec[0].clone(), "1080".to_string(), "m3u8".to_string())]))) + .formats(vec![videos::Video_Format::new(url_vec[0].clone(), "1080".to_string(), "m3u8".to_string())])) } - async fn get(&self, cache: VideoCache, page: u8, query: String) -> Result> { + async fn get(&self, cache: VideoCache, pool: DbPool, page: u8, query: String) -> Result> { let index = format!("{}:{}", query, page); let old_items = match cache.get(&index) { @@ -219,13 +245,12 @@ impl HanimeProvider { let hits_json: Vec = serde_json::from_str(hits.as_str()) .map_err(|e| format!("Failed to parse hits JSON: {}", e))?; // let timeout_duration = Duration::from_secs(120); - let futures = hits_json.into_iter().map(|el| self.get_video_item(el.clone())); - let results: Vec> = join_all(futures).await; - let mut items: Vec<(u64, Video_Item)> = results + let futures = hits_json.into_iter().map(|el| self.get_video_item(el.clone(), pool.clone())); + let results: Vec> = join_all(futures).await; + let video_items: Vec = results .into_iter() .filter_map(Result::ok) - .collect(); - let video_items: Vec = items.into_iter().map(|(_, item)| item).collect(); + .collect(); if !video_items.is_empty() { cache.remove(&index); cache.insert(index.clone(), video_items.clone()); @@ -241,6 +266,7 @@ impl Provider for HanimeProvider { async fn get_videos( &self, cache: VideoCache, + pool: DbPool, _channel: String, sort: String, query: Option, @@ -252,8 +278,8 @@ impl Provider for HanimeProvider { let _ = per_page; let _ = sort; let videos: std::result::Result, Error> = match query { - Some(q) => self.get(cache, page.parse::().unwrap_or(1), q).await, - None => self.get(cache, page.parse::().unwrap_or(1), "".to_string()).await, + Some(q) => self.get(cache, pool, page.parse::().unwrap_or(1), q).await, + None => self.get(cache, pool, page.parse::().unwrap_or(1), "".to_string()).await, }; match videos { Ok(v) => v, diff --git a/src/providers/mod.rs b/src/providers/mod.rs index 7e10a5f..b8c9df7 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -1,10 +1,10 @@ -use crate::{providers::{hanime::HanimeProvider, perverzija::PerverzijaProvider}, util::cache::VideoCache, videos::Video_Item}; +use crate::{providers::{hanime::HanimeProvider, perverzija::PerverzijaProvider}, util::cache::VideoCache, videos::Video_Item, DbPool}; pub mod perverzija; pub mod hanime; pub trait Provider{ - async fn get_videos(&self, cache: VideoCache ,channel: String, sort: String, query: Option, page: String, per_page: String, featured: String) -> Vec; + async fn get_videos(&self, cache: VideoCache, pool: DbPool, channel: String, sort: String, query: Option, page: String, per_page: String, featured: String) -> Vec; } pub enum AnyProvider { @@ -12,10 +12,10 @@ pub enum AnyProvider { Hanime(HanimeProvider), } impl Provider for AnyProvider { - async fn get_videos(&self, cache: VideoCache ,channel: String, sort: String, query: Option, page: String, per_page: String, featured: String) -> Vec { + async fn get_videos(&self, cache: VideoCache, pool:DbPool, channel: String, sort: String, query: Option, page: String, per_page: String, featured: String) -> Vec { match self { - AnyProvider::Perverzija(p) => p.get_videos(cache ,channel, sort, query, page, per_page, featured).await, - AnyProvider::Hanime(p) => p.get_videos(cache ,channel, sort, query, page, per_page, featured).await, + AnyProvider::Perverzija(p) => p.get_videos(cache, pool, channel, sort, query, page, per_page, featured).await, + AnyProvider::Hanime(p) => p.get_videos(cache, pool, channel, sort, query, page, per_page, featured).await, } } } diff --git a/src/providers/perverzija.rs b/src/providers/perverzija.rs index cddad5b..edb9f01 100644 --- a/src/providers/perverzija.rs +++ b/src/providers/perverzija.rs @@ -3,12 +3,16 @@ use std::env; use error_chain::error_chain; use htmlentity::entity::{decode, ICodedDataTrait}; use reqwest::{Proxy}; +use futures::future::join_all; +use ntex::web; +use crate::db; use crate::providers::Provider; use crate::util::cache::VideoCache; use crate::util::flaresolverr::{FlareSolverrRequest, Flaresolverr}; use crate::util::time::parse_time_to_seconds; -use crate::videos::{self, Video_Embed, Video_Item}; // Make sure Provider trait is imported +use crate::videos::{self, Video_Embed, Video_Item}; +use crate::DbPool; // Make sure Provider trait is imported error_chain! { foreign_links { @@ -26,9 +30,7 @@ impl PerverzijaProvider { url: "https://tube.perverzija.com/".to_string(), } } - async fn get(&self, cache:VideoCache ,page: u8, featured: String) -> Result> { - println!("get"); - + async fn get(&self, cache:VideoCache, pool:DbPool, page: u8, featured: String) -> Result> { //TODO // let mut url = Url::parse("https://example.net")?; // url.query_pairs_mut().append_pair("foo", "bar"); @@ -78,7 +80,7 @@ impl PerverzijaProvider { // print!("Response: {:?}\n", response); if response.status().is_success() { let text = response.text().await?; - let video_items: Vec = self.get_video_items_from_html(text.clone()); + let video_items: Vec = self.get_video_items_from_html(text.clone(), pool); if !video_items.is_empty() { cache.remove(&url); cache.insert(url.clone(), video_items.clone()); @@ -96,11 +98,10 @@ impl PerverzijaProvider { maxTimeout: 60000, }) .await; - println!("FlareSolverr result: {:?}", result); let video_items = match result { Ok(res) => { // println!("FlareSolverr response: {}", res); - self.get_video_items_from_html(res.solution.response) + self.get_video_items_from_html(res.solution.response, pool) } Err(e) => { println!("Error solving FlareSolverr: {}", e); @@ -116,22 +117,20 @@ impl PerverzijaProvider { Ok(video_items) } } - async fn query(&self, cache: VideoCache, page: u8, query: &str) -> Result> { - println!("query: {}", query); + async fn query(&self, cache: VideoCache, pool:DbPool, page: u8, query: &str) -> Result> { let search_string = query.replace(" ", "+"); let mut url = format!( - "{}advanced-search/?_sf_s={}&sf_paged={}", - self.url, search_string, page + "{}page/{}/?s={}", + self.url, page, search_string ); if page == 1 { - url = format!("{}advanced-search/?_sf_s={}", self.url, search_string); + url = format!("{}?s={}", self.url, search_string); } // Check our Video Cache. If the result is younger than 1 hour, we return it. let old_items = match cache.get(&url) { Some((time, items)) => { if time.elapsed().unwrap_or_default().as_secs() < 60 * 60 { - println!("Cache hit for URL: {}", url); return Ok(items.clone()); } else{ @@ -156,10 +155,9 @@ impl PerverzijaProvider { }; let response = client.get(url.clone()).send().await?; - // print!("Response: {:?}\n", response); if response.status().is_success() { let text = response.text().await?; - let video_items: Vec = self.get_video_items_from_html_query(text.clone()); + let video_items: Vec = self.get_video_items_from_html_query(text.clone(), pool).await; if !video_items.is_empty() { cache.remove(&url); cache.insert(url.clone(), video_items.clone()); @@ -177,11 +175,9 @@ impl PerverzijaProvider { maxTimeout: 60000, }) .await; - println!("FlareSolverr result: {:?}", result); let video_items = match result { Ok(res) => { - // println!("FlareSolverr response: {}", res); - self.get_video_items_from_html_query(res.solution.response) + self.get_video_items_from_html_query(res.solution.response, pool).await } Err(e) => { println!("Error solving FlareSolverr: {}", e); @@ -198,8 +194,7 @@ impl PerverzijaProvider { } } - fn get_video_items_from_html(&self, html: String) -> Vec { - // println!("HTML: {}", html); + fn get_video_items_from_html(&self, html: String, pool: DbPool) -> Vec { if html.is_empty() { println!("HTML is empty"); return vec![]; @@ -213,7 +208,6 @@ impl PerverzijaProvider { for video_segment in &raw_videos { let vid = video_segment.split("\n").collect::>(); if vid.len() > 20 { - println!("Skipping video segment with unexpected length: {}", vid.len()); continue; } let mut title = vid[1].split(">").collect::>()[1] @@ -258,10 +252,14 @@ impl PerverzijaProvider { .split("'") .collect::>()[0] .to_string(); - // let referer_url = vid[1].split("data-url='").collect::>()[1] - // .split("'") - // .collect::>()[0] - // .to_string(); + let id_url = vid[1].split("data-url='").collect::>()[1] + .split("'") + .collect::>()[0] + .to_string(); + + let mut conn = pool.get().expect("couldn't get db connection from pool"); + let _ = db::insert_video(&mut conn, &id_url, &url); + drop(conn); let referer_url = "https://xtremestream.xyz/".to_string(); let embed = Video_Embed::new(embed_html, url.clone()); @@ -298,97 +296,167 @@ impl PerverzijaProvider { return items; } - fn get_video_items_from_html_query(&self, html: String) -> Vec { - let mut items: Vec = Vec::new(); - let video_listing_content = html.split("search-filter-results-").collect::>()[1]; - let raw_videos = video_listing_content + async fn get_video_items_from_html_query(&self, html: String, pool:DbPool) -> Vec { + let raw_videos = html .split("video-item post") .collect::>()[1..] .to_vec(); - for video_segment in &raw_videos { - let vid = video_segment.split("\n").collect::>(); - if vid.len() > 20 { - continue; - } - let mut title = vid[3].split("title='").collect::>()[1] - .split("'") - .collect::>()[0] - .to_string(); - title = decode(title.as_bytes()).to_string().unwrap_or(title); - let url = vid[4].split("iframe src="").collect::>()[1] - .split(""") - .collect::>()[0] - .to_string().replace("index.php","xs1.php"); - let id = url.split("data=").collect::>()[1] - .split("&") - .collect::>()[0] - .to_string(); - let raw_duration = match vid.len() { - 18 => vid[16].split("time_dur\">").collect::>()[1] - .split("<") - .collect::>()[0] - .to_string(), - _ => "00:00".to_string(), - }; - let duration = parse_time_to_seconds(&raw_duration).unwrap_or(0) as u32; - let thumb_index = match vid.len() { - 18 => 14, - 13 => 8, - _ => { - continue; - } - }; - let thumb = match vid[thumb_index].contains("srcset=\"") { - true => vid[thumb_index].split(" ").collect::>()[0] - .to_string(), - false => vid[thumb_index].split("src=\"").collect::>()[1] - .split("\"") - .collect::>()[0] - .to_string(), - }; - let embed_html = vid[4].split("data-embed='").collect::>()[1] - .split("'") - .collect::>()[0] - .to_string(); - // let referer_url = vid[4].split("data-url='").collect::>()[1] - // .split("'") - // .collect::>()[0] - // .to_string(); - let referer_url = "https://xtremestream.xyz/".to_string(); - let embed = Video_Embed::new(embed_html, url.clone()); - let mut tags: Vec = Vec::new(); // Placeholder for tags, adjust as needed - for tag in vid[0].split(" ").collect::>(){ - if tag.starts_with("tag-") { - let tag_name = tag.split("tag-").collect::>()[1] - .to_string(); - if !tag_name.is_empty() { - tags.push(tag_name.replace("-", " ").to_string()); - } - } - } + let futures = raw_videos.into_iter().map(|el| self.get_video_item(el, pool.clone())); + let results: Vec> = join_all(futures).await; + let items: Vec = results + .into_iter() + .filter_map(Result::ok) + .collect(); + return items; + } - let mut video_item = Video_Item::new( - id, - title, - url.clone(), - "perverzija".to_string(), - thumb, - duration, - ) - .tags(tags); - // .embed(embed.clone()); - let mut format = - videos::Video_Format::new(url.clone(), "1080".to_string(), "m3u8".to_string()); - format.add_http_header("Referer".to_string(), referer_url.clone()); - if let Some(formats) = video_item.formats.as_mut() { - formats.push(format); - } else { - video_item.formats = Some(vec![format]); - } - items.push(video_item); + async fn get_video_item(&self, snippet: &str, pool: DbPool) -> Result { + let vid = snippet.split("\n").collect::>(); + if vid.len() > 30 { + return Err("Unexpected video snippet length".into()); } - return items; + // for (index,line) in vid.iter().enumerate() { + // println!("Line {}: {}", index, line.to_string().trim()); + // } + + let mut title = vid[5].split(" title=\"").collect::>()[1] + .split("\"") + .collect::>()[0] + .to_string(); + title = decode(title.as_bytes()).to_string().unwrap_or(title); + + let thumb = match vid[6].split(" src=\"").collect::>().len(){ + 1=>{ + for (index,line) in vid.iter().enumerate() { + println!("Line {}: {}", index, line.to_string().trim()); + } + return Err("Failed to parse thumbnail URL".into()); + } + _ => vid[6].split(" src=\"").collect::>()[1] + .split("\"") + .collect::>()[0] + .to_string(), + }; + let duration = 0; + + let lookup_url = vid[5].split(" href=\"").collect::>()[1] + .split("\"") + .collect::>()[0] + .to_string(); + let referer_url = "https://xtremestream.xyz/".to_string(); + + let mut conn = pool.get().expect("couldn't get db connection from pool"); + let db_result = db::get_video(&mut conn,lookup_url.clone()); + match db_result { + Ok(Some(url)) => { + let mut id = url.split("data=").collect::>()[1] + .to_string(); + if id.contains("&"){ + id = id.split("&").collect::>()[0].to_string() + } + let mut video_item = Video_Item::new( + id, + title, + url.clone(), + "perverzija".to_string(), + thumb, + duration, + ); + let mut format = + videos::Video_Format::new(url.clone(), "1080".to_string(), "m3u8".to_string()); + format.add_http_header("Referer".to_string(), referer_url.clone()); + if let Some(formats) = video_item.formats.as_mut() { + formats.push(format); + } else { + video_item.formats = Some(vec![format]); + } + return Ok(video_item); + } + Ok(None) => (), + Err(e) => { + println!("Error fetching video from database: {}", e); + // return Err(format!("Error fetching video from database: {}", e).into()); + } + } + drop(conn); + + let client = match env::var("BURP_URL").as_deref() { + Ok(burp_url) => + reqwest::Client::builder() + .user_agent("Mozilla/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/33.0 Mobile/15E148 Safari/605.1.15") + .proxy(Proxy::https(burp_url).unwrap()) + .danger_accept_invalid_certs(true) + .build()?, + Err(_) => reqwest::Client::builder() + .user_agent("Mozilla/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/33.0 Mobile/15E148 Safari/605.1.15") + .danger_accept_invalid_certs(true) + .build()?, + }; + + let response = client.get(lookup_url.clone()).send().await?; + let text = match response.status().is_success(){ + true => response.text().await?, + false => { + return Err("Failed to fetch video details".into()); + } + }; + + + let url = text.split("