ipspot 0.3__py3-none-any.whl → 0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ipspot/cli.py CHANGED
@@ -4,7 +4,7 @@ import argparse
4
4
  from typing import Union, Tuple
5
5
  from art import tprint
6
6
  from .ipv4 import get_public_ipv4, get_private_ipv4
7
- from .utils import filter_parameter
7
+ from .utils import _filter_parameter
8
8
  from .params import IPv4API, PARAMETERS_NAME_MAP
9
9
  from .params import IPSPOT_OVERVIEW, IPSPOT_REPO, IPSPOT_VERSION
10
10
 
@@ -17,14 +17,17 @@ def ipspot_info() -> None: # pragma: no cover
17
17
  print("Repo : " + IPSPOT_REPO)
18
18
 
19
19
 
20
- def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO, geo: bool=False,
21
- timeout: Union[float, Tuple[float, float]]=5) -> None: # pragma: no cover
20
+ def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE, geo: bool=False,
21
+ timeout: Union[float, Tuple[float, float]]=5,
22
+ max_retries: int = 0, retry_delay: float = 1.0) -> None: # pragma: no cover
22
23
  """
23
24
  Print collected IP and location data.
24
25
 
25
26
  :param ipv4_api: public IPv4 API
26
27
  :param geo: geolocation flag
27
28
  :param timeout: timeout value for API
29
+ :param max_retries: number of retries
30
+ :param retry_delay: delay between retries (in seconds)
28
31
  """
29
32
  private_result = get_private_ipv4()
30
33
  print("Private IP:\n")
@@ -36,13 +39,18 @@ def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO, geo: bool=False,
36
39
  public_title += " and Location Info"
37
40
  public_title += ":\n"
38
41
  print(public_title)
39
- public_result = get_public_ipv4(ipv4_api, geo=geo, timeout=timeout)
42
+ public_result = get_public_ipv4(
43
+ ipv4_api,
44
+ geo=geo,
45
+ timeout=timeout,
46
+ max_retries=max_retries,
47
+ retry_delay=retry_delay)
40
48
  if public_result["status"]:
41
49
  for name, parameter in sorted(public_result["data"].items()):
42
50
  print(
43
51
  " {name}: {parameter}".format(
44
52
  name=PARAMETERS_NAME_MAP[name],
45
- parameter=filter_parameter(parameter)))
53
+ parameter=_filter_parameter(parameter)))
46
54
  else:
47
55
  print(" Error: {public_result[error]}".format(public_result=public_result))
48
56
 
@@ -56,11 +64,13 @@ def main() -> None: # pragma: no cover
56
64
  type=str.lower,
57
65
  choices=[
58
66
  x.value for x in IPv4API],
59
- default=IPv4API.AUTO.value)
67
+ default=IPv4API.AUTO_SAFE.value)
60
68
  parser.add_argument('--info', help='info', nargs="?", const=1)
61
69
  parser.add_argument('--version', help='version', nargs="?", const=1)
62
70
  parser.add_argument('--no-geo', help='no geolocation data', nargs="?", const=1, default=False)
63
71
  parser.add_argument('--timeout', help='timeout for the API request', type=float, default=5.0)
72
+ parser.add_argument('--max-retries', help='number of retries', type=int, default=0)
73
+ parser.add_argument('--retry-delay', help='delay between retries (in seconds)', type=float, default=1.0)
64
74
 
65
75
  args = parser.parse_args()
66
76
  if args.version:
@@ -70,4 +80,9 @@ def main() -> None: # pragma: no cover
70
80
  else:
71
81
  ipv4_api = IPv4API(args.ipv4_api)
72
82
  geo = not args.no_geo
73
- display_ip_info(ipv4_api=ipv4_api, geo=geo, timeout=args.timeout)
83
+ display_ip_info(
84
+ ipv4_api=ipv4_api,
85
+ geo=geo,
86
+ timeout=args.timeout,
87
+ max_retries=args.max_retries,
88
+ retry_delay=args.retry_delay)
ipspot/ipv4.py CHANGED
@@ -6,7 +6,7 @@ from typing import Union, Dict, List, Tuple
6
6
  import requests
7
7
  from requests.adapters import HTTPAdapter
8
8
  from urllib3.poolmanager import PoolManager
9
- from .utils import is_loopback
9
+ from .utils import is_loopback, _get_json_standard, _attempt_with_retries
10
10
  from .params import REQUEST_HEADERS, IPv4API
11
11
 
12
12
 
@@ -53,6 +53,21 @@ class IPv4HTTPAdapter(HTTPAdapter):
53
53
  socket.getaddrinfo = self._original_getaddrinfo
54
54
 
55
55
 
56
+ def _get_json_ipv4_forced(url: str, timeout: Union[float, Tuple[float, float]]) -> dict:
57
+ """
58
+ Send GET request with forced IPv4 using IPv4HTTPAdapter that returns JSON response.
59
+
60
+ :param url: API url
61
+ :param timeout: timeout value for API
62
+ """
63
+ with requests.Session() as session:
64
+ session.mount("http://", IPv4HTTPAdapter())
65
+ session.mount("https://", IPv4HTTPAdapter())
66
+ response = session.get(url, headers=REQUEST_HEADERS, timeout=timeout)
67
+ response.raise_for_status()
68
+ return response.json()
69
+
70
+
56
71
  def is_ipv4(ip: str) -> bool:
57
72
  """
58
73
  Check if the given input is a valid IPv4 address.
@@ -81,8 +96,8 @@ def get_private_ipv4() -> Dict[str, Union[bool, Dict[str, str], str]]:
81
96
  return {"status": False, "error": str(e)}
82
97
 
83
98
 
84
- def _ipsb_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
85
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
99
+ def _ip_sb_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
100
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
86
101
  """
87
102
  Get public IP and geolocation using ip.sb.
88
103
 
@@ -90,30 +105,139 @@ def _ipsb_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
90
105
  :param timeout: timeout value for API
91
106
  """
92
107
  try:
93
- with requests.Session() as session:
94
- response = session.get("https://api-ipv4.ip.sb/geoip", headers=REQUEST_HEADERS, timeout=timeout)
95
- response.raise_for_status()
96
- data = response.json()
97
- result = {"status": True, "data": {"ip": data.get("ip"), "api": "ip.sb"}}
98
- if geo:
99
- geo_data = {
100
- "city": data.get("city"),
101
- "region": data.get("region"),
102
- "country": data.get("country"),
103
- "country_code": data.get("country_code"),
104
- "latitude": data.get("latitude"),
105
- "longitude": data.get("longitude"),
106
- "organization": data.get("organization"),
107
- "timezone": data.get("timezone")
108
- }
109
- result["data"].update(geo_data)
110
- return result
108
+ data = _get_json_standard(url="https://api-ipv4.ip.sb/geoip", timeout=timeout)
109
+ result = {"status": True, "data": {"ip": data["ip"], "api": "ip.sb"}}
110
+ if geo:
111
+ geo_data = {
112
+ "city": data.get("city"),
113
+ "region": data.get("region"),
114
+ "country": data.get("country"),
115
+ "country_code": data.get("country_code"),
116
+ "latitude": data.get("latitude"),
117
+ "longitude": data.get("longitude"),
118
+ "organization": data.get("organization"),
119
+ "timezone": data.get("timezone")
120
+ }
121
+ result["data"].update(geo_data)
122
+ return result
111
123
  except Exception as e:
112
124
  return {"status": False, "error": str(e)}
113
125
 
114
126
 
115
- def _ipapi_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
116
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
127
+ def _ipleak_net_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
128
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
129
+ """
130
+ Get public IP and geolocation using ipleak.net.
131
+
132
+ :param geo: geolocation flag
133
+ :param timeout: timeout value for API
134
+ """
135
+ try:
136
+ data = _get_json_standard(url="https://ipv4.ipleak.net/json/", timeout=timeout)
137
+ result = {"status": True, "data": {"ip": data["ip"], "api": "ipleak.net"}}
138
+ if geo:
139
+ geo_data = {
140
+ "city": data.get("city_name"),
141
+ "region": data.get("region_name"),
142
+ "country": data.get("country_name"),
143
+ "country_code": data.get("country_code"),
144
+ "latitude": data.get("latitude"),
145
+ "longitude": data.get("longitude"),
146
+ "organization": data.get("isp_name"),
147
+ "timezone": data.get("time_zone")
148
+ }
149
+ result["data"].update(geo_data)
150
+ return result
151
+ except Exception as e:
152
+ return {"status": False, "error": str(e)}
153
+
154
+
155
+ def _my_ip_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
156
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
157
+ """
158
+ Get public IP and geolocation using my-ip.io.
159
+
160
+ :param geo: geolocation flag
161
+ :param timeout: timeout value for API
162
+ """
163
+ try:
164
+ data = _get_json_standard(url="https://api4.my-ip.io/v2/ip.json", timeout=timeout)
165
+ result = {"status": True, "data": {"ip": data["ip"], "api": "my-ip.io"}}
166
+ if geo:
167
+ geo_data = {
168
+ "city": data.get("city"),
169
+ "region": data.get("region"),
170
+ "country": data.get("country", {}).get("name"),
171
+ "country_code": data.get("country", {}).get("code"),
172
+ "latitude": data.get("location", {}).get("lat"),
173
+ "longitude": data.get("location", {}).get("lon"),
174
+ "organization": data.get("asn", {}).get("name"),
175
+ "timezone": data.get("timeZone")
176
+ }
177
+ result["data"].update(geo_data)
178
+ return result
179
+ except Exception as e:
180
+ return {"status": False, "error": str(e)}
181
+
182
+
183
+ def _ifconfig_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
184
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
185
+ """
186
+ Get public IP and geolocation using ifconfig.co.
187
+
188
+ :param geo: geolocation flag
189
+ :param timeout: timeout value for API
190
+ """
191
+ try:
192
+ data = _get_json_ipv4_forced(url="https://ifconfig.co/json", timeout=timeout)
193
+ result = {"status": True, "data": {"ip": data["ip"], "api": "ifconfig.co"}}
194
+ if geo:
195
+ geo_data = {
196
+ "city": data.get("city"),
197
+ "region": data.get("region_name"),
198
+ "country": data.get("country"),
199
+ "country_code": data.get("country_iso"),
200
+ "latitude": data.get("latitude"),
201
+ "longitude": data.get("longitude"),
202
+ "organization": data.get("asn_org"),
203
+ "timezone": data.get("time_zone")
204
+ }
205
+ result["data"].update(geo_data)
206
+ return result
207
+ except Exception as e:
208
+ return {"status": False, "error": str(e)}
209
+
210
+
211
+ def _ipapi_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
212
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
213
+ """
214
+ Get public IP and geolocation using ipapi.co.
215
+
216
+ :param geo: geolocation flag
217
+ :param timeout: timeout value for API
218
+ """
219
+ try:
220
+ data = _get_json_ipv4_forced(url="https://ipapi.co/json/", timeout=timeout)
221
+ result = {"status": True, "data": {"ip": data["ip"], "api": "ipapi.co"}}
222
+ if geo:
223
+ geo_data = {
224
+ "city": data.get("city"),
225
+ "region": data.get("region"),
226
+ "country": data.get("country_name"),
227
+ "country_code": data.get("country_code"),
228
+ "latitude": data.get("latitude"),
229
+ "longitude": data.get("longitude"),
230
+ "organization": data.get("org"),
231
+ "timezone": data.get("timezone")
232
+ }
233
+ result["data"].update(geo_data)
234
+ return result
235
+ except Exception as e:
236
+ return {"status": False, "error": str(e)}
237
+
238
+
239
+ def _ip_api_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
240
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
117
241
  """
118
242
  Get public IP and geolocation using ip-api.com.
119
243
 
@@ -121,34 +245,29 @@ def _ipapi_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
121
245
  :param timeout: timeout value for API
122
246
  """
123
247
  try:
124
- with requests.Session() as session:
125
- session.mount("http://", IPv4HTTPAdapter())
126
- session.mount("https://", IPv4HTTPAdapter())
127
- response = session.get("http://ip-api.com/json/", headers=REQUEST_HEADERS, timeout=timeout)
128
- response.raise_for_status()
129
- data = response.json()
130
- if data.get("status") != "success":
131
- return {"status": False, "error": "ip-api lookup failed"}
132
- result = {"status": True, "data": {"ip": data.get("query"), "api": "ip-api.com"}}
133
- if geo:
134
- geo_data = {
135
- "city": data.get("city"),
136
- "region": data.get("regionName"),
137
- "country": data.get("country"),
138
- "country_code": data.get("countryCode"),
139
- "latitude": data.get("lat"),
140
- "longitude": data.get("lon"),
141
- "organization": data.get("org"),
142
- "timezone": data.get("timezone")
143
- }
144
- result["data"].update(geo_data)
145
- return result
248
+ data = _get_json_ipv4_forced(url="http://ip-api.com/json/", timeout=timeout)
249
+ if data.get("status") != "success":
250
+ return {"status": False, "error": "ip-api lookup failed"}
251
+ result = {"status": True, "data": {"ip": data["query"], "api": "ip-api.com"}}
252
+ if geo:
253
+ geo_data = {
254
+ "city": data.get("city"),
255
+ "region": data.get("regionName"),
256
+ "country": data.get("country"),
257
+ "country_code": data.get("countryCode"),
258
+ "latitude": data.get("lat"),
259
+ "longitude": data.get("lon"),
260
+ "organization": data.get("org"),
261
+ "timezone": data.get("timezone")
262
+ }
263
+ result["data"].update(geo_data)
264
+ return result
146
265
  except Exception as e:
147
266
  return {"status": False, "error": str(e)}
148
267
 
149
268
 
150
- def _ipinfo_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
151
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
269
+ def _ipinfo_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
270
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
152
271
  """
153
272
  Get public IP and geolocation using ipinfo.io.
154
273
 
@@ -156,27 +275,50 @@ def _ipinfo_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
156
275
  :param timeout: timeout value for API
157
276
  """
158
277
  try:
159
- with requests.Session() as session:
160
- session.mount("http://", IPv4HTTPAdapter())
161
- session.mount("https://", IPv4HTTPAdapter())
162
- response = session.get("https://ipinfo.io/json", headers=REQUEST_HEADERS, timeout=timeout)
163
- response.raise_for_status()
164
- data = response.json()
165
- result = {"status": True, "data": {"ip": data.get("ip"), "api": "ipinfo.io"}}
166
- if geo:
167
- loc = data.get("loc", "").split(",")
168
- geo_data = {
169
- "city": data.get("city"),
170
- "region": data.get("region"),
171
- "country": None,
172
- "country_code": data.get("country"),
173
- "latitude": float(loc[0]) if len(loc) == 2 else None,
174
- "longitude": float(loc[1]) if len(loc) == 2 else None,
175
- "organization": data.get("org"),
176
- "timezone": data.get("timezone")
177
- }
178
- result["data"].update(geo_data)
179
- return result
278
+ data = _get_json_ipv4_forced(url="https://ipinfo.io/json", timeout=timeout)
279
+ result = {"status": True, "data": {"ip": data["ip"], "api": "ipinfo.io"}}
280
+ if geo:
281
+ loc = data.get("loc", "").split(",")
282
+ geo_data = {
283
+ "city": data.get("city"),
284
+ "region": data.get("region"),
285
+ "country": None,
286
+ "country_code": data.get("country"),
287
+ "latitude": float(loc[0]) if len(loc) == 2 else None,
288
+ "longitude": float(loc[1]) if len(loc) == 2 else None,
289
+ "organization": data.get("org"),
290
+ "timezone": data.get("timezone")
291
+ }
292
+ result["data"].update(geo_data)
293
+ return result
294
+ except Exception as e:
295
+ return {"status": False, "error": str(e)}
296
+
297
+
298
+ def _reallyfreegeoip_org_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
299
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
300
+ """
301
+ Get public IP and geolocation using reallyfreegeoip.org.
302
+
303
+ :param geo: geolocation flag
304
+ :param timeout: timeout value for API
305
+ """
306
+ try:
307
+ data = _get_json_ipv4_forced(url="https://reallyfreegeoip.org/json/", timeout=timeout)
308
+ result = {"status": True, "data": {"ip": data["ip"], "api": "reallyfreegeoip.org"}}
309
+ if geo:
310
+ geo_data = {
311
+ "city": data.get("city"),
312
+ "region": data.get("region_name"),
313
+ "country": data.get("country_name"),
314
+ "country_code": data.get("country_code"),
315
+ "latitude": data.get("latitude"),
316
+ "longitude": data.get("longitude"),
317
+ "organization": None, # does not provide organization
318
+ "timezone": data.get("time_zone")
319
+ }
320
+ result["data"].update(geo_data)
321
+ return result
180
322
  except Exception as e:
181
323
  return {"status": False, "error": str(e)}
182
324
 
@@ -190,30 +332,27 @@ def _ident_me_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
190
332
  :param timeout: timeout value for API
191
333
  """
192
334
  try:
193
- with requests.Session() as session:
194
- response = session.get("https://4.ident.me/json", headers=REQUEST_HEADERS, timeout=timeout)
195
- response.raise_for_status()
196
- data = response.json()
197
- result = {"status": True, "data": {"ip": data.get("ip"), "api": "ident.me"}}
198
- if geo:
199
- geo_data = {
200
- "city": data.get("city"),
201
- "region": None,
202
- "country": data.get("country"),
203
- "country_code": data.get("cc"),
204
- "latitude": data.get("latitude"),
205
- "longitude": data.get("longitude"),
206
- "organization": data.get("aso"),
207
- "timezone": data.get("tz")
208
- }
209
- result["data"].update(geo_data)
210
- return result
335
+ data = _get_json_standard(url="https://4.ident.me/json", timeout=timeout)
336
+ result = {"status": True, "data": {"ip": data["ip"], "api": "ident.me"}}
337
+ if geo:
338
+ geo_data = {
339
+ "city": data.get("city"),
340
+ "region": None,
341
+ "country": data.get("country"),
342
+ "country_code": data.get("cc"),
343
+ "latitude": data.get("latitude"),
344
+ "longitude": data.get("longitude"),
345
+ "organization": data.get("aso"),
346
+ "timezone": data.get("tz")
347
+ }
348
+ result["data"].update(geo_data)
349
+ return result
211
350
  except Exception as e:
212
351
  return {"status": False, "error": str(e)}
213
352
 
214
353
 
215
- def _tnedime_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
216
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
354
+ def _tnedi_me_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
355
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
217
356
  """
218
357
  Get public IP and geolocation using tnedi.me.
219
358
 
@@ -221,53 +360,181 @@ def _tnedime_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
221
360
  :param timeout: timeout value for API
222
361
  """
223
362
  try:
224
- with requests.Session() as session:
225
- response = session.get("https://4.tnedi.me/json", headers=REQUEST_HEADERS, timeout=timeout)
226
- response.raise_for_status()
227
- data = response.json()
228
- result = {"status": True, "data": {"ip": data.get("ip"), "api": "tnedi.me"}}
229
- if geo:
230
- geo_data = {
231
- "city": data.get("city"),
232
- "region": None,
233
- "country": data.get("country"),
234
- "country_code": data.get("cc"),
235
- "latitude": data.get("latitude"),
236
- "longitude": data.get("longitude"),
237
- "organization": data.get("aso"),
238
- "timezone": data.get("tz")
239
- }
240
- result["data"].update(geo_data)
241
- return result
363
+ data = _get_json_standard(url="https://4.tnedi.me/json", timeout=timeout)
364
+ result = {"status": True, "data": {"ip": data["ip"], "api": "tnedi.me"}}
365
+ if geo:
366
+ geo_data = {
367
+ "city": data.get("city"),
368
+ "region": None,
369
+ "country": data.get("country"),
370
+ "country_code": data.get("cc"),
371
+ "latitude": data.get("latitude"),
372
+ "longitude": data.get("longitude"),
373
+ "organization": data.get("aso"),
374
+ "timezone": data.get("tz")
375
+ }
376
+ result["data"].update(geo_data)
377
+ return result
378
+ except Exception as e:
379
+ return {"status": False, "error": str(e)}
380
+
381
+
382
+ def _myip_la_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
383
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
384
+ """
385
+ Get public IP and geolocation using myip.la.
386
+
387
+ :param geo: geolocation flag
388
+ :param timeout: timeout value for API
389
+ """
390
+ try:
391
+ data = _get_json_ipv4_forced(url="https://api.myip.la/en?json", timeout=timeout)
392
+ result = {"status": True, "data": {"ip": data["ip"], "api": "myip.la"}}
393
+ if geo:
394
+ loc = data.get("location", {})
395
+ geo_data = {
396
+ "city": loc.get("city"),
397
+ "region": loc.get("province"),
398
+ "country": loc.get("country_name"),
399
+ "country_code": loc.get("country_code"),
400
+ "latitude": float(loc.get("latitude")) if loc.get("latitude") else None,
401
+ "longitude": float(loc.get("longitude")) if loc.get("longitude") else None,
402
+ "organization": None,
403
+ "timezone": None
404
+ }
405
+ result["data"].update(geo_data)
406
+ return result
407
+ except Exception as e:
408
+ return {"status": False, "error": str(e)}
409
+
410
+
411
+ def _freeipapi_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
412
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
413
+ """
414
+ Get public IP and geolocation using freeipapi.com.
415
+
416
+ :param geo: geolocation flag
417
+ :param timeout: timeout value for API
418
+ """
419
+ try:
420
+ data = _get_json_ipv4_forced(url="https://freeipapi.com/api/json", timeout=timeout)
421
+ result = {"status": True, "data": {"ip": data["ipAddress"], "api": "freeipapi.com"}}
422
+ if geo:
423
+ geo_data = {
424
+ "city": data.get("cityName"),
425
+ "region": data.get("regionName"),
426
+ "country": data.get("countryName"),
427
+ "country_code": data.get("countryCode"),
428
+ "latitude": data.get("latitude"),
429
+ "longitude": data.get("longitude"),
430
+ "organization": None,
431
+ "timezone": data.get("timeZone")
432
+ }
433
+ result["data"].update(geo_data)
434
+ return result
242
435
  except Exception as e:
243
436
  return {"status": False, "error": str(e)}
244
437
 
245
438
 
246
- def get_public_ipv4(api: IPv4API=IPv4API.AUTO, geo: bool=False,
247
- timeout: Union[float, Tuple[float, float]]=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
439
+ IPV4_API_MAP = {
440
+ IPv4API.IFCONFIG_CO: {
441
+ "thread_safe": False,
442
+ "geo": True,
443
+ "function": _ifconfig_co_ipv4
444
+ },
445
+ IPv4API.IDENT_ME: {
446
+ "thread_safe": True,
447
+ "geo": True,
448
+ "function": _ident_me_ipv4
449
+ },
450
+ IPv4API.TNEDI_ME: {
451
+ "thread_safe": True,
452
+ "geo": True,
453
+ "function": _tnedi_me_ipv4
454
+ },
455
+ IPv4API.IP_SB: {
456
+ "thread_safe": True,
457
+ "geo": True,
458
+ "function": _ip_sb_ipv4
459
+ },
460
+ IPv4API.IPLEAK_NET: {
461
+ "thread_safe": True,
462
+ "geo": True,
463
+ "function": _ipleak_net_ipv4
464
+ },
465
+ IPv4API.MY_IP_IO: {
466
+ "thread_safe": True,
467
+ "geo": True,
468
+ "function": _my_ip_io_ipv4
469
+ },
470
+ IPv4API.IP_API_COM: {
471
+ "thread_safe": False,
472
+ "geo": True,
473
+ "function": _ip_api_com_ipv4
474
+ },
475
+ IPv4API.IPINFO_IO: {
476
+ "thread_safe": False,
477
+ "geo": True,
478
+ "function": _ipinfo_io_ipv4
479
+ },
480
+ IPv4API.IPAPI_CO: {
481
+ "thread_safe": False,
482
+ "geo": True,
483
+ "function": _ipapi_co_ipv4
484
+ },
485
+ IPv4API.REALLYFREEGEOIP_ORG: {
486
+ "thread_safe": False,
487
+ "geo": True,
488
+ "function": _reallyfreegeoip_org_ipv4
489
+ },
490
+ IPv4API.FREEIPAPI_COM: {
491
+ "thread_safe": False,
492
+ "geo": True,
493
+ "function": _freeipapi_com_ipv4,
494
+ },
495
+ IPv4API.MYIP_LA: {
496
+ "thread_safe": False,
497
+ "geo": True,
498
+ "function": _myip_la_ipv4,
499
+ },
500
+ }
501
+
502
+
503
+ def get_public_ipv4(api: IPv4API=IPv4API.AUTO_SAFE, geo: bool=False,
504
+ timeout: Union[float, Tuple[float, float]]=5,
505
+ max_retries: int = 0,
506
+ retry_delay: float = 1.0) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
248
507
  """
249
508
  Get public IPv4 and geolocation info based on the selected API.
250
509
 
251
510
  :param api: public IPv4 API
252
511
  :param geo: geolocation flag
253
512
  :param timeout: timeout value for API
513
+ :param max_retries: number of retries
514
+ :param retry_delay: delay between retries (in seconds)
254
515
  """
255
- api_map = {
256
- IPv4API.IDENTME: _ident_me_ipv4,
257
- IPv4API.TNEDIME: _tnedime_ipv4,
258
- IPv4API.IPSB: _ipsb_ipv4,
259
- IPv4API.IPAPI: _ipapi_ipv4,
260
- IPv4API.IPINFO: _ipinfo_ipv4,
261
- }
262
-
263
- if api == IPv4API.AUTO:
264
- for _, func in api_map.items():
265
- result = func(geo=geo, timeout=timeout)
516
+ if api in [IPv4API.AUTO, IPv4API.AUTO_SAFE]:
517
+ for _, api_data in IPV4_API_MAP.items():
518
+ if api == IPv4API.AUTO_SAFE and not api_data["thread_safe"]:
519
+ continue
520
+ func = api_data["function"]
521
+ result = _attempt_with_retries(
522
+ func=func,
523
+ max_retries=max_retries,
524
+ retry_delay=retry_delay,
525
+ geo=geo,
526
+ timeout=timeout)
266
527
  if result["status"]:
267
528
  return result
268
529
  return {"status": False, "error": "All attempts failed."}
269
530
  else:
270
- func = api_map.get(api)
271
- if func:
272
- return func(geo=geo, timeout=timeout)
531
+ api_data = IPV4_API_MAP.get(api)
532
+ if api_data:
533
+ func = api_data["function"]
534
+ return _attempt_with_retries(
535
+ func=func,
536
+ max_retries=max_retries,
537
+ retry_delay=retry_delay,
538
+ geo=geo,
539
+ timeout=timeout)
273
540
  return {"status": False, "error": "Unsupported API: {api}".format(api=api)}
ipspot/params.py CHANGED
@@ -2,7 +2,7 @@
2
2
  """ipspot params."""
3
3
  from enum import Enum
4
4
 
5
- IPSPOT_VERSION = "0.3"
5
+ IPSPOT_VERSION = "0.4"
6
6
 
7
7
  IPSPOT_OVERVIEW = '''
8
8
  IPSpot is a Python library for retrieving the current system's IP address and location information.
@@ -22,11 +22,19 @@ class IPv4API(Enum):
22
22
  """Public IPv4 API enum."""
23
23
 
24
24
  AUTO = "auto"
25
- IPAPI = "ipapi"
26
- IPINFO = "ipinfo"
27
- IPSB = "ipsb"
28
- IDENTME = "identme"
29
- TNEDIME = "tnedime"
25
+ AUTO_SAFE = "auto-safe"
26
+ IP_API_COM = "ip-api.com"
27
+ IPAPI_CO = "ipapi.co"
28
+ IPINFO_IO = "ipinfo.io"
29
+ IP_SB = "ip.sb"
30
+ IDENT_ME = "ident.me"
31
+ TNEDI_ME = "tnedi.me"
32
+ IPLEAK_NET = "ipleak.net"
33
+ MY_IP_IO = "my-ip.io"
34
+ IFCONFIG_CO = "ifconfig.co"
35
+ REALLYFREEGEOIP_ORG = "reallyfreegeoip.org"
36
+ MYIP_LA = "myip.la"
37
+ FREEIPAPI_COM = "freeipapi.com"
30
38
 
31
39
 
32
40
  PARAMETERS_NAME_MAP = {
ipspot/utils.py CHANGED
@@ -1,7 +1,33 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """ipspot utils."""
3
+ import time
3
4
  import ipaddress
4
- from typing import Any
5
+ import requests
6
+ from typing import Callable, Dict
7
+ from typing import Union, Tuple, Any
8
+ from .params import REQUEST_HEADERS
9
+
10
+
11
+ def _attempt_with_retries(
12
+ func: Callable,
13
+ max_retries: int,
14
+ retry_delay: float, **kwargs: dict) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
15
+ """
16
+ Attempt a function call with retries and delay.
17
+
18
+ :param func: function to execute
19
+ :param max_retries: number of retries
20
+ :param retry_delay: delay between retries (in seconds)
21
+ :param kwargs: keyword arguments to pass to the function
22
+ """
23
+ max_retries = max(0, max_retries)
24
+ result = {"status": False, "error": ""}
25
+ for attempt in range(max_retries + 1):
26
+ result = func(**kwargs)
27
+ if result["status"]:
28
+ break
29
+ time.sleep(retry_delay)
30
+ return result
5
31
 
6
32
 
7
33
  def is_loopback(ip: str) -> bool:
@@ -17,7 +43,7 @@ def is_loopback(ip: str) -> bool:
17
43
  return False
18
44
 
19
45
 
20
- def filter_parameter(parameter: Any) -> Any:
46
+ def _filter_parameter(parameter: Any) -> Any:
21
47
  """
22
48
  Filter input parameter.
23
49
 
@@ -28,3 +54,16 @@ def filter_parameter(parameter: Any) -> Any:
28
54
  if isinstance(parameter, str) and len(parameter.strip()) == 0:
29
55
  return "N/A"
30
56
  return parameter
57
+
58
+
59
+ def _get_json_standard(url: str, timeout: Union[float, Tuple[float, float]]) -> dict:
60
+ """
61
+ Send standard GET request that returns JSON response.
62
+
63
+ :param url: API url
64
+ :param timeout: timeout value for API
65
+ """
66
+ with requests.Session() as session:
67
+ response = session.get(url, headers=REQUEST_HEADERS, timeout=timeout)
68
+ response.raise_for_status()
69
+ return response.json()
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ipspot
3
- Version: 0.3
3
+ Version: 0.4
4
4
  Summary: IPSpot: A Python Tool to Fetch the System's IP Address
5
5
  Home-page: https://github.com/openscilab/ipspot
6
- Download-URL: https://github.com/openscilab/ipspot/tarball/v0.3
6
+ Download-URL: https://github.com/openscilab/ipspot/tarball/v0.4
7
7
  Author: IPSpot Development Team
8
8
  Author-email: ipspot@openscilab.com
9
9
  License: MIT
@@ -102,13 +102,13 @@ Dynamic: summary
102
102
  ## Installation
103
103
 
104
104
  ### Source Code
105
- - Download [Version 0.3](https://github.com/openscilab/ipspot/archive/v0.3.zip) or [Latest Source](https://github.com/openscilab/ipspot/archive/dev.zip)
105
+ - Download [Version 0.4](https://github.com/openscilab/ipspot/archive/v0.4.zip) or [Latest Source](https://github.com/openscilab/ipspot/archive/dev.zip)
106
106
  - `pip install .`
107
107
 
108
108
  ### PyPI
109
109
 
110
110
  - Check [Python Packaging User Guide](https://packaging.python.org/installing/)
111
- - `pip install ipspot==0.3`
111
+ - `pip install ipspot==0.4`
112
112
 
113
113
 
114
114
  ## Usage
@@ -119,9 +119,11 @@ Dynamic: summary
119
119
 
120
120
  ```pycon
121
121
  >>> from ipspot import get_public_ipv4, IPv4API
122
- >>> get_public_ipv4(api=IPv4API.IPAPI)
122
+ >>> get_public_ipv4(api=IPv4API.IP_API_COM)
123
123
  {'status': True, 'data': {'ip': 'xx.xx.xx.xx', 'api': 'ip-api.com'}}
124
- >>> get_public_ipv4(api=IPv4API.IPAPI, geo=True, timeout=10)
124
+ >>> get_public_ipv4(api=IPv4API.IP_API_COM, geo=True, timeout=10)
125
+ {'data': {'country_code': 'GB', 'latitude': 50.9097, 'longitude': -1.4043, 'api': 'ip-api.com', 'country': 'United Kingdom', 'timezone': 'Europe/London', 'organization': '', 'region': 'England', 'ip': 'xx.xx.xx.xx', 'city': 'Southampton'}, 'status': True}
126
+ >>> get_public_ipv4(api=IPv4API.IP_API_COM, geo=True, timeout=10, max_retries=5, retry_delay=4)
125
127
  {'data': {'country_code': 'GB', 'latitude': 50.9097, 'longitude': -1.4043, 'api': 'ip-api.com', 'country': 'United Kingdom', 'timezone': 'Europe/London', 'organization': '', 'region': 'England', 'ip': 'xx.xx.xx.xx', 'city': 'Southampton'}, 'status': True}
126
128
  ```
127
129
 
@@ -142,7 +144,7 @@ Dynamic: summary
142
144
  ```console
143
145
  > ipspot --version
144
146
 
145
- 0.3
147
+ 0.4
146
148
  ```
147
149
 
148
150
  #### Info
@@ -157,11 +159,11 @@ Dynamic: summary
157
159
  |___||_| |____/ | .__/ \___/ \__|
158
160
  |_|
159
161
 
160
- __ __ ___ _____
161
- \ \ / / _ / _ \ |___ /
162
- \ \ / / (_)| | | | |_ \
163
- \ V / _ | |_| | _ ___) |
164
- \_/ (_) \___/ (_)|____/
162
+ __ __ ___ _ _
163
+ \ \ / / _ / _ \ | || |
164
+ \ \ / / (_)| | | | | || |_
165
+ \ V / _ | |_| | _ |__ _|
166
+ \_/ (_) \___/ (_) |_|
165
167
 
166
168
 
167
169
 
@@ -197,12 +199,12 @@ Public IP and Location Info:
197
199
 
198
200
  #### IPv4 API
199
201
 
200
- ℹ️ `ipv4-api` valid choices: [`auto`, `ipapi`, `ipinfo`, `ipsb`, `identme`, `tnedime`]
202
+ ℹ️ `ipv4-api` valid choices: [`auto-safe`, `auto`, `ip-api.com`, `ipinfo.io`, `ip.sb`, `ident.me`, `tnedi.me`, `ipapi.co`, `ipleak.net`, `my-ip.io`, `ifconfig.co`, `reallyfreegeoip.org`, `freeipapi.com`, `myip.la`]
201
203
 
202
- ℹ️ The default value: `auto`
204
+ ℹ️ The default value: `auto-safe`
203
205
 
204
206
  ```console
205
- > ipspot --ipv4-api="ipinfo"
207
+ > ipspot --ipv4-api="ipinfo.io"
206
208
  Private IP:
207
209
 
208
210
  10.36.18.154
@@ -267,6 +269,29 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
267
269
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
268
270
 
269
271
  ## [Unreleased]
272
+ ## [0.4] - 2025-06-09
273
+ ### Added
274
+ - Support [ipapi.co](https://ipapi.co/json/)
275
+ - Support [ipleak.net](https://ipleak.net/json/)
276
+ - Support [my-ip.io](https://www.my-ip.io/)
277
+ - Support [ifconfig.co](https://ifconfig.co/json)
278
+ - Support [reallyfreegeoip.org](https://reallyfreegeoip.org/json/)
279
+ - Support [myip.la](https://api.myip.la/en?json)
280
+ - Support [freeipapi.com](https://freeipapi.com/api/json/)
281
+ - `AUTO_SAFE` mode
282
+ - `_get_json_standard` function
283
+ - `_get_json_ipv4_forced` function
284
+ - `--max-retries` argument
285
+ - `--retry-delay` argument
286
+ ### Changed
287
+ - `IPv4API.IPAPI` renamed to `IPv4API.IP_API_COM`
288
+ - `IPv4API.IPINFO` renamed to `IPv4API.IPINFO_IO`
289
+ - `IPv4API.IPSB` renamed to `IPv4API.IP_SB`
290
+ - `IPv4API.IDENTME` renamed to `IPv4API.IDENT_ME`
291
+ - `IPv4API.TNEDIME` renamed to `IPv4API.TNEDI_ME`
292
+ - `get_public_ipv4` function modified
293
+ - `filter_parameter` function renamed to `_filter_parameter`
294
+ - `README.md` updated
270
295
  ## [0.3] - 2025-05-19
271
296
  ### Added
272
297
  - `is_ipv4` function
@@ -303,7 +328,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
303
328
  - `--no-geo` argument
304
329
  - Logo
305
330
 
306
- [Unreleased]: https://github.com/openscilab/ipspot/compare/v0.3...dev
331
+ [Unreleased]: https://github.com/openscilab/ipspot/compare/v0.4...dev
332
+ [0.4]: https://github.com/openscilab/ipspot/compare/v0.3...v0.4
307
333
  [0.3]: https://github.com/openscilab/ipspot/compare/v0.2...v0.3
308
334
  [0.2]: https://github.com/openscilab/ipspot/compare/v0.1...v0.2
309
335
  [0.1]: https://github.com/openscilab/ipspot/compare/3216fb7...v0.1
@@ -0,0 +1,13 @@
1
+ ipspot/__init__.py,sha256=e_1FqqNDD9eoDVw9eKL_1pS7du-Sei3lm6A4UFjYiUo,211
2
+ ipspot/__main__.py,sha256=xKHY_tc94SWktkIHV0O9YADAHPZvT8yXOFSm_L2yMno,105
3
+ ipspot/cli.py,sha256=M23l9ftqKbdDGhb906q4ywJmcjkHW3VWijyXj76APC4,3222
4
+ ipspot/ipv4.py,sha256=zGFvzD2GeM2lFxMWc6lcCphBaj5qY0kxiHE0FBhDoTc,19998
5
+ ipspot/params.py,sha256=hdArSL10EEq4vtfxA51ftCkn5WfsAzsxdoX1T11yCdQ,1419
6
+ ipspot/utils.py,sha256=YtkyWGljpFc72rSGljsARjlVrF9hkE7Gl78_5uex1yI,1870
7
+ ipspot-0.4.dist-info/licenses/AUTHORS.md,sha256=5ZvxP1KnuVkurB4psQDSqnqiAyn44q1aeiXUsnYgps4,411
8
+ ipspot-0.4.dist-info/licenses/LICENSE,sha256=0aOd4wzZRoSH_35UZXRHS7alPFTtuFEBJrajLuonEIw,1067
9
+ ipspot-0.4.dist-info/METADATA,sha256=8D5MqjjxrCDU60BDPikNoX75iE5bMKVMQX_kFw03WiU,10875
10
+ ipspot-0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ ipspot-0.4.dist-info/entry_points.txt,sha256=DJVLepYr8H3UcvWekU5Jy-tcr_xmWrphzgWGNOd3hlk,43
12
+ ipspot-0.4.dist-info/top_level.txt,sha256=v0WgE1z2iCy_bXU53fVcllwHLTvGNBIvq8u3KPC2_Sc,7
13
+ ipspot-0.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,6 +1,8 @@
1
1
  # Core Developers
2
2
  ----------
3
3
  - Sepand Haghighi - Open Science Laboratory ([Github](https://github.com/sepandhaghighi)) **
4
+ - Sadra Sabouri - Open Science Laboratory ([Github](https://github.com/sadrasabouri))
5
+ - AmirHosein Rostami - Open Science Laboratory ([Github](https://github.com/AHReccese))
4
6
 
5
7
  ** **Maintainer**
6
8
 
@@ -1,13 +0,0 @@
1
- ipspot/__init__.py,sha256=e_1FqqNDD9eoDVw9eKL_1pS7du-Sei3lm6A4UFjYiUo,211
2
- ipspot/__main__.py,sha256=xKHY_tc94SWktkIHV0O9YADAHPZvT8yXOFSm_L2yMno,105
3
- ipspot/cli.py,sha256=Yq0GVzyZhMUauTMA_BUO8UXa6OqfYDBLLVjERZ3vOgE,2632
4
- ipspot/ipv4.py,sha256=OhQQnRdkwQ-SrfaqJdhF1Qu_zsW-mi93NEk_N5QAOrk,10757
5
- ipspot/params.py,sha256=EekA1HmFlxRT-wk0mEKHvPSR_O6UTHWSBgQoiC5-aRU,1147
6
- ipspot/utils.py,sha256=OaRrNQI8ASUHisMVEhyfy4lwcgugRxXK4JcWqljITfE,636
7
- ipspot-0.3.dist-info/licenses/AUTHORS.md,sha256=AgFL2paPd70ItL_YK18lWm_NjNWytKZHmR57o-et8kU,236
8
- ipspot-0.3.dist-info/licenses/LICENSE,sha256=0aOd4wzZRoSH_35UZXRHS7alPFTtuFEBJrajLuonEIw,1067
9
- ipspot-0.3.dist-info/METADATA,sha256=h2XSvM65GkEAL8COdzOpbuOtiQSj3fEgdYCfON6ZCeA,9400
10
- ipspot-0.3.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
11
- ipspot-0.3.dist-info/entry_points.txt,sha256=DJVLepYr8H3UcvWekU5Jy-tcr_xmWrphzgWGNOd3hlk,43
12
- ipspot-0.3.dist-info/top_level.txt,sha256=v0WgE1z2iCy_bXU53fVcllwHLTvGNBIvq8u3KPC2_Sc,7
13
- ipspot-0.3.dist-info/RECORD,,