diff --git a/src/client/channel.rs b/src/client/channel.rs index 99fc25a..014b369 100644 --- a/src/client/channel.rs +++ b/src/client/channel.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use serde::Serialize; use url::Url; @@ -15,7 +17,7 @@ use super::{response, ClientType, MapResponse, RustyPipeQuery, YTContext}; #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct QChannel<'a> { - context: YTContext, + context: YTContext<'a>, browse_id: &'a str, params: Params, } @@ -257,11 +259,11 @@ fn map_vanity_url(url: &str, id: &str) -> Option { return None; } - let mut parsed_url = ok_or_bail!(Url::parse(url), None); - - // The vanity URL from YouTube is http for some reason - let _ = parsed_url.set_scheme("https"); - Some(parsed_url.to_string()) + Url::parse(url).ok().map(|mut parsed_url| { + // The vanity URL from YouTube is http for some reason + let _ = parsed_url.set_scheme("https"); + parsed_url.to_string() + }) } fn map_channel( @@ -272,13 +274,17 @@ fn map_channel( id: &str, lang: Language, ) -> Result, ExtractionError> { - let header = - header.ok_or_else(|| ExtractionError::ContentUnavailable("channel not found".into()))?; + let header = header.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( + "channel not found", + )))?; let metadata = metadata - .ok_or_else(|| ExtractionError::ContentUnavailable("channel not found".into()))? + .ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( + "channel not found", + )))? .channel_metadata_renderer; - let microformat = microformat - .ok_or_else(|| ExtractionError::ContentUnavailable("channel not found".into()))?; + let microformat = microformat.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( + "channel not found", + )))?; if metadata.external_id != id { return Err(ExtractionError::WrongResult(format!( @@ -384,7 +390,9 @@ fn map_channel_content( } }) .next() - .ok_or_else(|| ExtractionError::InvalidData("could not extract content".into()))?; + .ok_or(ExtractionError::InvalidData(Cow::Borrowed( + "could not extract content", + )))?; if let Some(target_id) = target_id { // YouTube falls back to the featured page if the channel does not have a "videos" tab. diff --git a/src/client/mod.rs b/src/client/mod.rs index 0ec221c..b8a9276 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -15,8 +15,8 @@ mod video_details; #[cfg_attr(docsrs, doc(cfg(feature = "rss")))] mod channel_rss; -use std::fmt::Debug; use std::sync::Arc; +use std::{borrow::Cow, fmt::Debug}; use chrono::{DateTime, Duration, Utc}; use fancy_regex::Regex; @@ -69,29 +69,29 @@ impl ClientType { #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "camelCase")] -struct YTContext { - client: ClientInfo, +struct YTContext<'a> { + client: ClientInfo<'a>, /// only used on desktop #[serde(skip_serializing_if = "Option::is_none")] request: Option, user: User, /// only used for the embedded player #[serde(skip_serializing_if = "Option::is_none")] - third_party: Option, + third_party: Option>, } #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "camelCase")] -struct ClientInfo { - client_name: String, - client_version: String, +struct ClientInfo<'a> { + client_name: &'a str, + client_version: Cow<'a, str>, #[serde(skip_serializing_if = "Option::is_none")] - client_screen: Option, + client_screen: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] - device_model: Option, - platform: String, + device_model: Option<&'a str>, + platform: &'a str, #[serde(skip_serializing_if = "Option::is_none")] - original_url: Option, + original_url: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] visitor_data: Option, hl: Language, @@ -124,21 +124,21 @@ struct User { #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "camelCase")] -struct ThirdParty { - embed_url: String, +struct ThirdParty<'a> { + embed_url: &'a str, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -struct QBrowse { - context: YTContext, +struct QBrowse<'a> { + context: YTContext<'a>, browse_id: String, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct QContinuation<'a> { - context: YTContext, + context: YTContext<'a>, continuation: &'a str, } @@ -531,11 +531,11 @@ impl RustyPipe { ) .await?; - util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or_else(|| { - Error::from(ExtractionError::InvalidData( - "Could not find desktop client version in sw.js".into(), - )) - }) + util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or( + Error::Extraction(ExtractionError::InvalidData(Cow::Borrowed( + "Could not find desktop client version in sw.js", + ))), + ) }; let from_html = async { @@ -549,11 +549,11 @@ impl RustyPipe { ) .await?; - util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or_else(|| { - Error::from(ExtractionError::InvalidData( - "Could not find desktop client version in sw.js".into(), - )) - }) + util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or( + Error::Extraction(ExtractionError::InvalidData(Cow::Borrowed( + "Could not find desktop client version on html page", + ))), + ) }; match from_swjs.await { @@ -578,11 +578,11 @@ impl RustyPipe { ) .await?; - util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or_else(|| { - Error::from(ExtractionError::InvalidData( - "Could not find music client version in sw.js".into(), - )) - }) + util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or( + Error::Extraction(ExtractionError::InvalidData(Cow::Borrowed( + "Could not find music client version in sw.js", + ))), + ) }; let from_html = async { @@ -596,11 +596,11 @@ impl RustyPipe { ) .await?; - util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or_else(|| { - Error::from(ExtractionError::InvalidData( - "Could not find music client version on html page".into(), - )) - }) + util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or( + Error::Extraction(ExtractionError::InvalidData(Cow::Borrowed( + "Could not find music client version on html page", + ))), + ) }; match from_swjs.await { @@ -757,12 +757,12 @@ impl RustyPipeQuery { match ctype { ClientType::Desktop => YTContext { client: ClientInfo { - client_name: "WEB".to_owned(), - client_version: self.client.get_desktop_client_version().await, + client_name: "WEB", + client_version: Cow::Owned(self.client.get_desktop_client_version().await), client_screen: None, device_model: None, - platform: "DESKTOP".to_owned(), - original_url: Some("https://www.youtube.com/".to_owned()), + platform: "DESKTOP", + original_url: Some("https://www.youtube.com/"), visitor_data: None, hl, gl, @@ -773,12 +773,12 @@ impl RustyPipeQuery { }, ClientType::DesktopMusic => YTContext { client: ClientInfo { - client_name: "WEB_REMIX".to_owned(), - client_version: self.client.get_music_client_version().await, + client_name: "WEB_REMIX", + client_version: Cow::Owned(self.client.get_music_client_version().await), client_screen: None, device_model: None, - platform: "DESKTOP".to_owned(), - original_url: Some("https://music.youtube.com/".to_owned()), + platform: "DESKTOP", + original_url: Some("https://music.youtube.com/"), visitor_data: None, hl, gl, @@ -789,11 +789,11 @@ impl RustyPipeQuery { }, ClientType::TvHtml5Embed => YTContext { client: ClientInfo { - client_name: "TVHTML5_SIMPLY_EMBEDDED_PLAYER".to_owned(), - client_version: TVHTML5_CLIENT_VERSION.to_owned(), - client_screen: Some("EMBED".to_owned()), + client_name: "TVHTML5_SIMPLY_EMBEDDED_PLAYER", + client_version: Cow::Borrowed(TVHTML5_CLIENT_VERSION), + client_screen: Some("EMBED"), device_model: None, - platform: "TV".to_owned(), + platform: "TV", original_url: None, visitor_data: None, hl, @@ -802,16 +802,16 @@ impl RustyPipeQuery { request: Some(RequestYT::default()), user: User::default(), third_party: Some(ThirdParty { - embed_url: "https://www.youtube.com/".to_owned(), + embed_url: "https://www.youtube.com/", }), }, ClientType::Android => YTContext { client: ClientInfo { - client_name: "ANDROID".to_owned(), - client_version: MOBILE_CLIENT_VERSION.to_owned(), + client_name: "ANDROID", + client_version: Cow::Borrowed(MOBILE_CLIENT_VERSION), client_screen: None, device_model: None, - platform: "MOBILE".to_owned(), + platform: "MOBILE", original_url: None, visitor_data: None, hl, @@ -823,11 +823,11 @@ impl RustyPipeQuery { }, ClientType::Ios => YTContext { client: ClientInfo { - client_name: "IOS".to_owned(), - client_version: MOBILE_CLIENT_VERSION.to_owned(), + client_name: "IOS", + client_version: Cow::Borrowed(MOBILE_CLIENT_VERSION), client_screen: None, - device_model: Some(IOS_DEVICE_MODEL.to_owned()), - platform: "MOBILE".to_owned(), + device_model: Some(IOS_DEVICE_MODEL), + platform: "MOBILE", original_url: None, visitor_data: None, hl, diff --git a/src/client/pagination.rs b/src/client/pagination.rs index bcfa726..94b5396 100644 --- a/src/client/pagination.rs +++ b/src/client/pagination.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use crate::error::{Error, ExtractionError}; use crate::model::{Comment, Paginator, PlaylistVideo, YouTubeItem}; use crate::param::ContinuationEndpoint; @@ -52,14 +54,13 @@ impl> MapResponse> for response::Continuati _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>, ExtractionError> { let mut actions = self.on_response_received_actions; - let items = some_or_bail!( - actions.try_swap_remove(0), - Err(ExtractionError::InvalidData( - "no item section renderer".into() - )) - ) - .append_continuation_items_action - .continuation_items; + let items = actions + .try_swap_remove(0) + .ok_or(ExtractionError::InvalidData(Cow::Borrowed( + "no item section renderer", + )))? + .append_continuation_items_action + .continuation_items; let mut mapper = response::YouTubeListMapper::::new(lang); mapper.map_response(items); diff --git a/src/client/player.rs b/src/client/player.rs index f70e936..bdfdd56 100644 --- a/src/client/player.rs +++ b/src/client/player.rs @@ -27,7 +27,7 @@ use super::{ #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct QPlayer<'a> { - context: YTContext, + context: YTContext<'a>, /// Website playback context #[serde(skip_serializing_if = "Option::is_none")] playback_context: Option, @@ -79,14 +79,11 @@ impl RustyPipeQuery { video_id: &str, client_type: ClientType, ) -> Result { - let q1 = self.clone(); - let t_context = tokio::spawn(async move { q1.get_context(client_type, false).await }); - let q2 = self.client.clone(); - let t_deobf = tokio::spawn(async move { q2.get_deobf().await }); - - let (context, deobf) = tokio::join!(t_context, t_deobf); - let context = context.unwrap(); - let deobf = deobf.unwrap()?; + let (context, deobf) = tokio::join!( + self.get_context(client_type, false), + self.client.get_deobf() + ); + let deobf = deobf?; let request_body = if client_type.is_web() { QPlayer { @@ -168,14 +165,16 @@ impl MapResponse for response::Player { } }; - let mut streaming_data = some_or_bail!( - self.streaming_data, - Err(ExtractionError::InvalidData("no streaming data".into())) - ); - let video_details = some_or_bail!( - self.video_details, - Err(ExtractionError::InvalidData("no video details".into())) - ); + let mut streaming_data = + self.streaming_data + .ok_or(ExtractionError::InvalidData(Cow::Borrowed( + "no streaming data", + )))?; + let video_details = + self.video_details + .ok_or(ExtractionError::InvalidData(Cow::Borrowed( + "no video details", + )))?; if video_details.video_id != id { return Err(ExtractionError::WrongResult(format!( @@ -294,12 +293,11 @@ fn cipher_to_url_params( // `sp`: Signature parameter // `url`: URL that is missing the signature parameter - let sig = some_or_bail!(params.get("s"), Err(DeobfError::Extraction("s param"))); - let sp = some_or_bail!(params.get("sp"), Err(DeobfError::Extraction("sp param"))); - let raw_url = some_or_bail!( - params.get("url"), - Err(DeobfError::Extraction("no url param")) - ); + let sig = params.get("s").ok_or(DeobfError::Extraction("s param"))?; + let sp = params.get("sp").ok_or(DeobfError::Extraction("sp param"))?; + let raw_url = params + .get("url") + .ok_or(DeobfError::Extraction("no url param"))?; let (url_base, mut url_params) = util::url_to_params(raw_url).or(Err(DeobfError::Extraction("url params")))?; diff --git a/src/client/playlist.rs b/src/client/playlist.rs index be331fc..03ee9a7 100644 --- a/src/client/playlist.rs +++ b/src/client/playlist.rs @@ -1,4 +1,4 @@ -use std::convert::TryFrom; +use std::{borrow::Cow, convert::TryFrom}; use crate::{ deobfuscate::Deobfuscator, @@ -63,42 +63,40 @@ impl MapResponse for response::Playlist { }; let mut tcbr_contents = contents.two_column_browse_results_renderer.contents; - let video_items = some_or_bail!( - some_or_bail!( - some_or_bail!( - tcbr_contents.try_swap_remove(0), - Err(ExtractionError::InvalidData( - "twoColumnBrowseResultsRenderer empty".into() - )) - ) - .tab_renderer - .content - .section_list_renderer - .contents - .try_swap_remove(0), - Err(ExtractionError::InvalidData( - "sectionListRenderer empty".into() - )) - ) + + let video_items = tcbr_contents + .try_swap_remove(0) + .ok_or(ExtractionError::InvalidData(Cow::Borrowed( + "twoColumnBrowseResultsRenderer empty", + )))? + .tab_renderer + .content + .section_list_renderer + .contents + .try_swap_remove(0) + .ok_or(ExtractionError::InvalidData(Cow::Borrowed( + "sectionListRenderer empty", + )))? .item_section_renderer .contents - .try_swap_remove(0), - Err(ExtractionError::InvalidData( - "itemSectionRenderer empty".into() - )) - ) - .playlist_video_list_renderer - .contents; + .try_swap_remove(0) + .ok_or(ExtractionError::InvalidData(Cow::Borrowed( + "itemSectionRenderer empty", + )))? + .playlist_video_list_renderer + .contents; let (videos, ctoken) = map_playlist_items(video_items.c); let (thumbnails, last_update_txt) = match self.sidebar { Some(sidebar) => { let mut sidebar_items = sidebar.playlist_sidebar_renderer.items; - let mut primary = some_or_bail!( - sidebar_items.try_swap_remove(0), - Err(ExtractionError::InvalidData("no primary sidebar".into())) - ); + let mut primary = + sidebar_items + .try_swap_remove(0) + .ok_or(ExtractionError::InvalidData(Cow::Borrowed( + "no primary sidebar", + )))?; ( primary @@ -113,10 +111,12 @@ impl MapResponse for response::Playlist { ) } None => { - let header_banner = some_or_bail!( - header.playlist_header_renderer.playlist_header_banner, - Err(ExtractionError::InvalidData("no thumbnail found".into())) - ); + let header_banner = header + .playlist_header_renderer + .playlist_header_banner + .ok_or(ExtractionError::InvalidData(Cow::Borrowed( + "no thumbnail found", + )))?; let mut byline = header.playlist_header_renderer.byline; let last_update_txt = byline @@ -131,12 +131,8 @@ impl MapResponse for response::Playlist { }; let n_videos = match ctoken { - Some(_) => { - ok_or_bail!( - util::parse_numeric(&header.playlist_header_renderer.num_videos_text), - Err(ExtractionError::InvalidData("no video count".into())) - ) - } + Some(_) => util::parse_numeric(&header.playlist_header_renderer.num_videos_text) + .map_err(|_| ExtractionError::InvalidData(Cow::Borrowed("no video count")))?, None => videos.len() as u64, }; @@ -187,7 +183,9 @@ impl MapResponse> for response::PlaylistCont { let mut actions = self.on_response_received_actions; let action = actions .try_swap_remove(0) - .ok_or_else(|| ExtractionError::InvalidData("no onResponseReceivedAction".into()))?; + .ok_or(ExtractionError::InvalidData(Cow::Borrowed( + "no onResponseReceivedAction", + )))?; let (items, ctoken) = map_playlist_items(action.append_continuation_items_action.continuation_items.c); diff --git a/src/client/search.rs b/src/client/search.rs index 8d93883..e206589 100644 --- a/src/client/search.rs +++ b/src/client/search.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use serde::{de::IgnoredAny, Serialize}; use crate::{ @@ -12,7 +14,7 @@ use super::{response, ClientType, MapResponse, MapResult, RustyPipeQuery, YTCont #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct QSearch<'a> { - context: YTContext, + context: YTContext<'a>, query: &'a str, #[serde(skip_serializing_if = "Option::is_none")] params: Option, @@ -69,11 +71,11 @@ impl RustyPipeQuery { .http_request_txt(self.client.inner.http.get(url).build()?) .await?; - let trimmed = response.get(5..).ok_or_else(|| { - Error::Extraction(ExtractionError::InvalidData( - "could not get string slice".into(), - )) - })?; + let trimmed = response + .get(5..) + .ok_or(Error::Extraction(ExtractionError::InvalidData( + Cow::Borrowed("could not get string slice"), + )))?; let parsed = serde_json::from_str::<( IgnoredAny, diff --git a/src/client/trends.rs b/src/client/trends.rs index d98b23b..da0bd60 100644 --- a/src/client/trends.rs +++ b/src/client/trends.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use crate::{ error::{Error, ExtractionError}, model::{Paginator, VideoItem}, @@ -54,7 +56,7 @@ impl MapResponse> for response::Startpage { let mut contents = self.contents.two_column_browse_results_renderer.tabs; let grid = contents .try_swap_remove(0) - .ok_or_else(|| ExtractionError::InvalidData("no contents".into()))? + .ok_or(ExtractionError::InvalidData(Cow::Borrowed("no contents")))? .tab_renderer .content .section_list_renderer @@ -78,7 +80,7 @@ impl MapResponse> for response::Trending { let mut contents = self.contents.two_column_browse_results_renderer.tabs; let items = contents .try_swap_remove(0) - .ok_or_else(|| ExtractionError::InvalidData("no contents".into()))? + .ok_or(ExtractionError::InvalidData(Cow::Borrowed("no contents")))? .tab_renderer .content .section_list_renderer diff --git a/src/client/url_resolver.rs b/src/client/url_resolver.rs index 9e1f15d..d5ee925 100644 --- a/src/client/url_resolver.rs +++ b/src/client/url_resolver.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use serde::Serialize; use crate::{ @@ -12,8 +14,8 @@ use super::{response, ClientType, MapResponse, RustyPipeQuery, YTContext}; #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -struct QResolveUrl { - context: YTContext, +struct QResolveUrl<'a> { + context: YTContext<'a>, url: String, } @@ -28,7 +30,7 @@ impl RustyPipeQuery { }); let mut path_split = url .path_segments() - .ok_or_else(|| Error::Other("invalid url: empty path".into()))?; + .ok_or(Error::Other(Cow::Borrowed("invalid url: empty path")))?; let get_start_time = || { params @@ -41,7 +43,7 @@ impl RustyPipeQuery { Some("watch") => { let id = params .get("v") - .ok_or_else(|| Error::Other("invalid url: no video id".into()))? + .ok_or(Error::Other(Cow::Borrowed("invalid url: no video id")))? .to_string(); Ok(UrlTarget::Video { @@ -56,7 +58,7 @@ impl RustyPipeQuery { Some("playlist") => { let id = params .get("list") - .ok_or_else(|| Error::Other("invalid url: no playlist id".into()))? + .ok_or(Error::Other(Cow::Borrowed("invalid url: no playlist id")))? .to_string(); Ok(UrlTarget::Playlist { id }) @@ -190,14 +192,16 @@ impl MapResponse for response::ResolvedUrl { let page_type = self .endpoint .command_metadata - .ok_or_else(|| ExtractionError::InvalidData("No command metadata".into()))? + .ok_or(ExtractionError::InvalidData(Cow::Borrowed( + "No command metadata", + )))? .web_command_metadata .web_page_type; let id = self .endpoint .browse_endpoint - .ok_or_else(|| ExtractionError::InvalidData("No browse ID".into()))? + .ok_or(ExtractionError::InvalidData(Cow::Borrowed("No browse ID")))? .browse_id; Ok(MapResult { diff --git a/src/client/video_details.rs b/src/client/video_details.rs index d515f3e..132af0f 100644 --- a/src/client/video_details.rs +++ b/src/client/video_details.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use serde::Serialize; use crate::{ @@ -16,7 +18,7 @@ use super::{ #[derive(Debug, Serialize)] struct QVideo<'a> { - context: YTContext, + context: YTContext<'a>, /// YouTube video ID video_id: &'a str, /// Set to true to allow extraction of streams with sensitive content @@ -79,10 +81,14 @@ impl MapResponse for response::VideoDetails { let contents = self .contents - .ok_or_else(|| ExtractionError::ContentUnavailable("Video not found".into()))?; - let current_video_endpoint = self - .current_video_endpoint - .ok_or_else(|| ExtractionError::ContentUnavailable("Video not found".into()))?; + .ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( + "Video not found", + )))?; + let current_video_endpoint = + self.current_video_endpoint + .ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed( + "Video not found", + )))?; let video_id = current_video_endpoint.watch_endpoint.video_id; if id != video_id { diff --git a/src/deobfuscate.rs b/src/deobfuscate.rs index 60ae12e..f4723c8 100644 --- a/src/deobfuscate.rs +++ b/src/deobfuscate.rs @@ -92,48 +92,44 @@ fn get_sig_fn(player_js: &str) -> Result { let function_pattern_str = "(".to_owned() + &dfunc_name.replace('$', "\\$") + "=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})"; - let function_pattern = ok_or_bail!( - Regex::new(&function_pattern_str), - Err(DeobfError::Other("could not parse function pattern regex")) - ); + let function_pattern = Regex::new(&function_pattern_str) + .map_err(|_| DeobfError::Other("could not parse function pattern regex"))?; let deobfuscate_function = "var ".to_owned() - + some_or_bail!( - function_pattern.captures(player_js).ok().flatten(), - Err(DeobfError::Extraction("deobf function")) - ) - .get(1) - .unwrap() - .as_str() + + function_pattern + .captures(player_js) + .ok() + .flatten() + .ok_or(DeobfError::Extraction("deobf function"))? + .get(1) + .unwrap() + .as_str() + ";"; static HELPER_OBJECT_NAME_REGEX: Lazy = Lazy::new(|| Regex::new(";([A-Za-z0-9_\\$]{2})\\...\\(").unwrap()); - let helper_object_name = some_or_bail!( - HELPER_OBJECT_NAME_REGEX - .captures(&deobfuscate_function) - .ok() - .flatten(), - Err(DeobfError::Extraction("helper object name")) - ) - .get(1) - .unwrap() - .as_str(); + let helper_object_name = HELPER_OBJECT_NAME_REGEX + .captures(&deobfuscate_function) + .ok() + .flatten() + .ok_or(DeobfError::Extraction("helper object name"))? + .get(1) + .unwrap() + .as_str(); let helper_pattern_str = "(var ".to_owned() + &helper_object_name.replace('$', "\\$") + "=\\{.+?\\}\\};)"; - let helper_pattern = ok_or_bail!( - Regex::new(&helper_pattern_str), - Err(DeobfError::Other("could not parse helper pattern regex")) - ); + let helper_pattern = Regex::new(&helper_pattern_str) + .map_err(|_| DeobfError::Other("could not parse helper pattern regex"))?; let player_js_nonl = player_js.replace('\n', ""); - let helper_object = some_or_bail!( - helper_pattern.captures(&player_js_nonl).ok().flatten(), - Err(DeobfError::Extraction("helper object")) - ) - .get(1) - .unwrap() - .as_str(); + let helper_object = helper_pattern + .captures(&player_js_nonl) + .ok() + .flatten() + .ok_or(DeobfError::Extraction("helper object"))? + .get(1) + .unwrap() + .as_str(); Ok(helper_object.to_owned() + &deobfuscate_function + &caller_function(&dfunc_name)) } @@ -156,10 +152,11 @@ fn get_nsig_fn_name(player_js: &str) -> Result { .unwrap() }); - let fname_match = some_or_bail!( - FUNCTION_NAME_REGEX.captures(player_js).ok().flatten(), - Err(DeobfError::Extraction("n_deobf function")) - ); + let fname_match = FUNCTION_NAME_REGEX + .captures(player_js) + .ok() + .flatten() + .ok_or(DeobfError::Extraction("n_deobf function"))?; let function_name = fname_match.get(1).unwrap().as_str(); @@ -179,18 +176,19 @@ fn get_nsig_fn_name(player_js: &str) -> Result { "could not parse helper pattern regex", )))?; - let array_str = some_or_bail!( - array_pattern.captures(player_js).ok().flatten(), - Err(DeobfError::Extraction("n_deobf array_str")) - ) - .get(1) - .unwrap() - .as_str(); + let array_str = array_pattern + .captures(player_js) + .ok() + .flatten() + .ok_or(DeobfError::Extraction("n_deobf array_str"))? + .get(1) + .unwrap() + .as_str(); + let mut names = array_str.split(','); - let name = some_or_bail!( - names.nth(array_num), - Err(DeobfError::Extraction("n_deobf function name")) - ); + let name = names + .nth(array_num) + .ok_or(DeobfError::Extraction("n_deobf function name"))?; Ok(name.to_owned()) } @@ -279,13 +277,14 @@ async fn get_player_js_url(http: &Client) -> Result { Regex::new(r#"https:\\\/\\\/www\.youtube\.com\\\/s\\\/player\\\/([a-z0-9]{8})\\\/"#) .unwrap() }); - let player_hash = some_or_bail!( - PLAYER_HASH_PATTERN.captures(&text).ok().flatten(), - Err(DeobfError::Extraction("player hash")) - ) - .get(1) - .unwrap() - .as_str(); + let player_hash = PLAYER_HASH_PATTERN + .captures(&text) + .ok() + .flatten() + .ok_or(DeobfError::Extraction("player hash"))? + .get(1) + .unwrap() + .as_str(); Ok(format!( "https://www.youtube.com/s/player/{}/player_ias.vflset/en_US/base.js", @@ -302,14 +301,15 @@ fn get_sts(player_js: &str) -> Result { static STS_PATTERN: Lazy = Lazy::new(|| Regex::new("signatureTimestamp[=:](\\d+)").unwrap()); - Ok(some_or_bail!( - STS_PATTERN.captures(player_js).ok().flatten(), - Err(DeobfError::Extraction("sts")) - ) - .get(1) - .unwrap() - .as_str() - .to_owned()) + Ok(STS_PATTERN + .captures(player_js) + .ok() + .flatten() + .ok_or(DeobfError::Extraction("sts"))? + .get(1) + .unwrap() + .as_str() + .to_owned()) } #[cfg(test)] diff --git a/src/download.rs b/src/download.rs index 1d52655..4dc144e 100644 --- a/src/download.rs +++ b/src/download.rs @@ -1,6 +1,6 @@ //! YouTube audio/video downloader -use std::{cmp::Ordering, ffi::OsString, ops::Range, path::PathBuf}; +use std::{borrow::Cow, cmp::Ordering, ffi::OsString, ops::Range, path::PathBuf}; use fancy_regex::Regex; use futures::stream::{self, StreamExt}; @@ -45,16 +45,15 @@ fn get_download_range(offset: u64, size: Option) -> Range { fn parse_cr_header(cr_header: &str) -> Result<(u64, u64)> { static PATTERN: Lazy = Lazy::new(|| Regex::new(r#"bytes (\d+)-(\d+)/(\d+)"#).unwrap()); - let captures = some_or_bail!( - PATTERN.captures(cr_header).ok().flatten(), - Err(DownloadError::Progressive( + let captures = PATTERN.captures(cr_header).ok().flatten().ok_or_else(|| { + DownloadError::Progressive( format!( "Content-Range header '{}' does not match pattern", cr_header ) - .into() - )) - ); + .into(), + ) + })?; Ok(( captures.get(2).unwrap().as_str().parse().map_err(|_| { @@ -106,16 +105,18 @@ async fn download_single_file>( .await? .error_for_status()?; - let cr_header = some_or_bail!( - res.headers().get(header::CONTENT_RANGE), - Err(DownloadError::Progressive( - "Did not get Content-Range header".into() - )) - ) - .to_str() - .map_err(|_| { - DownloadError::Progressive("could not convert Content-Range header to string".into()) - })?; + let cr_header = res + .headers() + .get(header::CONTENT_RANGE) + .ok_or(DownloadError::Progressive(Cow::Borrowed( + "Did not get Content-Range header", + )))? + .to_str() + .map_err(|_| { + DownloadError::Progressive( + "could not convert Content-Range header to string".into(), + ) + })?; let (_, original_size) = parse_cr_header(cr_header)?; @@ -193,16 +194,18 @@ async fn download_chunks_by_header( .error_for_status()?; // Content-Range: bytes 0-100/451368980 - let cr_header = some_or_bail!( - res.headers().get(header::CONTENT_RANGE), - Err(DownloadError::Progressive( - "Did not get Content-Range header".into() - )) - ) - .to_str() - .map_err(|_| { - DownloadError::Progressive("could not convert Content-Range header to string".into()) - })?; + let cr_header = res + .headers() + .get(header::CONTENT_RANGE) + .ok_or(DownloadError::Progressive(Cow::Borrowed( + "Did not get Content-Range header", + )))? + .to_str() + .map_err(|_| { + DownloadError::Progressive( + "could not convert Content-Range header to string".into(), + ) + })?; let (parsed_offset, parsed_size) = parse_cr_header(cr_header)?; diff --git a/src/macros.rs b/src/macros.rs index 3b77400..7da2081 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,5 +1,4 @@ /// Returns an unwrapped Option if Some() otherwise returns the passed expression -#[allow(unused_macros)] macro_rules! some_or_bail { ($opt:expr, $ret:expr $(,)?) => {{ match $opt { @@ -11,21 +10,7 @@ macro_rules! some_or_bail { }}; } -/// Returns an unwrapped Option if Some() otherwise continues a loop. -#[allow(unused_macros)] -macro_rules! some_or_continue { - ($opt:expr $(,)?) => {{ - match $opt { - Some(stuff) => stuff, - None => { - continue; - } - } - }}; -} - /// Returns an unwrapped Result if Ok() otherwise returns the passed expression -#[allow(unused_macros)] macro_rules! ok_or_bail { ($result:expr, $ret:expr $(,)?) => {{ match $result { @@ -36,15 +21,3 @@ macro_rules! ok_or_bail { } }}; } - -#[allow(unused_macros)] -macro_rules! ok_or_continue { - ($opt:expr $(,)?) => {{ - match $opt { - Ok(stuff) => stuff, - Err(_) => { - continue; - } - } - }}; -} diff --git a/src/param/stream_filter.rs b/src/param/stream_filter.rs index 34210ee..ce14c3e 100644 --- a/src/param/stream_filter.rs +++ b/src/param/stream_filter.rs @@ -304,13 +304,10 @@ impl VideoPlayer { (Some(video_stream), None) => (Some(video_stream), None), (Some(video_stream), Some(video_only_stream)) => match video_only_stream > video_stream { - true => ( - Some(video_only_stream), - Some(some_or_bail!( - self.select_audio_stream(filter), - (Some(video_stream), None) - )), - ), + true => match self.select_audio_stream(filter) { + Some(audio_stream) => (Some(video_only_stream), Some(audio_stream)), + None => (Some(video_stream), None), + }, false => (Some(video_stream), None), }, }