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

213 lines
6.3 KiB
Rust
Raw Normal View History

2025-08-12 21:36:27 +03:00
use std::sync::Arc;
2025-04-10 06:55:12 +00:00
2025-09-30 07:53:39 +03:00
use color_eyre::eyre::{OptionExt as _, WrapErr as _};
2025-08-13 07:46:28 +03:00
use foldhash::HashSetExt 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::{
2025-08-13 07:46:28 +03:00
HashSet,
config::{Config, Source},
2025-04-10 06:55:12 +00:00
parsers::PROXY_REGEX,
proxy::{Proxy, ProxyType},
utils::pretty_error,
2025-04-10 06:55:12 +00:00
};
async fn scrape_one(
config: Arc<Config>,
http_client: reqwest_middleware::ClientWithMiddleware,
2025-04-10 06:55:12 +00:00
proto: ProxyType,
proxies: Arc<parking_lot::Mutex<HashSet<Proxy>>>,
source: Arc<Source>,
2025-04-27 21:59:32 +03:00
#[cfg(feature = "tui")] tx: tokio::sync::mpsc::UnboundedSender<Event>,
) -> crate::Result<()> {
let text_result = if let Ok(u) = url::Url::parse(&source.url) {
match u.scheme() {
"http" | "https" => {
let mut request = http_client.get(u);
drop(http_client);
if let Some(auth) = &source.basic_auth {
request = request
.basic_auth(&auth.username, auth.password.as_ref());
}
if let Some(headers) = &source.headers {
for (k, v) in headers {
request = request.header(k, v);
}
}
match request.send().await {
Ok(resp) => resp.text().await.map_err(Into::into),
Err(e) => Err(e.into()),
}
}
2025-08-15 20:56:51 +03:00
_ => {
drop(http_client);
match u.to_file_path() {
2025-09-30 07:53:39 +03:00
Ok(path) => tokio::fs::read_to_string(path)
.await
.wrap_err_with(move || {
format!("failed to read file to string: {u}")
}),
Err(()) => tokio::fs::read_to_string(&source.url)
.await
.wrap_err_with(move || {
format!("failed to read file to string: {u}")
}),
2025-08-15 20:56:51 +03:00
}
}
}
2025-07-24 10:19:22 +03:00
} else {
2025-08-15 20:56:51 +03:00
drop(http_client);
2025-09-30 07:53:39 +03:00
tokio::fs::read_to_string(&source.url).await.wrap_err_with(|| {
format!("failed to read file to string: {}", source.url)
})
2025-07-24 10:19:22 +03:00
};
2025-04-10 06:55:12 +00:00
2025-04-27 21:59:32 +03:00
#[cfg(feature = "tui")]
drop(tx.send(Event::App(AppEvent::SourceScraped(proto))));
2025-04-10 06:55:12 +00:00
let text = match text_result {
Ok(text) => text,
Err(e) => {
tracing::warn!("{}: {}", source.url, pretty_error(&e));
2025-05-08 18:20:19 +03:00
return Ok(());
2025-04-10 06:55:12 +00:00
}
};
2025-09-18 11:55:32 +03:00
#[cfg(feature = "tui")]
let mut seen_protocols = HashSet::new();
let mut new_proxies = HashSet::new();
for maybe_capture in PROXY_REGEX.captures_iter(&text) {
2025-08-13 19:50:30 +03:00
if config.scraping.max_proxies_per_source != 0
2025-09-18 11:55:32 +03:00
&& new_proxies.len() >= config.scraping.max_proxies_per_source
2025-08-13 19:50:30 +03:00
{
tracing::warn!(
"{}: too many proxies (> {}) - skipped",
source.url,
2025-08-13 19:50:30 +03:00
config.scraping.max_proxies_per_source
);
return Ok(());
}
2025-04-10 06:55:12 +00:00
2025-09-18 11:55:32 +03:00
let capture = maybe_capture?;
2025-08-15 20:56:51 +03:00
2025-05-24 01:31:42 +03:00
let protocol = match capture.name("protocol") {
Some(m) => m.as_str().parse()?,
None => proto,
2025-04-10 06:55:12 +00:00
};
2025-09-18 11:55:32 +03:00
if config.protocol_is_enabled(protocol) {
2025-05-24 01:31:42 +03:00
#[cfg(feature = "tui")]
seen_protocols.insert(protocol);
2025-09-18 11:55:32 +03:00
new_proxies.insert(Proxy {
2025-05-24 01:31:42 +03:00
protocol,
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()?,
username: capture
.name("username")
2025-05-24 16:19:17 +03:00
.map(|m| m.as_str().to_owned()),
2025-05-24 01:31:42 +03:00
password: capture
.name("password")
2025-05-24 16:19:17 +03:00
.map(|m| m.as_str().to_owned()),
2025-05-24 01:31:42 +03:00
timeout: None,
exit_ip: None,
});
}
2025-04-10 06:55:12 +00:00
}
2025-08-15 20:56:51 +03:00
drop(config);
drop(text);
2025-09-18 11:55:32 +03:00
if new_proxies.is_empty() {
tracing::warn!("{}: no proxies found", source.url);
return Ok(());
}
drop(source);
let mut proxies = proxies.lock();
proxies.extend(new_proxies);
2025-05-08 18:20:19 +03:00
#[cfg(feature = "tui")]
for proto in seen_protocols {
let count = proxies.iter().filter(move |p| p.protocol == proto).count();
2025-07-15 15:13:55 +03:00
drop(tx.send(Event::App(AppEvent::TotalProxies(proto, count))));
2025-05-08 18:20:19 +03:00
}
2025-09-18 11:55:32 +03:00
2025-05-24 01:31:42 +03:00
drop(proxies);
2025-09-18 11:55:32 +03:00
2025-05-08 18:20:19 +03:00
Ok(())
2025-04-10 06:55:12 +00:00
}
2025-04-24 08:57:50 +03:00
pub async fn scrape_all(
2025-04-24 13:44:02 +03:00
config: Arc<Config>,
http_client: reqwest_middleware::ClientWithMiddleware,
token: tokio_util::sync::CancellationToken,
2025-04-27 21:59:32 +03:00
#[cfg(feature = "tui")] tx: tokio::sync::mpsc::UnboundedSender<Event>,
) -> crate::Result<Vec<Proxy>> {
let proxies = Arc::new(parking_lot::Mutex::new(HashSet::new()));
2025-04-10 06:55:12 +00:00
let mut join_set = tokio::task::JoinSet::new();
for (&proto, sources) in &config.scraping.sources {
2025-04-27 21:59:32 +03:00
#[cfg(feature = "tui")]
drop(tx.send(Event::App(AppEvent::SourcesTotal(proto, 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();
2025-05-24 01:31:42 +03:00
let proxies = Arc::clone(&proxies);
let token = token.clone();
let source = Arc::clone(source);
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 {
tokio::select! {
biased;
res = scrape_one(
config,
http_client,
proto,
proxies,
source,
#[cfg(feature = "tui")]
tx,
) => res,
2025-08-08 09:54:24 +03:00
() = token.cancelled() => Ok(()),
}
2025-04-10 06:55:12 +00:00
});
}
}
2025-08-15 17:19:50 +03:00
drop(config);
drop(http_client);
drop(token);
2025-08-15 20:28:39 +03:00
#[cfg(feature = "tui")]
2025-08-15 17:19:50 +03:00
drop(tx);
2025-04-10 06:55:12 +00:00
while let Some(res) = join_set.join_next().await {
2025-09-26 17:13:16 +03:00
res??;
2025-04-10 06:55:12 +00:00
}
2025-05-08 18:20:19 +03:00
2025-08-15 17:19:50 +03:00
drop(join_set);
Ok(Arc::into_inner(proxies)
.ok_or_eyre("failed to unwrap Arc")?
.into_inner()
.into_iter()
.collect())
2025-04-10 06:55:12 +00:00
}