1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! Pluggable antibot strategy API.
//!
//! [`AntibotStrategy`] is a hook pair: [`AntibotStrategy::pre_request`] fires
//! before the tower-stack fetch (useful for header injection, token rotation,
//! CAPTCHA pre-warming), and [`AntibotStrategy::post_response`] fires after WAF
//! classification to produce a [`Decision`] that the dispatch loop acts on.
//!
//! # Wiring into the engine
//!
//! Attach a strategy via [`crate::types::DispatchProfile::antibot_strategy`].
//! When `None`, the engine's built-in WAF-signal → escalation logic applies
//! unchanged (identical to pre-Cluster-5 behaviour).
//!
//! # Example
//!
//! ```
//! use crawlberg::{AntibotStrategy, AntibotError, Decision};
//! use crawlberg::http::HttpResponse;
//! use crawlberg::WafSignal;
//! use async_trait::async_trait;
//! use std::time::Duration;
//!
//! #[derive(Debug)]
//! struct VendorAwareStrategy;
//!
//! #[async_trait]
//! impl AntibotStrategy for VendorAwareStrategy {
//! async fn pre_request(&self, _url: &str) -> Result<(), AntibotError> {
//! Ok(())
//! }
//!
//! async fn post_response(
//! &self,
//! _response: &HttpResponse,
//! waf_signal: Option<&WafSignal>,
//! ) -> Decision {
//! match waf_signal {
//! Some(signal) if signal.vendor == "datadome" => {
//! Decision::Retry { backoff: Duration::from_secs(5) }
//! }
//! Some(_) => Decision::EscalateBrowser,
//! None => Decision::Accept,
//! }
//! }
//! }
//! ```
use fmt;
use Arc;
use Duration;
use async_trait;
use crateHttpResponse;
use crateWafSignal;
/// Errors from [`AntibotStrategy`] hook invocations.
/// What the dispatch loop does after the antibot `post_response` hook runs.
///
/// Returned by [`AntibotStrategy::post_response`]. The engine translates each
/// variant into its internal `RetryDirective` before the retry-policy sees it.
/// Pluggable antibot hook pair.
///
/// Implement this trait to intercept every request/response pair in the
/// dispatch loop. Hook code should be fast — it runs on the same Tokio task as
/// the fetch.
///
/// # Object safety
///
/// `AntibotStrategy` is object-safe. Wrap in `Arc<dyn AntibotStrategy>` and
/// attach to [`crate::types::DispatchProfile::antibot_strategy`].
/// Convenience alias.
pub type DynAntibotStrategy = ;
/// Default [`AntibotStrategy`] that mirrors the pre-Cluster-5 engine behaviour.
///
/// - `pre_request` is a no-op.
/// - `post_response` returns [`Decision::EscalateBrowser`] when a WAF signal is
/// present, and [`Decision::Accept`] otherwise — identical to what the engine
/// previously did without a pluggable strategy.
///
/// Use this as a base for custom strategies that want to add vendor-specific
/// backoff rules without reimplementing the default escalation logic.
;