From e3a7fd92468a9cc1b61556f4eefff4d9c08e95b8 Mon Sep 17 00:00:00 2001 From: bytedream Date: Sun, 10 Mar 2024 12:49:47 +0100 Subject: [PATCH] Add option so specify different proxies for api and download requests (#282) --- crunchy-cli-core/src/lib.rs | 86 +++++++++++++++++------------- crunchy-cli-core/src/utils/clap.rs | 37 ++++++++++++- 2 files changed, 85 insertions(+), 38 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index d362a2c..9dd3360 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -61,9 +61,10 @@ pub struct Cli { #[arg(help = "Use a proxy to route all traffic through")] #[arg(long_help = "Use a proxy to route all traffic through. \ - Make sure that the proxy can either forward TLS requests, which is needed to bypass the (cloudflare) bot protection, or that it is configured so that the proxy can bypass the protection itself")] - #[arg(global = true, long, value_parser = crate::utils::clap::clap_parse_proxy)] - proxy: Option, + Make sure that the proxy can either forward TLS requests, which is needed to bypass the (cloudflare) bot protection, or that it is configured so that the proxy can bypass the protection itself. \ + Besides specifying a simple url, you also can partially control where a proxy should be used: ':' only proxies api requests, ':' only proxies download traffic, ':' proxies api requests through the first url and download traffic through the second url")] + #[arg(global = true, long, value_parser = crate::utils::clap::clap_parse_proxies)] + proxy: Option<(Option, Option)>, #[arg(help = "Use custom user agent")] #[arg(global = true, long)] @@ -238,43 +239,29 @@ async fn execute_executor(executor: impl Execute, ctx: Context) { } async fn create_ctx(cli: &mut Cli) -> Result { - let client = { - let mut builder = CrunchyrollBuilder::predefined_client_builder(); - if let Some(p) = &cli.proxy { - builder = builder.proxy(p.clone()) - } - if let Some(ua) = &cli.user_agent { - builder = builder.user_agent(ua) - } + let crunchy_client = reqwest_client( + cli.proxy.as_ref().and_then(|p| p.0.clone()), + cli.user_agent.clone(), + ); + let internal_client = reqwest_client( + cli.proxy.as_ref().and_then(|p| p.1.clone()), + cli.user_agent.clone(), + ); - #[cfg(any(feature = "openssl-tls", feature = "openssl-tls-static"))] - let client = { - let mut builder = builder.use_native_tls().tls_built_in_root_certs(false); - - for certificate in rustls_native_certs::load_native_certs().unwrap() { - builder = builder.add_root_certificate( - reqwest::Certificate::from_der(certificate.0.as_slice()).unwrap(), - ) - } - - builder.build().unwrap() - }; - #[cfg(not(any(feature = "openssl-tls", feature = "openssl-tls-static")))] - let client = builder.build().unwrap(); - - client - }; - - let rate_limiter = cli - .speed_limit - .map(|l| RateLimiterService::new(l, client.clone())); - - let crunchy = crunchyroll_session(cli, client.clone(), rate_limiter.clone()).await?; + let crunchy = crunchyroll_session( + cli, + crunchy_client.clone(), + cli.speed_limit + .map(|l| RateLimiterService::new(l, crunchy_client)), + ) + .await?; Ok(Context { crunchy, - client, - rate_limiter, + client: internal_client.clone(), + rate_limiter: cli + .speed_limit + .map(|l| RateLimiterService::new(l, internal_client)), }) } @@ -372,3 +359,30 @@ async fn crunchyroll_session( Ok(crunchy) } + +fn reqwest_client(proxy: Option, user_agent: Option) -> Client { + let mut builder = CrunchyrollBuilder::predefined_client_builder(); + if let Some(p) = proxy { + builder = builder.proxy(p) + } + if let Some(ua) = user_agent { + builder = builder.user_agent(ua) + } + + #[cfg(any(feature = "openssl-tls", feature = "openssl-tls-static"))] + let client = { + let mut builder = builder.use_native_tls().tls_built_in_root_certs(false); + + for certificate in rustls_native_certs::load_native_certs().unwrap() { + builder = builder.add_root_certificate( + reqwest::Certificate::from_der(certificate.0.as_slice()).unwrap(), + ) + } + + builder.build().unwrap() + }; + #[cfg(not(any(feature = "openssl-tls", feature = "openssl-tls-static")))] + let client = builder.build().unwrap(); + + client +} diff --git a/crunchy-cli-core/src/utils/clap.rs b/crunchy-cli-core/src/utils/clap.rs index 37a34d3..35de71f 100644 --- a/crunchy-cli-core/src/utils/clap.rs +++ b/crunchy-cli-core/src/utils/clap.rs @@ -1,13 +1,46 @@ use crate::utils::parse::parse_resolution; use crunchyroll_rs::media::Resolution; +use regex::Regex; use reqwest::Proxy; pub fn clap_parse_resolution(s: &str) -> Result { parse_resolution(s.to_string()).map_err(|e| e.to_string()) } -pub fn clap_parse_proxy(s: &str) -> Result { - Proxy::all(s).map_err(|e| e.to_string()) +pub fn clap_parse_proxies(s: &str) -> Result<(Option, Option), String> { + let double_proxy_regex = + Regex::new(r"^(?P(https?|socks5h?)://.+):(?P(https?|socks5h?)://.+)$") + .unwrap(); + + if let Some(capture) = double_proxy_regex.captures(s) { + // checks if the input is formatted like 'https://example.com:socks5://examples.com' and + // splits the string into 2 separate proxies at the middle colon + + let first = capture.name("first").unwrap().as_str(); + let second = capture.name("second").unwrap().as_str(); + Ok(( + Some(Proxy::all(first).map_err(|e| format!("first proxy: {e}"))?), + Some(Proxy::all(second).map_err(|e| format!("second proxy: {e}"))?), + )) + } else if s.starts_with(':') { + // checks if the input is formatted like ':https://example.com' and returns a proxy on the + // second tuple position + Ok(( + None, + Some(Proxy::all(s.trim_start_matches(':')).map_err(|e| e.to_string())?), + )) + } else if s.ends_with(':') { + // checks if the input is formatted like 'https://example.com:' and returns a proxy on the + // first tuple position + Ok(( + Some(Proxy::all(s.trim_end_matches(':')).map_err(|e| e.to_string())?), + None, + )) + } else { + // returns the same proxy for both tuple positions + let proxy = Proxy::all(s).map_err(|e| e.to_string())?; + Ok((Some(proxy.clone()), Some(proxy))) + } } pub fn clap_parse_speed_limit(s: &str) -> Result {