1use serde::{Deserialize, Serialize};
2#[derive(Serialize, Deserialize, Debug)]
3pub struct IpInfo {
4 pub ip: String,
5 #[serde(rename = "type")]
6 pub ip_type: String,
7 pub continent_code: String,
8 pub continent_name: String,
9 pub country_code: String,
10 pub country_name: String,
11 pub region_code: String,
12 pub region_name: String,
13 pub city: String,
14 pub zip: String,
15 pub latitude: f64,
16 pub longitude: f64,
17 pub location: Location,
18 pub time_zone: TimeZone,
19 pub currency: Currency,
20 pub connection: Connection,
21}
22
23#[derive(Serialize, Deserialize, Debug)]
24pub struct Location {
25 pub geoname_id: u32,
26 pub capital: String,
27 pub languages: Vec<Language>,
28 pub country_flag: String,
29 pub country_flag_emoji: String,
30 pub country_flag_emoji_unicode: String,
31 pub calling_code: String,
32 pub is_eu: bool,
33}
34
35#[derive(Serialize, Deserialize, Debug)]
36pub struct Language {
37 pub code: String,
38 pub name: String,
39 pub native: String,
40}
41
42#[derive(Serialize, Deserialize, Debug)]
43pub struct TimeZone {
44 pub id: String,
45 pub current_time: String,
46 pub gmt_offset: i32,
47 pub code: String,
48 pub is_daylight_saving: bool,
49}
50
51#[derive(Serialize, Deserialize, Debug)]
52pub struct Currency {
53 pub code: String,
54 pub name: String,
55 pub plural: String,
56 pub symbol: String,
57 pub symbol_native: String,
58}
59
60#[derive(Serialize, Deserialize, Debug)]
61pub struct Connection {
62 pub asn: u32,
63 pub isp: String,
64}
65use std::fmt;
66
67impl fmt::Display for IpInfo {
68 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69 write!(
70 f,
71 "{}, {}, {}, {}",
72 self.city, self.region_name, self.country_name, self.zip
73 )
74 }
75}
76
77#[derive(Clone)]
78#[cfg(feature = "reqwest")]
79pub struct IpApiClient {
80 client: reqwest::Client,
81 base_url: String,
82 access_key: Option<String>,
83}
84#[cfg(feature = "reqwest")]
85extern crate reqwest;
86#[cfg(feature = "reqwest")]
87impl IpApiClient {
88 pub fn new_from_env() -> Self {
89 Self::new(if let Ok(x) = std::env::var("IPAPI_ACCESS_KEY") {
90 Some(x)
91 } else {
92 None
93 })
94 }
95 pub fn new(access_key: Option<String>) -> Self {
96 Self {
97 access_key,
98 client: reqwest::Client::new(),
99 base_url: "https://api.ipapi.com/api".into(),
100 }
101 }
102
103 pub async fn lookup(&self, ip: String) -> Result<IpInfo, reqwest::Error> {
104 let mut params = vec![];
105 if let Some(x) = &self.access_key {
106 params.push(("access_key", x.clone()));
107 }
108 let mut url = reqwest::Url::parse_with_params(&self.base_url, params).unwrap();
109 url.path_segments_mut().unwrap().push(ip.as_str());
110 Ok(self.client.get(url).send().await?.json().await?)
111 }
112}