captchaforge 0.2.39

Captcha detection and solving for Firefox and BiDi-driven browsers. Detection, vendor solver scaffolding, trusted cross-origin click delivery into nested OOPIFs, and stealth personas are implemented and tested; broad live-vendor solve rates are not yet benchmarked.
Documentation
//! One source for captchaforge's plain (non-impersonating) HTTP clients.
//!
//! The CAPTCHA backends/solvers each talk to a *local* service (Ollama,
//! Whisper, Tesseract sidecar) or a vendor solver API, not to the target
//! site, so they want a plain timed client, not the browser-impersonating
//! [`guise`] client. The same `reqwest::Client::builder().timeout(d).build()`
//! block was copy-pasted across `backends`, `solver::{audio,third_party,
//! vlm}`, `stt`, and the CLI. Centralizing it here gives one place to evolve
//! connection policy (connect-timeout, pooling, a default UA) for all of them.

use std::time::Duration;

/// A plain reqwest client with a total-request `timeout` and no browser
/// impersonation. Callers that can return an error own their own handling
/// (`?`, `match`, `.ok()?`). Callers in an *infallible* constructor must use
/// [`timed_client_or_panic`]. NOT `.unwrap_or_else(|_| reqwest::Client::new())`.
pub(crate) fn timed_client(timeout: Duration) -> reqwest::Result<reqwest::Client> {
    reqwest::Client::builder()
        .timeout(timeout)
        .no_gzip()
        .no_brotli()
        .build()
}

/// [`timed_client`] for the handful of infallible solver constructors that
/// cannot return a `Result`.
///
/// Law 10: the historical `.unwrap_or_else(|_| reqwest::Client::new())` at these
/// sites was a SILENT fallback *and* a footgun, it dropped the caller's
/// `timeout` (returning an un-timed client that can hang a solve forever), and
/// `reqwest::Client::new()` itself panics on the very same build failure, just
/// with a less useful message. A reqwest client only fails to build when the TLS
/// backend can't initialise: a fatal environment problem, not something to paper
/// over with a differently-configured client. So fail LOUD, with the cause and
/// the fix, and never hand back a client whose timeout was silently discarded.
pub(crate) fn timed_client_or_panic(timeout: Duration) -> reqwest::Client {
    timed_client(timeout).unwrap_or_else(|e| {
        panic!(
            "captchaforge: could not build an HTTP client (timeout={timeout:?}): {e}. \
             reqwest's TLS backend failed to initialise, check the system TLS / rustls \
             build, not your captcha configuration."
        )
    })
}