Files
proxy-scraper-checker/src/scraper.rs
T

168 lines
5.0 KiB
Rust
Raw Normal View History

2025-04-10 06:55:12 +00:00
use std::{collections::HashSet, sync::Arc};
2025-04-29 13:06:18 +03:00
use color_eyre::eyre::{OptionExt as _, WrapErr as _};
2025-04-10 06:55:12 +00:00
2025-04-28 08:24:01 +03:00
#[cfg(feature = "tui")]
use crate::event::{AppEvent, Event};
2025-04-10 06:55:12 +00:00
use crate::{
config::Config,
parsers::PROXY_REGEX,
proxy::{Proxy, ProxyType},
storage::ProxyStorage,
2025-04-29 13:06:18 +03:00
utils::{is_http_url, pretty_error},
2025-04-10 06:55:12 +00:00
};
async fn fetch_text(
config: Arc<Config>,
http_client: reqwest::Client,
source: &str,
) -> color_eyre::Result<String> {
2025-04-11 17:05:21 +03:00
if is_http_url(source) {
2025-04-10 06:55:12 +00:00
http_client
.get(source)
.timeout(config.source_timeout)
.send()
.await
.wrap_err_with(move || {
format!("failed to send request to {source}")
})?
.error_for_status()
.wrap_err_with(move || {
format!("got error status code from {source}")
})?
.text()
.await
.wrap_err_with(move || {
format!("failed to decode {source} response as text")
2025-04-11 17:05:21 +03:00
})
2025-04-10 06:55:12 +00:00
} else {
tokio::fs::read_to_string(
source.strip_prefix("file://").unwrap_or(source),
)
.await
2025-04-11 17:05:21 +03:00
.wrap_err("failed to read file to string")
}
2025-04-10 06:55:12 +00:00
}
async fn scrape_one(
config: Arc<Config>,
http_client: reqwest::Client,
proto: ProxyType,
source: &str,
2025-04-27 21:59:32 +03:00
#[cfg(feature = "tui")] tx: tokio::sync::mpsc::UnboundedSender<Event>,
2025-04-10 06:55:12 +00:00
) -> color_eyre::Result<HashSet<Proxy>> {
let text_result =
2025-04-24 08:57:50 +03:00
fetch_text(Arc::clone(&config), http_client.clone(), source).await;
2025-04-10 06:55:12 +00:00
2025-04-27 21:59:32 +03:00
#[cfg(feature = "tui")]
2025-04-10 06:55:12 +00:00
tx.send(Event::App(AppEvent::SourceScraped(proto.clone())))?;
let text = match text_result {
Ok(text) => text,
Err(e) => {
2025-04-29 13:06:18 +03:00
log::warn!("{} | {}", source, pretty_error(&e));
return Ok(HashSet::new());
2025-04-10 06:55:12 +00:00
}
};
let matches: Vec<_> = PROXY_REGEX.captures_iter(&text).collect();
if matches.is_empty() {
log::warn!("{source} | No proxies found");
return Ok(HashSet::new());
2025-04-10 06:55:12 +00:00
}
if config.proxies_per_source_limit != 0
&& matches.len() > config.proxies_per_source_limit
{
log::warn!(
"{} | Too many proxies ({}) - skipped",
source,
matches.len(),
);
return Ok(HashSet::new());
2025-04-10 06:55:12 +00:00
}
let mut proxies = HashSet::with_capacity(matches.len());
2025-04-10 06:55:12 +00:00
for capture in matches {
2025-04-29 13:06:18 +03:00
let capture =
capture.wrap_err("failed to match regex captures groups")?;
2025-04-10 06:55:12 +00:00
let proxy = Proxy {
2025-04-29 13:06:18 +03:00
protocol: match capture.name("protocol") {
Some(m) => m.as_str().try_into()?,
None => proto.clone(),
},
host: capture
.name("host")
.ok_or_eyre("failed to match \"host\" regex capture group")?
.as_str()
.to_owned(),
port: capture
.name("port")
.ok_or_eyre("failed to match \"port\" regex capture group")?
.as_str()
.parse()?,
2025-04-10 06:55:12 +00:00
username: capture.name("username").map(|m| m.as_str().to_owned()),
password: capture.name("password").map(|m| m.as_str().to_owned()),
timeout: None,
exit_ip: None,
};
proxies.insert(proxy);
}
Ok(proxies)
}
2025-04-24 08:57:50 +03:00
pub async fn scrape_all(
2025-04-24 13:44:02 +03:00
config: Arc<Config>,
2025-04-10 06:55:12 +00:00
http_client: reqwest::Client,
2025-04-27 21:59:32 +03:00
#[cfg(feature = "tui")] tx: tokio::sync::mpsc::UnboundedSender<Event>,
2025-04-10 06:55:12 +00:00
) -> color_eyre::Result<ProxyStorage> {
let mut join_set = tokio::task::JoinSet::new();
for (proto, sources) in config.sources.clone() {
2025-04-27 21:59:32 +03:00
#[cfg(feature = "tui")]
2025-04-10 06:55:12 +00:00
tx.send(Event::App(AppEvent::SourcesTotal(
proto.clone(),
sources.len(),
)))?;
for source in sources {
2025-04-24 08:57:50 +03:00
let config = Arc::clone(&config);
2025-04-10 06:55:12 +00:00
let http_client = http_client.clone();
let proto = proto.clone();
2025-04-27 21:59:32 +03:00
#[cfg(feature = "tui")]
2025-04-10 06:55:12 +00:00
let tx = tx.clone();
join_set.spawn(async move {
2025-04-27 21:59:32 +03:00
scrape_one(
config,
http_client,
proto,
&source,
#[cfg(feature = "tui")]
tx,
)
.await
2025-04-10 06:55:12 +00:00
});
}
}
let mut storage =
ProxyStorage::new(config.sources.keys().cloned().collect());
while let Some(res) = join_set.join_next().await {
2025-04-27 21:59:32 +03:00
#[cfg(feature = "tui")]
2025-04-10 06:55:12 +00:00
let mut seen_protocols = HashSet::new();
2025-04-29 13:06:18 +03:00
for proxy in res
.wrap_err("failed to join proxy scrape task")?
.wrap_err("proxy scrape task failed")?
{
2025-04-27 21:59:32 +03:00
#[cfg(feature = "tui")]
2025-04-10 06:55:12 +00:00
seen_protocols.insert(proxy.protocol.clone());
storage.insert(proxy);
}
2025-04-27 21:59:32 +03:00
#[cfg(feature = "tui")]
2025-04-10 06:55:12 +00:00
for proto in seen_protocols {
let count = storage.iter().filter(|p| p.protocol == proto).count();
tx.send(Event::App(AppEvent::TotalProxies(proto, count)))?;
}
}
Ok(storage)
}