rustenium-identity 0.1.7

A versatile stealth overlay for rustenium
Documentation
use crate::error::IdentityError;

/// Resolve timezone: use the explicit override if provided,
/// otherwise query ip-api.com to get the timezone for the proxy IP.
pub async fn resolve_timezone(
    explicit: Option<&str>,
    proxy: Option<&str>,
) -> Result<String, IdentityError> {
    if let Some(tz) = explicit {
        return Ok(tz.to_string());
    }

    fetch_timezone_from_ipapi(proxy).await
}

/// Query ip-api.com to get the IANA timezone for the current IP
/// (optionally routed through a proxy).
async fn fetch_timezone_from_ipapi(proxy: Option<&str>) -> Result<String, IdentityError> {
    let mut builder = reqwest::Client::builder();
    if let Some(proxy_url) = proxy {
        builder = builder.proxy(
            reqwest::Proxy::all(proxy_url)
                .map_err(|e| IdentityError::TimezoneError(format!("invalid proxy: {e}")))?,
        );
    }
    let client = builder
        .build()
        .map_err(|e| IdentityError::TimezoneError(format!("http client error: {e}")))?;

    let resp: serde_json::Value = client
        .get("http://ip-api.com/json/?fields=timezone")
        .send()
        .await
        .map_err(|e| IdentityError::TimezoneError(format!("ip-api request failed: {e}")))?
        .json()
        .await
        .map_err(|e| IdentityError::TimezoneError(format!("ip-api parse failed: {e}")))?;

    resp.get("timezone")
        .and_then(|v| v.as_str())
        .map(|s| s.to_string())
        .ok_or_else(|| IdentityError::TimezoneError("ip-api returned no timezone".into()))
}