use clap::Parser;
use reqwest::Client;
use std::error::Error;
use std::sync::Arc;
use yt_transcript_rs::proxies::GenericProxyConfig;
use yt_transcript_rs::proxies::InvalidProxyConfig;
use yt_transcript_rs::video_data_fetcher::VideoDataFetcher;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Args {
#[arg(short, long, env = "YOUTUBE_VIDEO_ID")]
video_id: String,
#[arg(long, env = "PROXY_HOST")]
proxy_host: Option<String>,
#[arg(long, env = "PROXY_PORT")]
proxy_port: Option<u16>,
#[arg(long, env = "PROXY_USERNAME")]
proxy_username: Option<String>,
#[arg(long, env = "PROXY_PASSWORD")]
proxy_password: Option<String>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
dotenvy::dotenv().ok();
tracing_subscriber::fmt::init();
let args = Args::parse();
println!("Arguments: {:?}", args);
let proxy_config = if args.proxy_host.is_some() {
EnvProxyConfig::from_args(&args)?.into_boxed_proxy()
} else {
EnvProxyConfig::from_env()?.into_boxed_proxy()
};
let client = {
let mut builder = Client::builder()
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
.default_headers({
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::ACCEPT_LANGUAGE,
reqwest::header::HeaderValue::from_static("en-US"),
);
headers
});
if let Some(proxy_config_ref) = &proxy_config {
let proxy_map = proxy_config_ref.to_requests_dict();
let proxies = reqwest::Proxy::custom(move |_url| {
if let Some(http_proxy) = proxy_map.get("http") {
return Some(http_proxy.clone());
}
None
});
builder = builder.proxy(proxies);
if proxy_config_ref.prevent_keeping_connections_alive() {
builder = builder.connection_verbose(true).tcp_keepalive(None);
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::CONNECTION,
reqwest::header::HeaderValue::from_static("close"),
);
builder = builder.default_headers(headers);
}
}
builder.build()?
};
println!("proxy_config: {:?}", proxy_config);
println!("client: {:?}", client);
let fetcher = Arc::new(VideoDataFetcher::new(client.clone()));
let video_info = fetcher.fetch_video_infos(&args.video_id).await?;
println!("Video title: {:?}", video_info);
Ok(())
}
#[derive(Debug, Clone)]
pub struct EnvProxyConfig {
pub host: String,
pub port: u16,
pub username: String,
pub password: String,
}
impl EnvProxyConfig {
pub fn from_args(args: &Args) -> Result<Option<Self>, InvalidProxyConfig> {
match (
&args.proxy_host,
args.proxy_port,
&args.proxy_username,
&args.proxy_password,
) {
(Some(host), Some(port), Some(username), Some(password)) => Ok(Some(Self {
host: host.clone(),
port,
username: username.clone(),
password: password.clone(),
})),
(None, None, None, None) => Ok(None),
(host, port, username, password) => Err(InvalidProxyConfig(format!(
"Invalid proxy config: host={:?}, port={:?}, username={:?}, password={:?}",
host, port, username, password,
))),
}
}
pub fn from_env() -> Result<Option<Self>, InvalidProxyConfig> {
let host = std::env::var("PROXY_HOST").ok();
let port_str = std::env::var("PROXY_PORT").ok();
let username = std::env::var("PROXY_USERNAME").ok();
let password = std::env::var("PROXY_PASSWORD").ok();
match (host, port_str, username, password) {
(Some(host), Some(port_str), Some(username), Some(password)) => {
let port = match port_str.parse::<u16>() {
Ok(port) => port,
Err(_) => {
return Err(InvalidProxyConfig(format!("Invalid port: {}", port_str)));
}
};
Ok(Some(Self {
host,
port,
username,
password,
}))
}
(None, None, None, None) => Ok(None),
(host, port_str, username, password) => Err(InvalidProxyConfig(format!(
"Invalid proxy config: host={:?}, port={:?}, username={:?}, password={:?}",
host, port_str, username, password,
))),
}
}
}
pub fn get_proxy_config(
config: Option<&EnvProxyConfig>,
) -> Option<Box<dyn yt_transcript_rs::proxies::ProxyConfig + Send + Sync>> {
config
.and_then(|p| GenericProxyConfig::try_from(p.clone()).ok())
.map(
|config| -> Box<dyn yt_transcript_rs::proxies::ProxyConfig + Send + Sync> {
Box::new(config)
},
)
}
pub trait IntoBoxedProxyConfig {
fn into_boxed_proxy(
self,
) -> Option<Box<dyn yt_transcript_rs::proxies::ProxyConfig + Send + Sync>>;
}
impl IntoBoxedProxyConfig for Option<EnvProxyConfig> {
fn into_boxed_proxy(
self,
) -> Option<Box<dyn yt_transcript_rs::proxies::ProxyConfig + Send + Sync>> {
self.as_ref().and_then(|p| get_proxy_config(Some(p)))
}
}
impl TryFrom<EnvProxyConfig> for GenericProxyConfig {
type Error = InvalidProxyConfig;
fn try_from(config: EnvProxyConfig) -> Result<Self, Self::Error> {
GenericProxyConfig::new(
Some(format!(
"http://{}:{}@{}:{}",
config.username, config.password, config.host, config.port
)),
Some(format!(
"https://{}:{}@{}:{}",
config.username, config.password, config.host, config.port
)),
)
}
}