use crate::docker::DockerManager;
use crate::types::{ApiRequest, ApiResponse};
use crate::{AntibotError, Provider, Solution};
use tracing::{debug, error, info};
pub struct Antibot {
http: reqwest::Client,
base_url: String,
max_timeout_ms: u64,
}
impl Antibot {
pub fn builder() -> AntibotBuilder {
AntibotBuilder::default()
}
pub fn connect(base_url: &str) -> Self {
Self {
http: reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(120))
.build()
.expect("failed to build HTTP client"),
base_url: base_url.trim_end_matches('/').to_string(),
max_timeout_ms: 60000,
}
}
pub async fn is_available(&self) -> bool {
match self.http.get(&self.base_url).send().await {
Ok(resp) => resp.status().is_success(),
Err(_) => false,
}
}
pub async fn solve(&self, url: &str) -> Result<Solution, AntibotError> {
self.solve_with_timeout(url, self.max_timeout_ms).await
}
pub async fn solve_with_timeout(
&self,
url: &str,
max_timeout_ms: u64,
) -> Result<Solution, AntibotError> {
let request = ApiRequest {
cmd: "request.get".to_string(),
url: url.to_string(),
max_timeout: max_timeout_ms,
};
info!("solving challenge for {}", url);
let resp = self
.http
.post(format!("{}/v1", self.base_url))
.json(&request)
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status();
let body = resp.text().await.unwrap_or_default();
return Err(AntibotError::UnexpectedResponse(format!(
"HTTP {}: {}",
status,
&body[..body.len().min(500)]
)));
}
let api_resp: ApiResponse = resp.json().await?;
if api_resp.status != "ok" {
error!("challenge failed: {}", api_resp.message);
return Err(AntibotError::ChallengeFailed {
url: url.to_string(),
reason: api_resp.message,
});
}
let solution = api_resp.solution.ok_or_else(|| {
AntibotError::UnexpectedResponse("status ok but no solution returned".into())
})?;
debug!(
"solved with {} cookies, status={}",
solution.cookies.len(),
solution.status
);
info!("solved {} — status {}", url, solution.status);
Ok(solution)
}
pub fn build_http_client(user_agent: &str) -> Result<reqwest::Client, AntibotError> {
use reqwest::header::{ACCEPT, ACCEPT_LANGUAGE, HeaderMap, HeaderValue};
let mut headers = HeaderMap::new();
headers.insert(
ACCEPT,
HeaderValue::from_static(
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
),
);
headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9"));
reqwest::Client::builder()
.user_agent(user_agent)
.default_headers(headers)
.timeout(std::time::Duration::from_secs(30))
.build()
.map_err(AntibotError::Http)
}
}
pub struct AntibotBuilder {
provider: Provider,
port: u16,
auto_start: bool,
container_name: Option<String>,
max_timeout_ms: u64,
health_check_attempts: u32,
}
impl Default for AntibotBuilder {
fn default() -> Self {
Self {
provider: Provider::Byparr,
port: 8191,
auto_start: false,
container_name: None,
max_timeout_ms: 60000,
health_check_attempts: 15,
}
}
}
impl AntibotBuilder {
pub fn provider(mut self, provider: Provider) -> Self {
self.provider = provider;
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn auto_start(mut self, enabled: bool) -> Self {
self.auto_start = enabled;
self
}
pub fn container_name(mut self, name: impl Into<String>) -> Self {
self.container_name = Some(name.into());
self
}
pub fn max_timeout_ms(mut self, ms: u64) -> Self {
self.max_timeout_ms = ms;
self
}
pub fn health_check_attempts(mut self, attempts: u32) -> Self {
self.health_check_attempts = attempts;
self
}
pub async fn build(self) -> Result<Antibot, AntibotError> {
let base_url = format!("http://localhost:{}", self.port);
if self.auto_start {
let mut manager = DockerManager::new(self.provider, self.port);
if let Some(name) = self.container_name {
manager = manager.with_container_name(name);
}
if !manager.is_docker_available().await {
return Err(AntibotError::DockerNotAvailable);
}
manager.start().await?;
manager.wait_healthy(self.health_check_attempts).await?;
}
let client = Antibot {
http: reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(120))
.build()
.expect("failed to build HTTP client"),
base_url,
max_timeout_ms: self.max_timeout_ms,
};
Ok(client)
}
}