gwelle 0.1.0

Lightweight Rust client for the Google Trends API
Documentation
use crate::{Result, TrendsError};
use reqwest::header::SET_COOKIE;
use tracing::info;

#[derive(Clone)]
pub struct SessionCookies {
    pub cookie_header: String,
}

impl std::fmt::Debug for SessionCookies {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let cookie_count = self
            .cookie_header
            .split(';')
            .filter(|part| !part.trim().is_empty())
            .count();
        f.debug_struct("SessionCookies")
            .field("cookie_header", &"<redacted>")
            .field("cookie_count", &cookie_count)
            .finish()
    }
}

pub async fn bootstrap_session() -> Result<SessionCookies> {
    info!("Fetching session cookies natively from Google Trends");
    let client = reqwest::Client::builder()
        .user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/121.0.0.0 Safari/537.36")
        .build()?;

    let resp = client
        .get("https://trends.google.com/?geo=US")
        .send()
        .await?;
    resp.error_for_status_ref()?;

    let mut cookies = Vec::new();
    for cookie in resp.headers().get_all(SET_COOKIE) {
        if let Ok(cookie_str) = cookie.to_str() {
            // A cookie string looks like: NID=511=...; expires=...; path=/; domain=.google.com; HttpOnly
            if let Some(cookie_pair) = cookie_str.split(';').next() {
                cookies.push(cookie_pair.to_string());
            }
        }
    }

    if cookies.is_empty() {
        return Err(TrendsError::Session(
            "Failed to acquire any cookies from Google Trends natively. Are you IP blocked?"
                .to_string(),
        ));
    }

    Ok(SessionCookies {
        cookie_header: cookies.join("; "),
    })
}