ghfetch 0.1.0

GitHub stats in the terminal, neofetch-style
use anyhow::Result;
use reqwest::header::{ACCEPT, AUTHORIZATION, USER_AGENT};

pub struct GhClient {
    pub http: reqwest::Client,
    pub token: Option<String>,
    pub verbose: bool,
}

impl GhClient {
    pub fn new(token: Option<String>, verbose: bool) -> Result<Self> {
        let http = reqwest::Client::builder().user_agent("ghfetch").build()?;
        Ok(Self {
            http,
            token,
            verbose,
        })
    }

    pub fn is_authenticated(&self) -> bool {
        self.token.is_some()
    }

    pub fn auth_headers(&self) -> Vec<(reqwest::header::HeaderName, String)> {
        let mut headers = vec![
            (ACCEPT, "application/vnd.github+json".to_string()),
            (USER_AGENT, "ghfetch".to_string()),
        ];
        if let Some(ref t) = self.token {
            headers.push((AUTHORIZATION, format!("Bearer {t}")));
        }
        headers
    }

    pub async fn rest_get(&self, url: &str) -> Result<reqwest::Response> {
        let mut req = self.http.get(url);
        for (k, v) in self.auth_headers() {
            req = req.header(k, v);
        }
        let resp = req.send().await?;
        if self.verbose {
            eprintln!("[api] GET {}{}", url, resp.status());
        }
        Ok(resp)
    }

    pub async fn graphql(
        &self,
        query: &str,
        variables: &serde_json::Value,
    ) -> Result<serde_json::Value> {
        let token = self
            .token
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("GraphQL requires authentication"))?;

        let body = serde_json::json!({
            "query": query,
            "variables": variables,
        });

        let resp = self
            .http
            .post("https://api.github.com/graphql")
            .header(AUTHORIZATION, format!("Bearer {token}"))
            .header(USER_AGENT, "ghfetch")
            .json(&body)
            .send()
            .await?;

        if self.verbose {
            eprintln!("[api] POST /graphql → {}", resp.status());
        }

        let data: serde_json::Value = resp.json().await?;
        if let Some(errors) = data.get("errors") {
            anyhow::bail!("GraphQL errors: {errors}");
        }
        Ok(data)
    }
}