mod flaresolverr;
use crate::flaresolverr::FlareSolverrClient;
use anyhow::Result;
use async_stream::stream;
use bon::bon;
use futures::Stream;
use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use std::time::Duration;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UrlStatus {
pub url: String,
pub status: Option<u16>,
}
#[derive(Clone, Debug)]
pub struct LinkChecker {
client: Client,
flaresolverr: Option<FlareSolverrClient>,
}
impl Default for LinkChecker {
fn default() -> Self {
Self {
client: Client::builder().timeout(DEFAULT_TIMEOUT).build().unwrap(),
flaresolverr: None,
}
}
}
#[bon]
impl LinkChecker {
#[builder]
pub async fn new(
client: Option<Client>,
flaresolverr: Option<String>,
) -> Result<Self> {
let client = client
.map(Ok)
.unwrap_or_else(|| Client::builder().timeout(DEFAULT_TIMEOUT).build())?;
let flaresolverr = if let Some(url) = flaresolverr {
Some(FlareSolverrClient::new(client.clone(), 60, url).await?)
} else {
None
};
Ok(Self {
client,
flaresolverr,
})
}
async fn checker(
url: String,
client: Client,
flaresolverr: Option<FlareSolverrClient>,
) -> UrlStatus {
let result = &client.get(&url).send().await;
match result {
Ok(response) => {
let code = response.status();
if code == StatusCode::FORBIDDEN {
if let Some(solver) = flaresolverr {
solver.check(&url).await
} else {
UrlStatus {
url,
status: Some(code.as_u16()),
}
}
} else {
UrlStatus {
url,
status: Some(code.as_u16()),
}
}
}
Err(_) => UrlStatus { url, status: None },
}
}
pub async fn check(&self, url: impl Into<String>) -> UrlStatus {
Self::checker(url.into(), self.client.clone(), self.flaresolverr.clone()).await
}
pub async fn check_all<I, S>(&self, urls: I) -> impl Stream<Item = UrlStatus>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let mut tasks = tokio::task::JoinSet::new();
for url in urls {
tasks.spawn(Self::checker(
url.into(),
self.client.clone(),
self.flaresolverr.clone(),
));
}
stream! {
while let Some(task) = tasks.join_next().await {
if let Ok(status) = task {
yield status;
}
}
}
}
pub async fn close(self) -> Result<()> {
if let Some(solverr) = self.flaresolverr {
solverr.close().await?
}
Ok(())
}
}