Add option so specify different proxies for api and download requests (#282)

This commit is contained in:
bytedream 2024-03-10 12:49:47 +01:00
parent f1d266c940
commit e3a7fd9246
2 changed files with 85 additions and 38 deletions

View file

@ -61,9 +61,10 @@ pub struct Cli {
#[arg(help = "Use a proxy to route all traffic through")] #[arg(help = "Use a proxy to route all traffic through")]
#[arg(long_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")] 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)] Besides specifying a simple url, you also can partially control where a proxy should be used: '<url>:' only proxies api requests, ':<url>' only proxies download traffic, '<url>:<url>' proxies api requests through the first url and download traffic through the second url")]
proxy: Option<Proxy>, #[arg(global = true, long, value_parser = crate::utils::clap::clap_parse_proxies)]
proxy: Option<(Option<Proxy>, Option<Proxy>)>,
#[arg(help = "Use custom user agent")] #[arg(help = "Use custom user agent")]
#[arg(global = true, long)] #[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<Context> { async fn create_ctx(cli: &mut Cli) -> Result<Context> {
let client = { let crunchy_client = reqwest_client(
let mut builder = CrunchyrollBuilder::predefined_client_builder(); cli.proxy.as_ref().and_then(|p| p.0.clone()),
if let Some(p) = &cli.proxy { cli.user_agent.clone(),
builder = builder.proxy(p.clone()) );
} let internal_client = reqwest_client(
if let Some(ua) = &cli.user_agent { cli.proxy.as_ref().and_then(|p| p.1.clone()),
builder = builder.user_agent(ua) cli.user_agent.clone(),
} );
#[cfg(any(feature = "openssl-tls", feature = "openssl-tls-static"))] let crunchy = crunchyroll_session(
let client = { cli,
let mut builder = builder.use_native_tls().tls_built_in_root_certs(false); crunchy_client.clone(),
cli.speed_limit
for certificate in rustls_native_certs::load_native_certs().unwrap() { .map(|l| RateLimiterService::new(l, crunchy_client)),
builder = builder.add_root_certificate( )
reqwest::Certificate::from_der(certificate.0.as_slice()).unwrap(), .await?;
)
}
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?;
Ok(Context { Ok(Context {
crunchy, crunchy,
client, client: internal_client.clone(),
rate_limiter, rate_limiter: cli
.speed_limit
.map(|l| RateLimiterService::new(l, internal_client)),
}) })
} }
@ -372,3 +359,30 @@ async fn crunchyroll_session(
Ok(crunchy) Ok(crunchy)
} }
fn reqwest_client(proxy: Option<Proxy>, user_agent: Option<String>) -> 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
}

View file

@ -1,13 +1,46 @@
use crate::utils::parse::parse_resolution; use crate::utils::parse::parse_resolution;
use crunchyroll_rs::media::Resolution; use crunchyroll_rs::media::Resolution;
use regex::Regex;
use reqwest::Proxy; use reqwest::Proxy;
pub fn clap_parse_resolution(s: &str) -> Result<Resolution, String> { pub fn clap_parse_resolution(s: &str) -> Result<Resolution, String> {
parse_resolution(s.to_string()).map_err(|e| e.to_string()) parse_resolution(s.to_string()).map_err(|e| e.to_string())
} }
pub fn clap_parse_proxy(s: &str) -> Result<Proxy, String> { pub fn clap_parse_proxies(s: &str) -> Result<(Option<Proxy>, Option<Proxy>), String> {
Proxy::all(s).map_err(|e| e.to_string()) let double_proxy_regex =
Regex::new(r"^(?P<first>(https?|socks5h?)://.+):(?P<second>(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<u32, String> { pub fn clap_parse_speed_limit(s: &str) -> Result<u32, String> {