ip_lookup/
lookup.rs

1/* src/lookup.rs */
2
3use serde::Serialize;
4use crate::ip::get_public_ip_addr;
5
6#[derive(Serialize, Debug, Default)]
7pub struct CountryInfo {
8    pub city: Option<String>,
9    pub code: Option<String>,
10    pub zip: Option<String>,
11    pub timezone: Option<String>,
12}
13
14#[derive(Serialize, Debug, Default)]
15pub struct LocationInfo {
16    pub latitude: Option<f64>,
17    pub longitude: Option<f64>,
18}
19
20#[derive(Serialize, Debug, Default)]
21pub struct ConnectionInfo {
22    pub is_proxy: Option<bool>,
23    pub is_tor: Option<bool>,
24    pub is_crawler: Option<bool>,
25    pub is_datacenter: Option<bool>,
26    pub is_vpn: Option<bool>,
27}
28
29#[derive(Serialize, Debug, Default)]
30pub struct NetworkInfo {
31    pub ip: Option<String>,
32    pub isp: Option<String>,
33    pub org: Option<String>,
34    pub asn: Option<String>,
35}
36
37#[derive(Serialize, Debug, Default)]
38pub struct LookupResult {
39    pub country: CountryInfo,
40    pub location: LocationInfo,
41    pub connection: ConnectionInfo,
42    pub network: NetworkInfo,
43}
44
45pub fn lookup_with_ipinfo() -> Option<LookupResult> {
46    let url = "https://ipinfo.io/json";
47    let resp = reqwest::blocking::get(url).ok()?.json::<serde_json::Value>().ok()?;
48
49    let loc_str = resp.get("loc").and_then(|v| v.as_str()).unwrap_or("");
50    let mut split = loc_str.split(',');
51    let latitude = split.next().and_then(|v| v.parse().ok());
52    let longitude = split.next().and_then(|v| v.parse().ok());
53
54    Some(LookupResult {
55        country: CountryInfo {
56            city: resp.get("city").and_then(|v| v.as_str()).map(String::from),
57            code: resp.get("country").and_then(|v| v.as_str()).map(String::from),
58            zip: resp.get("postal").and_then(|v| v.as_str()).map(String::from),
59            timezone: resp.get("timezone").and_then(|v| v.as_str()).map(String::from),
60        },
61        location: LocationInfo { latitude, longitude },
62        connection: ConnectionInfo::default(),
63        network: NetworkInfo {
64            ip: resp.get("ip").and_then(|v| v.as_str()).map(String::from),
65            isp: resp.get("org").and_then(|v| v.as_str()).map(String::from),
66            org: resp.get("org").and_then(|v| v.as_str()).map(String::from),
67            asn: None,
68        },
69    })
70}
71
72pub fn lookup_with_ipapi() -> Option<LookupResult> {
73    let url = "http://ip-api.com/json/";
74    let resp = reqwest::blocking::get(url).ok()?.json::<serde_json::Value>().ok()?;
75
76    if resp.get("status")?.as_str()? != "success" {
77        return None;
78    }
79
80    Some(LookupResult {
81        country: CountryInfo {
82            city: resp.get("city").and_then(|v| v.as_str()).map(String::from),
83            code: resp.get("countryCode").and_then(|v| v.as_str()).map(String::from),
84            zip: resp.get("zip").and_then(|v| v.as_str()).map(String::from),
85            timezone: resp.get("timezone").and_then(|v| v.as_str()).map(String::from),
86        },
87        location: LocationInfo {
88            latitude: resp.get("lat").and_then(|v| v.as_f64()),
89            longitude: resp.get("lon").and_then(|v| v.as_f64()),
90        },
91        connection: ConnectionInfo::default(),
92        network: NetworkInfo {
93            ip: resp.get("query").and_then(|v| v.as_str()).map(String::from),
94            isp: resp.get("isp").and_then(|v| v.as_str()).map(String::from),
95            org: resp.get("org").and_then(|v| v.as_str()).map(String::from),
96            asn: resp.get("as").and_then(|v| v.as_str()).map(String::from),
97        },
98    })
99}
100
101pub fn lookup_with_ipsb() -> Option<LookupResult> {
102    let url = "https://api.ip.sb/geoip";
103    let resp = reqwest::blocking::get(url).ok()?.json::<serde_json::Value>().ok()?;
104
105    Some(LookupResult {
106        country: CountryInfo {
107            city: resp.get("city").and_then(|v| v.as_str()).map(String::from),
108            code: resp.get("country_code").and_then(|v| v.as_str()).map(String::from),
109            zip: resp.get("postal_code").and_then(|v| v.as_str()).map(String::from),
110            timezone: resp.get("timezone").and_then(|v| v.as_str()).map(String::from),
111        },
112        location: LocationInfo {
113            latitude: resp.get("latitude").and_then(|v| v.as_f64()),
114            longitude: resp.get("longitude").and_then(|v| v.as_f64()),
115        },
116        connection: ConnectionInfo::default(),
117        network: NetworkInfo {
118            ip: resp.get("ip").and_then(|v| v.as_str()).map(String::from),
119            isp: resp.get("isp").and_then(|v| v.as_str()).map(String::from),
120            org: resp.get("organization").and_then(|v| v.as_str()).map(String::from),
121            asn: resp.get("asn").map(|v| v.to_string()),
122        },
123    })
124}
125
126pub fn lookup_with_ipapiio() -> Option<LookupResult> {
127    let url = "https://ip-api.io/api/v1/ip";
128    let resp = reqwest::blocking::get(url).ok()?.json::<serde_json::Value>().ok()?;
129
130    let location = resp.get("location")?;
131    let factors = resp.get("suspicious_factors").unwrap_or(&serde_json::Value::Null);
132
133    Some(LookupResult {
134        country: CountryInfo {
135            city: location.get("city").and_then(|v| v.as_str()).map(String::from),
136            code: location.get("country_code").and_then(|v| v.as_str()).map(String::from),
137            zip: location.get("zip").and_then(|v| v.as_str()).map(String::from),
138            timezone: location.get("timezone").and_then(|v| v.as_str()).map(String::from),
139        },
140        location: LocationInfo {
141            latitude: location.get("latitude").and_then(|v| v.as_f64()),
142            longitude: location.get("longitude").and_then(|v| v.as_f64()),
143        },
144        connection: ConnectionInfo {
145            is_proxy: factors.get("is_proxy").and_then(|v| v.as_bool()),
146            is_tor: factors.get("is_tor_node").and_then(|v| v.as_bool()),
147            is_crawler: factors.get("is_crawler").and_then(|v| v.as_bool()),
148            is_datacenter: factors.get("is_datacenter").and_then(|v| v.as_bool()),
149            is_vpn: factors.get("is_vpn").and_then(|v| v.as_bool()),
150        },
151        network: NetworkInfo {
152            ip: resp.get("ip").and_then(|v| v.as_str()).map(String::from),
153            isp: None,
154            org: None,
155            asn: None,
156        },
157    })
158}
159
160pub fn lookup_with_apipcc() -> Option<LookupResult> {
161    let ip = get_public_ip_addr()?;
162    let url = format!("https://apip.cc/api-json/{}", ip);
163    let resp = reqwest::blocking::get(&url).ok()?.json::<serde_json::Value>().ok()?;
164
165    if resp.get("status")?.as_str()? != "success" {
166        return None;
167    }
168
169    Some(LookupResult {
170        country: CountryInfo {
171            city: resp.get("City").and_then(|v| v.as_str()).map(String::from),
172            code: resp.get("CountryCode").and_then(|v| v.as_str()).map(String::from),
173            zip: resp.get("Postal").and_then(|v| v.as_str()).map(String::from),
174            timezone: resp.get("TimeZone").and_then(|v| v.as_str()).map(String::from),
175        },
176        location: LocationInfo {
177            latitude: resp.get("Latitude").and_then(|v| v.as_str()).and_then(|v| v.parse().ok()),
178            longitude: resp.get("Longitude").and_then(|v| v.as_str()).and_then(|v| v.parse().ok()),
179        },
180        connection: ConnectionInfo::default(),
181        network: NetworkInfo {
182            ip: resp.get("query").and_then(|v| v.as_str()).map(String::from),
183            isp: None,
184            org: resp.get("org").and_then(|v| v.as_str()).map(String::from),
185            asn: resp.get("asn").and_then(|v| v.as_str()).map(String::from),
186        },
187    })
188}
189
190pub fn lookup_with_ipapiis() -> Option<LookupResult> {
191    let url = "https://api.ipapi.is";
192    let resp = reqwest::blocking::get(url).ok()?.json::<serde_json::Value>().ok()?;
193
194    let location = resp.get("location")?;
195    let asn = resp.get("asn")?;
196    let company = resp.get("company");
197
198    Some(LookupResult {
199        country: CountryInfo {
200            city: location.get("city").and_then(|v| v.as_str()).map(String::from),
201            code: location.get("country_code").and_then(|v| v.as_str()).map(String::from),
202            zip: location.get("zip").and_then(|v| v.as_str()).map(String::from),
203            timezone: location.get("timezone").and_then(|v| v.as_str()).map(String::from),
204        },
205        location: LocationInfo {
206            latitude: location.get("latitude").and_then(|v| v.as_f64()),
207            longitude: location.get("longitude").and_then(|v| v.as_f64()),
208        },
209        connection: ConnectionInfo::default(),
210        network: NetworkInfo {
211            ip: resp.get("ip").and_then(|v| v.as_str()).map(String::from),
212            isp: company
213                .and_then(|v| v.get("name"))
214                .or_else(|| resp.get("datacenter").and_then(|v| v.get("datacenter")))
215                .and_then(|v| v.as_str())
216                .map(String::from),
217            org: company
218                .and_then(|v| v.get("name"))
219                .and_then(|v| v.as_str())
220                .map(String::from),
221            asn: asn.get("asn").map(|v| v.to_string()),
222        },
223    })
224}
225
226pub fn lookup_with_geolocated() -> Option<LookupResult> {
227    let url = "https://us-west-1.geolocated.io/where-am-i";
228    let resp = reqwest::blocking::get(url).ok()?.json::<serde_json::Value>().ok()?;
229
230    let connection = resp.get("connection");
231    let city = resp.get("cityName").or_else(|| resp.pointer("/country/capital"));
232
233    Some(LookupResult {
234        country: CountryInfo {
235            city: city.and_then(|v| v.as_str()).map(String::from),
236            code: resp.get("countryCode").and_then(|v| v.as_str()).map(String::from),
237            zip: resp.get("zipCode").and_then(|v| v.as_str()).map(String::from),
238            timezone: resp.get("timeZone").or_else(|| resp.pointer("/timeZoneInfo/zoneName")).and_then(|v| v.as_str()).map(String::from),
239        },
240        location: LocationInfo {
241            latitude: resp.get("latitude").and_then(|v| v.as_f64()),
242            longitude: resp.get("longitude").and_then(|v| v.as_f64()),
243        },
244        connection: ConnectionInfo::default(),
245        network: NetworkInfo {
246            ip: resp.get("ip").and_then(|v| v.as_str()).map(String::from),
247            isp: connection.and_then(|v| v.get("isp")).and_then(|v| v.as_str()).map(String::from),
248            org: connection.and_then(|v| v.get("organization")).and_then(|v| v.as_str()).map(String::from),
249            asn: connection.and_then(|v| v.get("asn")).map(|v| v.to_string()),
250        },
251    })
252}
253
254pub fn lookup_with_iplocationapi() -> Option<LookupResult> {
255    let url = "https://api.iplocationapi.net/api/self-lookup";
256    let resp = reqwest::blocking::get(url).ok()?.json::<serde_json::Value>().ok()?;
257
258    let timezone = resp.get("timezone");
259    let asn = resp.get("asn");
260    let connection = resp.get("connection");
261
262    Some(LookupResult {
263        country: CountryInfo {
264            city: resp.get("city").and_then(|v| v.as_str()).map(String::from),
265            code: resp.get("country_code").and_then(|v| v.as_str()).map(String::from),
266            zip: resp.get("postal_code").and_then(|v| v.as_str()).map(String::from),
267            timezone: timezone.and_then(|v| v.get("id")).and_then(|v| v.as_str()).map(String::from),
268        },
269        location: LocationInfo {
270            latitude: resp.get("latitude").and_then(|v| v.as_f64()),
271            longitude: resp.get("longitude").and_then(|v| v.as_f64()),
272        },
273        connection: ConnectionInfo::default(),
274        network: NetworkInfo {
275            ip: resp.get("ip").and_then(|v| v.as_str()).map(String::from),
276            isp: connection.and_then(|v| v.get("isp")).and_then(|v| v.as_str()).map(String::from),
277            org: connection.and_then(|v| v.get("organization")).and_then(|v| v.as_str()).map(String::from),
278            asn: asn.and_then(|v| v.get("number")).map(|v| v.to_string()),
279        },
280    })
281}