1use 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}