use crate::UrlStatus;
use anyhow::{Context, Result};
use reqwest::{Url, header::CONTENT_TYPE};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use uuid::Uuid;
#[derive(Serialize, Deserialize, Debug)]
pub(crate) enum FalreSolverrCommand {
Get {
url: String,
timeout: u32,
session: Uuid,
},
CreateSession(
Uuid,
),
DestroySession(
Uuid,
),
}
impl FalreSolverrCommand {
pub fn to_json(&self) -> serde_json::Value {
match self {
FalreSolverrCommand::Get {
url,
timeout,
session,
} => serde_json::json!({
"cmd": "request.get",
"url": &url,
"maxTimeout": timeout * 1000, "session": &session
}),
FalreSolverrCommand::CreateSession(session) => serde_json::json!({
"cmd": "sessions.create",
"session": &session,
}),
FalreSolverrCommand::DestroySession(session) => serde_json::json!({
"cmd": "sessions.destroy",
"session": &session,
}),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
struct FlareSolverrSolution {
url: String,
status: Option<u16>,
}
#[derive(Serialize, Deserialize, Debug)]
struct FlareSolverrResponse {
solution: FlareSolverrSolution,
}
impl From<FlareSolverrResponse> for UrlStatus {
fn from(response: FlareSolverrResponse) -> Self {
UrlStatus {
url: response.solution.url,
status: response.solution.status,
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct FlareSolverrClient {
client: reqwest::Client,
session: Uuid,
timeout: u32,
url: Url,
}
impl FlareSolverrClient {
pub(crate) async fn new(
client: reqwest::Client,
timeout: u32,
url: impl AsRef<str>,
) -> Result<Self> {
let url = Url::parse(url.as_ref())?.join("v1")?;
let session = Uuid::new_v4();
let json = FalreSolverrCommand::CreateSession(session).to_json();
client
.post(url.as_str())
.json(&json)
.send()
.await
.with_context(|| {
format!(
"Failed to create a FlareSolverr session {:?} at {:?}",
session,
url.as_str()
)
})?;
Ok(Self {
client,
timeout,
session,
url,
})
}
pub(crate) async fn check(&self, url: impl Into<String>) -> UrlStatus {
let url = url.into();
let json = FalreSolverrCommand::Get {
url: url.clone(),
timeout: self.timeout,
session: self.session,
}
.to_json();
let status = self
.client
.post(self.url.as_str())
.timeout(Duration::from_secs(self.timeout.into()))
.header(CONTENT_TYPE, "application/json")
.json(&json)
.send()
.await;
match status {
Ok(response) => match response.json::<FlareSolverrResponse>().await {
Ok(response) => UrlStatus::from(response),
Err(_) => UrlStatus { url, status: None },
},
Err(_) => UrlStatus { url, status: None },
}
}
pub(crate) async fn close(self) -> Result<()> {
let json = FalreSolverrCommand::DestroySession(self.session).to_json();
self.client
.post(self.url.as_str())
.json(&json)
.send()
.await
.with_context(|| {
format!(
"Failed to destroy FlareSolverr session {:?} at {:?}",
self.session, self.url
)
})?;
Ok(())
}
}