use serde::ser::{Serialize, SerializeMap, Serializer};
use serde::Serialize as DeriveSerialize;
use super::{require_non_empty, CaptchaRequest};
use crate::proxy::ProxyInfo;
use crate::timeouts::GetResultTimeouts;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HuntMode {
Fingerprint,
Token(String),
WidgetUrl(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HuntMetadata {
pub api_get_lib: String,
pub mode: HuntMode,
}
impl HuntMetadata {
pub fn fingerprint(api_get_lib: impl Into<String>) -> Self {
Self {
api_get_lib: api_get_lib.into(),
mode: HuntMode::Fingerprint,
}
}
pub fn with_token(api_get_lib: impl Into<String>, data: impl Into<String>) -> Self {
Self {
api_get_lib: api_get_lib.into(),
mode: HuntMode::Token(data.into()),
}
}
pub fn with_widget_url(api_get_lib: impl Into<String>, widget_url: impl Into<String>) -> Self {
Self {
api_get_lib: api_get_lib.into(),
mode: HuntMode::WidgetUrl(widget_url.into()),
}
}
}
impl Serialize for HuntMetadata {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let len = if self.mode == HuntMode::Fingerprint {
1
} else {
2
};
let mut map = serializer.serialize_map(Some(len))?;
map.serialize_entry("apiGetLib", &self.api_get_lib)?;
match &self.mode {
HuntMode::Fingerprint => {}
HuntMode::Token(data) => map.serialize_entry("data", data)?,
HuntMode::WidgetUrl(url) => map.serialize_entry("widgetUrl", url)?,
}
map.end()
}
}
#[derive(Debug, Clone, DeriveSerialize)]
pub struct HuntCustomTaskRequest {
#[serde(rename = "websiteURL")]
pub website_url: String,
pub metadata: HuntMetadata,
#[serde(flatten)]
pub proxy: ProxyInfo,
#[serde(rename = "userAgent", skip_serializing_if = "Option::is_none")]
pub user_agent: Option<String>,
}
impl HuntCustomTaskRequest {
pub fn new(website_url: impl Into<String>, metadata: HuntMetadata, proxy: ProxyInfo) -> Self {
Self {
website_url: website_url.into(),
metadata,
proxy,
user_agent: None,
}
}
pub fn with_user_agent(mut self, value: impl Into<String>) -> Self {
self.user_agent = Some(value.into());
self
}
}
impl CaptchaRequest for HuntCustomTaskRequest {
const TASK_TYPE: &'static str = "CustomTask";
const CLASS: Option<&'static str> = Some("HUNT");
fn timeouts(&self) -> GetResultTimeouts {
GetResultTimeouts::secs(1.0, 0.0, 3.0, 180.0)
}
fn validate(&self) -> crate::error::Result<()> {
require_non_empty("websiteURL", &self.website_url)?;
require_non_empty("metadata.apiGetLib", &self.metadata.api_get_lib)?;
match &self.metadata.mode {
HuntMode::Fingerprint => {}
HuntMode::Token(data) => require_non_empty("metadata.data", data)?,
HuntMode::WidgetUrl(url) => require_non_empty("metadata.widgetUrl", url)?,
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::proxy::ProxyType;
use serde_json::json;
fn proxy() -> ProxyInfo {
ProxyInfo::new(ProxyType::Http, "1.2.3.4", 8080).with_auth("u", "p")
}
#[test]
fn minimal_fingerprint_task() {
let task = HuntCustomTaskRequest::new(
"https://example.com",
HuntMetadata::fingerprint("https://example.com/api.js"),
proxy(),
)
.to_task()
.unwrap();
assert_eq!(
task,
json!({
"type": "CustomTask", "class": "HUNT", "websiteURL": "https://example.com",
"metadata": {"apiGetLib": "https://example.com/api.js"},
"proxyType": "http", "proxyAddress": "1.2.3.4", "proxyPort": 8080,
"proxyLogin": "u", "proxyPassword": "p"
})
);
}
#[test]
fn full_token_task() {
let task = HuntCustomTaskRequest::new(
"https://example.com",
HuntMetadata::with_token("L", "TOKEN"),
proxy(),
)
.with_user_agent("UA")
.to_task()
.unwrap();
assert_eq!(task["metadata"], json!({"apiGetLib": "L", "data": "TOKEN"}));
assert_eq!(task["userAgent"], "UA");
}
#[test]
fn widget_url_task() {
let task = HuntCustomTaskRequest::new(
"https://example.com",
HuntMetadata::with_widget_url("L", "https://c.example.com/widget?hash=h"),
proxy(),
)
.to_task()
.unwrap();
assert_eq!(
task["metadata"],
json!({"apiGetLib": "L", "widgetUrl": "https://c.example.com/widget?hash=h"})
);
}
#[test]
fn empty_required_strings_are_rejected() {
let err = HuntCustomTaskRequest::new("", HuntMetadata::fingerprint("L"), proxy())
.to_task()
.unwrap_err();
assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("websiteURL")));
let err = HuntCustomTaskRequest::new(
"https://example.com",
HuntMetadata::fingerprint(""),
proxy(),
)
.to_task()
.unwrap_err();
assert!(
matches!(err, crate::error::Error::Validation(ref m) if m.contains("metadata.apiGetLib"))
);
let err = HuntCustomTaskRequest::new(
"https://example.com",
HuntMetadata::with_token("L", ""),
proxy(),
)
.to_task()
.unwrap_err();
assert!(
matches!(err, crate::error::Error::Validation(ref m) if m.contains("metadata.data"))
);
let err = HuntCustomTaskRequest::new(
"https://example.com",
HuntMetadata::with_widget_url("L", ""),
proxy(),
)
.to_task()
.unwrap_err();
assert!(
matches!(err, crate::error::Error::Validation(ref m) if m.contains("metadata.widgetUrl"))
);
}
}