ripinfo 0.1.1

ripinfo is a tool to gather ip information using the ipinfo.io API
Documentation
use anyhow::{anyhow, bail};
use flate2::bufread::GzDecoder;
use reqwest::{Client, StatusCode};
use std::io::prelude::*;

use crate::{
    config::RipInfoConfig,
    ip_response::{IpData, IpResponseBusiness},
};

#[derive(Debug)]
pub struct RipInfo {
    pub client: Client,
}

impl RipInfo {
    pub fn new(user_agent: UserAgent) -> anyhow::Result<Self> {
        let client = Client::builder()
            .user_agent(user_agent.to_string())
            .build()?;

        Ok(Self { client })
    }

    pub async fn fetch_api_data(&self, ip: &str, config: &RipInfoConfig) -> anyhow::Result<IpData> {
        if config.use_token && config.token.is_none() {
            return Err(anyhow!("Missing token inside ripinfo_config.json"));
        }

        let data: IpData = if config.use_token {
            let token = config.token.clone().unwrap();
            let url = format!("https://ipinfo.io/{}/json", ip);

            let response = self
                .client
                .get(url)
                .header("Referer", "https://ipinfo.io/")
                .header("Accept", "application/json")
                .header("Authorization", format!("Bearer {}", &token))
                .send()
                .await?;
            let text = response.text().await?;
            serde_json::from_str::<IpData>(&text)?
        } else {
            let url = format!("https://ipinfo.io/widget/demo/{}", &ip);
            let cookie_val = "flash=; jwt-express=eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo4NzYwOTQsImVtYWlsIjoiR2VyczIwMTdAb3V0bG9vay5jb20iLCJjcmVhdGVkIjoiYSBmZXcgc2Vjb25kcyBhZ28oMjAyMy0wMy0wNFQxNzoxNzozOC4zMzJaKSIsInN0cmlwZV9pZCI6bnVsbCwiaWF0IjoxNjc3OTUwMjU4LCJleHAiOjE2ODA1NDIyNTh9.AXU025TfoTZ3WGHWsV_HSiR_Gvc7H9Q2Gqt0jKuDamxWp8PsFLgrcWPCxfKHvIReTQaDnUD4135NBX2rYBout_m7AHCBTzbeaPWpsDFHbO19tCzOBdU0tYxLbtZLdWxEUHUHMctvUXlktpsm4aqUAdgL75_6otp5a95iQ8HD21MwIT3e";

            let response = self
                .client
                .get(url)
                .header("Cookie", cookie_val)
                .header("Referer", "https://ipinfo.io/")
                .header("Accept", "application/json")
                .header("Accept-Encoding", "gzip, deflate, br")
                .header("Sec-Fetch-Mode", "cors")
                .send()
                .await?;

            let status_code = response.status();
            if status_code != StatusCode::OK {
                // shortcut for: return Err(anyhow!())
                bail!("Error: {}", status_code);
            }

            // decompress gzip data
            let mut text = String::new();
            let bytes = response.bytes().await?;
            let mut decoder = GzDecoder::new(&bytes[..]);
            decoder.read_to_string(&mut text)?;

            let ip_b_response = serde_json::from_str::<IpResponseBusiness>(&text)?;

            if ip_b_response.error.is_some() {
                bail!(ip_b_response.error.unwrap());
            }

            match ip_b_response.data {
                Some(ip_data) => ip_data,
                None => bail!(ip_b_response.error.unwrap()),
            }
        };

        Ok(data)
    }
}

#[derive(Debug)]
pub enum UserAgent {
    Chrome,
    Firefox,
    Edge,
}

impl std::fmt::Display for UserAgent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match self {
            UserAgent::Chrome => String::from("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"),
            UserAgent::Firefox => String::from("Mozilla/5.0 (Windows NT 10.0; rv:100.0) Gecko/20100101 Firefox/100.0"),
            UserAgent::Edge => String::from("Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1474.0"),
        };

        write!(f, "{}", s)
    }
}

impl Default for UserAgent {
    fn default() -> Self {
        UserAgent::Chrome
    }
}