ipspot 0.5__py3-none-any.whl → 0.7__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 +24 -15
- ipspot/ipv4.py +46 -100
- ipspot/ipv6.py +152 -12
- ipspot/params.py +10 -1
- ipspot/utils.py +71 -3
- {ipspot-0.5.dist-info → ipspot-0.7.dist-info}/METADATA +73 -29
- ipspot-0.7.dist-info/RECORD +14 -0
- ipspot-0.5.dist-info/RECORD +0 -14
- {ipspot-0.5.dist-info → ipspot-0.7.dist-info}/WHEEL +0 -0
- {ipspot-0.5.dist-info → ipspot-0.7.dist-info}/entry_points.txt +0 -0
- {ipspot-0.5.dist-info → ipspot-0.7.dist-info}/licenses/AUTHORS.md +0 -0
- {ipspot-0.5.dist-info → ipspot-0.7.dist-info}/licenses/LICENSE +0 -0
- {ipspot-0.5.dist-info → ipspot-0.7.dist-info}/top_level.txt +0 -0
ipspot/cli.py
CHANGED
|
@@ -8,9 +8,11 @@ from .ipv6 import get_public_ipv6, get_private_ipv6
|
|
|
8
8
|
from .utils import _filter_parameter
|
|
9
9
|
from .params import IPv4API, IPv6API, PARAMETERS_NAME_MAP
|
|
10
10
|
from .params import IPSPOT_OVERVIEW, IPSPOT_REPO, IPSPOT_VERSION
|
|
11
|
+
from .params import PUBLIC_IPV4_ERROR, PRIVATE_IPV4_ERROR
|
|
12
|
+
from .params import PUBLIC_IPV6_ERROR, PRIVATE_IPV6_ERROR
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
def
|
|
15
|
+
def _print_ipspot_info() -> None: # pragma: no cover
|
|
14
16
|
"""Print ipspot details."""
|
|
15
17
|
tprint("IPSpot")
|
|
16
18
|
tprint("V:" + IPSPOT_VERSION)
|
|
@@ -18,11 +20,13 @@ def ipspot_info() -> None: # pragma: no cover
|
|
|
18
20
|
print("Repo : " + IPSPOT_REPO)
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
def _print_report(ipv4_api: IPv4API,
|
|
24
|
+
ipv6_api: IPv6API,
|
|
25
|
+
geo: bool,
|
|
26
|
+
timeout: Union[float, Tuple[float, float]],
|
|
27
|
+
max_retries: int,
|
|
28
|
+
retry_delay: float,
|
|
29
|
+
backoff_factor: float) -> None: # pragma: no cover
|
|
26
30
|
"""
|
|
27
31
|
Print collected IP and location data.
|
|
28
32
|
|
|
@@ -32,20 +36,21 @@ def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE,
|
|
|
32
36
|
:param timeout: timeout value for API
|
|
33
37
|
:param max_retries: number of retries
|
|
34
38
|
:param retry_delay: delay between retries (in seconds)
|
|
39
|
+
:param backoff_factor: backoff factor
|
|
35
40
|
"""
|
|
36
41
|
print("Private IP:\n")
|
|
37
42
|
private_ipv4_result = get_private_ipv4()
|
|
38
43
|
if private_ipv4_result["status"]:
|
|
39
44
|
private_ipv4 = private_ipv4_result["data"]["ip"]
|
|
40
45
|
else:
|
|
41
|
-
private_ipv4 =
|
|
46
|
+
private_ipv4 = PRIVATE_IPV4_ERROR
|
|
42
47
|
print(" IPv4: {private_ipv4}\n".format(private_ipv4=private_ipv4))
|
|
43
48
|
|
|
44
49
|
private_ipv6_result = get_private_ipv6()
|
|
45
50
|
if private_ipv6_result["status"]:
|
|
46
51
|
private_ipv6 = private_ipv6_result["data"]["ip"]
|
|
47
52
|
else:
|
|
48
|
-
private_ipv6 =
|
|
53
|
+
private_ipv6 = PRIVATE_IPV6_ERROR
|
|
49
54
|
print(" IPv6: {private_ipv6}".format(private_ipv6=private_ipv6))
|
|
50
55
|
|
|
51
56
|
public_title = "\nPublic IP"
|
|
@@ -59,7 +64,8 @@ def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE,
|
|
|
59
64
|
geo=geo,
|
|
60
65
|
timeout=timeout,
|
|
61
66
|
max_retries=max_retries,
|
|
62
|
-
retry_delay=retry_delay
|
|
67
|
+
retry_delay=retry_delay,
|
|
68
|
+
backoff_factor=backoff_factor)
|
|
63
69
|
if public_ipv4_result["status"]:
|
|
64
70
|
for name, parameter in sorted(public_ipv4_result["data"].items()):
|
|
65
71
|
print(
|
|
@@ -67,7 +73,7 @@ def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE,
|
|
|
67
73
|
name=PARAMETERS_NAME_MAP[name],
|
|
68
74
|
parameter=_filter_parameter(parameter)))
|
|
69
75
|
else:
|
|
70
|
-
print(" Error: {public_ipv4_result
|
|
76
|
+
print(" Error: {public_ipv4_result}".format(public_ipv4_result=PUBLIC_IPV4_ERROR))
|
|
71
77
|
|
|
72
78
|
print("\n IPv6:\n")
|
|
73
79
|
public_ipv6_result = get_public_ipv6(
|
|
@@ -75,7 +81,8 @@ def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE,
|
|
|
75
81
|
geo=geo,
|
|
76
82
|
timeout=timeout,
|
|
77
83
|
max_retries=max_retries,
|
|
78
|
-
retry_delay=retry_delay
|
|
84
|
+
retry_delay=retry_delay,
|
|
85
|
+
backoff_factor=backoff_factor)
|
|
79
86
|
if public_ipv6_result["status"]:
|
|
80
87
|
for name, parameter in sorted(public_ipv6_result["data"].items()):
|
|
81
88
|
print(
|
|
@@ -83,7 +90,7 @@ def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE,
|
|
|
83
90
|
name=PARAMETERS_NAME_MAP[name],
|
|
84
91
|
parameter=_filter_parameter(parameter)))
|
|
85
92
|
else:
|
|
86
|
-
print(" Error: {public_ipv6_result
|
|
93
|
+
print(" Error: {public_ipv6_result}".format(public_ipv6_result=PUBLIC_IPV6_ERROR))
|
|
87
94
|
|
|
88
95
|
|
|
89
96
|
def main() -> None: # pragma: no cover
|
|
@@ -109,20 +116,22 @@ def main() -> None: # pragma: no cover
|
|
|
109
116
|
parser.add_argument('--timeout', help='timeout for the API request', type=float, default=5.0)
|
|
110
117
|
parser.add_argument('--max-retries', help='number of retries', type=int, default=0)
|
|
111
118
|
parser.add_argument('--retry-delay', help='delay between retries (in seconds)', type=float, default=1.0)
|
|
119
|
+
parser.add_argument('--backoff-factor', help='backoff factor', type=float, default=1.0)
|
|
112
120
|
|
|
113
121
|
args = parser.parse_args()
|
|
114
122
|
if args.version:
|
|
115
123
|
print(IPSPOT_VERSION)
|
|
116
124
|
elif args.info:
|
|
117
|
-
|
|
125
|
+
_print_ipspot_info()
|
|
118
126
|
else:
|
|
119
127
|
ipv4_api = IPv4API(args.ipv4_api)
|
|
120
128
|
ipv6_api = IPv6API(args.ipv6_api)
|
|
121
129
|
geo = not args.no_geo
|
|
122
|
-
|
|
130
|
+
_print_report(
|
|
123
131
|
ipv4_api=ipv4_api,
|
|
124
132
|
ipv6_api=ipv6_api,
|
|
125
133
|
geo=geo,
|
|
126
134
|
timeout=args.timeout,
|
|
127
135
|
max_retries=args.max_retries,
|
|
128
|
-
retry_delay=args.retry_delay
|
|
136
|
+
retry_delay=args.retry_delay,
|
|
137
|
+
backoff_factor=args.backoff_factor)
|
ipspot/ipv4.py
CHANGED
|
@@ -3,69 +3,9 @@
|
|
|
3
3
|
import ipaddress
|
|
4
4
|
import socket
|
|
5
5
|
from typing import Union, Dict, List, Tuple
|
|
6
|
-
import
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from .utils import is_loopback, _get_json_standard, _attempt_with_retries
|
|
10
|
-
from .params import REQUEST_HEADERS, IPv4API
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class IPv4HTTPAdapter(HTTPAdapter):
|
|
14
|
-
"""A custom HTTPAdapter that enforces the use of IPv4 for DNS resolution during HTTP(S) requests using the requests library."""
|
|
15
|
-
|
|
16
|
-
def init_poolmanager(self, connections: int, maxsize: int, block: bool = False, **kwargs: dict) -> None:
|
|
17
|
-
"""
|
|
18
|
-
Initialize the connection pool manager using a temporary override of socket.getaddrinfo to ensure only IPv4 addresses are used.
|
|
19
|
-
|
|
20
|
-
:param connections: the number of connection pools to cache
|
|
21
|
-
:param maxsize: the maximum number of connections to save in the pool
|
|
22
|
-
:param block: whether the connections should block when reaching the max size
|
|
23
|
-
:param kwargs: additional keyword arguments for the PoolManager
|
|
24
|
-
"""
|
|
25
|
-
self.poolmanager = PoolManager(
|
|
26
|
-
num_pools=connections,
|
|
27
|
-
maxsize=maxsize,
|
|
28
|
-
block=block,
|
|
29
|
-
socket_options=self._ipv4_socket_options(),
|
|
30
|
-
**kwargs
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
def _ipv4_socket_options(self) -> list:
|
|
34
|
-
"""
|
|
35
|
-
Temporarily patches socket.getaddrinfo to filter only IPv4 addresses (AF_INET).
|
|
36
|
-
|
|
37
|
-
:return: an empty list of socket options; DNS patching occurs here
|
|
38
|
-
"""
|
|
39
|
-
original_getaddrinfo = socket.getaddrinfo
|
|
40
|
-
|
|
41
|
-
def ipv4_only_getaddrinfo(*args: list, **kwargs: dict) -> List[Tuple]:
|
|
42
|
-
results = original_getaddrinfo(*args, **kwargs)
|
|
43
|
-
return [res for res in results if res[0] == socket.AF_INET]
|
|
44
|
-
|
|
45
|
-
self._original_getaddrinfo = socket.getaddrinfo
|
|
46
|
-
socket.getaddrinfo = ipv4_only_getaddrinfo
|
|
47
|
-
|
|
48
|
-
return []
|
|
49
|
-
|
|
50
|
-
def __del__(self) -> None:
|
|
51
|
-
"""Restores the original socket.getaddrinfo function upon adapter deletion."""
|
|
52
|
-
if hasattr(self, "_original_getaddrinfo"):
|
|
53
|
-
socket.getaddrinfo = self._original_getaddrinfo
|
|
54
|
-
|
|
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()
|
|
6
|
+
from .utils import is_loopback, _attempt_with_retries
|
|
7
|
+
from .utils import _get_json_standard, _get_json_force_ip
|
|
8
|
+
from .params import IPv4API
|
|
69
9
|
|
|
70
10
|
|
|
71
11
|
def is_ipv4(ip: str) -> bool:
|
|
@@ -96,8 +36,8 @@ def get_private_ipv4() -> Dict[str, Union[bool, Dict[str, str], str]]:
|
|
|
96
36
|
return {"status": False, "error": str(e)}
|
|
97
37
|
|
|
98
38
|
|
|
99
|
-
def _ip_sb_ipv4(geo: bool
|
|
100
|
-
|
|
39
|
+
def _ip_sb_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
40
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
101
41
|
"""
|
|
102
42
|
Get public IP and geolocation using ip.sb.
|
|
103
43
|
|
|
@@ -124,8 +64,8 @@ def _ip_sb_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
124
64
|
return {"status": False, "error": str(e)}
|
|
125
65
|
|
|
126
66
|
|
|
127
|
-
def _ipleak_net_ipv4(geo: bool
|
|
128
|
-
|
|
67
|
+
def _ipleak_net_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
68
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
129
69
|
"""
|
|
130
70
|
Get public IP and geolocation using ipleak.net.
|
|
131
71
|
|
|
@@ -152,8 +92,8 @@ def _ipleak_net_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
152
92
|
return {"status": False, "error": str(e)}
|
|
153
93
|
|
|
154
94
|
|
|
155
|
-
def _my_ip_io_ipv4(geo: bool
|
|
156
|
-
|
|
95
|
+
def _my_ip_io_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
96
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
157
97
|
"""
|
|
158
98
|
Get public IP and geolocation using my-ip.io.
|
|
159
99
|
|
|
@@ -180,8 +120,9 @@ def _my_ip_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
180
120
|
return {"status": False, "error": str(e)}
|
|
181
121
|
|
|
182
122
|
|
|
183
|
-
|
|
184
|
-
|
|
123
|
+
# very low rate limit
|
|
124
|
+
def _ifconfig_co_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
125
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
185
126
|
"""
|
|
186
127
|
Get public IP and geolocation using ifconfig.co.
|
|
187
128
|
|
|
@@ -189,7 +130,7 @@ def _ifconfig_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]
|
|
|
189
130
|
:param timeout: timeout value for API
|
|
190
131
|
"""
|
|
191
132
|
try:
|
|
192
|
-
data =
|
|
133
|
+
data = _get_json_force_ip(url="https://ifconfig.co/json", timeout=timeout, version="ipv4")
|
|
193
134
|
result = {"status": True, "data": {"ip": data["ip"], "api": "ifconfig.co"}}
|
|
194
135
|
if geo:
|
|
195
136
|
geo_data = {
|
|
@@ -208,8 +149,8 @@ def _ifconfig_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]
|
|
|
208
149
|
return {"status": False, "error": str(e)}
|
|
209
150
|
|
|
210
151
|
|
|
211
|
-
def _ipapi_co_ipv4(geo: bool
|
|
212
|
-
|
|
152
|
+
def _ipapi_co_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
153
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
213
154
|
"""
|
|
214
155
|
Get public IP and geolocation using ipapi.co.
|
|
215
156
|
|
|
@@ -217,7 +158,7 @@ def _ipapi_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
217
158
|
:param timeout: timeout value for API
|
|
218
159
|
"""
|
|
219
160
|
try:
|
|
220
|
-
data =
|
|
161
|
+
data = _get_json_force_ip(url="https://ipapi.co/json/", timeout=timeout, version="ipv4")
|
|
221
162
|
result = {"status": True, "data": {"ip": data["ip"], "api": "ipapi.co"}}
|
|
222
163
|
if geo:
|
|
223
164
|
geo_data = {
|
|
@@ -236,8 +177,8 @@ def _ipapi_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
236
177
|
return {"status": False, "error": str(e)}
|
|
237
178
|
|
|
238
179
|
|
|
239
|
-
def _ip_api_com_ipv4(geo: bool
|
|
240
|
-
|
|
180
|
+
def _ip_api_com_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
181
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
241
182
|
"""
|
|
242
183
|
Get public IP and geolocation using ip-api.com.
|
|
243
184
|
|
|
@@ -245,7 +186,7 @@ def _ip_api_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
245
186
|
:param timeout: timeout value for API
|
|
246
187
|
"""
|
|
247
188
|
try:
|
|
248
|
-
data =
|
|
189
|
+
data = _get_json_force_ip(url="http://ip-api.com/json/", timeout=timeout, version="ipv4")
|
|
249
190
|
if data.get("status") != "success":
|
|
250
191
|
return {"status": False, "error": "ip-api lookup failed"}
|
|
251
192
|
result = {"status": True, "data": {"ip": data["query"], "api": "ip-api.com"}}
|
|
@@ -266,8 +207,8 @@ def _ip_api_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
266
207
|
return {"status": False, "error": str(e)}
|
|
267
208
|
|
|
268
209
|
|
|
269
|
-
def _ipinfo_io_ipv4(geo: bool
|
|
270
|
-
|
|
210
|
+
def _ipinfo_io_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
211
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
271
212
|
"""
|
|
272
213
|
Get public IP and geolocation using ipinfo.io.
|
|
273
214
|
|
|
@@ -275,7 +216,7 @@ def _ipinfo_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
275
216
|
:param timeout: timeout value for API
|
|
276
217
|
"""
|
|
277
218
|
try:
|
|
278
|
-
data =
|
|
219
|
+
data = _get_json_force_ip(url="https://ipinfo.io/json", timeout=timeout, version="ipv4")
|
|
279
220
|
result = {"status": True, "data": {"ip": data["ip"], "api": "ipinfo.io"}}
|
|
280
221
|
if geo:
|
|
281
222
|
loc = data.get("loc", "").split(",")
|
|
@@ -295,8 +236,8 @@ def _ipinfo_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
295
236
|
return {"status": False, "error": str(e)}
|
|
296
237
|
|
|
297
238
|
|
|
298
|
-
def _reallyfreegeoip_org_ipv4(
|
|
299
|
-
|
|
239
|
+
def _reallyfreegeoip_org_ipv4(
|
|
240
|
+
geo: bool, timeout: Union[float, Tuple[float, float]]) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
300
241
|
"""
|
|
301
242
|
Get public IP and geolocation using reallyfreegeoip.org.
|
|
302
243
|
|
|
@@ -304,7 +245,7 @@ def _reallyfreegeoip_org_ipv4(geo: bool=False, timeout: Union[float, Tuple[float
|
|
|
304
245
|
:param timeout: timeout value for API
|
|
305
246
|
"""
|
|
306
247
|
try:
|
|
307
|
-
data =
|
|
248
|
+
data = _get_json_force_ip(url="https://reallyfreegeoip.org/json/", timeout=timeout, version="ipv4")
|
|
308
249
|
result = {"status": True, "data": {"ip": data["ip"], "api": "reallyfreegeoip.org"}}
|
|
309
250
|
if geo:
|
|
310
251
|
geo_data = {
|
|
@@ -323,8 +264,8 @@ def _reallyfreegeoip_org_ipv4(geo: bool=False, timeout: Union[float, Tuple[float
|
|
|
323
264
|
return {"status": False, "error": str(e)}
|
|
324
265
|
|
|
325
266
|
|
|
326
|
-
def _ident_me_ipv4(geo: bool
|
|
327
|
-
|
|
267
|
+
def _ident_me_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
268
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
328
269
|
"""
|
|
329
270
|
Get public IP and geolocation using ident.me.
|
|
330
271
|
|
|
@@ -351,8 +292,8 @@ def _ident_me_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
351
292
|
return {"status": False, "error": str(e)}
|
|
352
293
|
|
|
353
294
|
|
|
354
|
-
def _tnedi_me_ipv4(geo: bool
|
|
355
|
-
|
|
295
|
+
def _tnedi_me_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
296
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
356
297
|
"""
|
|
357
298
|
Get public IP and geolocation using tnedi.me.
|
|
358
299
|
|
|
@@ -379,7 +320,7 @@ def _tnedi_me_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
379
320
|
return {"status": False, "error": str(e)}
|
|
380
321
|
|
|
381
322
|
|
|
382
|
-
def _myip_la_ipv4(geo: bool
|
|
323
|
+
def _myip_la_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
383
324
|
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
384
325
|
"""
|
|
385
326
|
Get public IP and geolocation using myip.la.
|
|
@@ -388,7 +329,7 @@ def _myip_la_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
|
|
|
388
329
|
:param timeout: timeout value for API
|
|
389
330
|
"""
|
|
390
331
|
try:
|
|
391
|
-
data =
|
|
332
|
+
data = _get_json_force_ip(url="https://api.myip.la/en?json", timeout=timeout, version="ipv4")
|
|
392
333
|
result = {"status": True, "data": {"ip": data["ip"], "api": "myip.la"}}
|
|
393
334
|
if geo:
|
|
394
335
|
loc = data.get("location", {})
|
|
@@ -408,7 +349,7 @@ def _myip_la_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
|
|
|
408
349
|
return {"status": False, "error": str(e)}
|
|
409
350
|
|
|
410
351
|
|
|
411
|
-
def _freeipapi_com_ipv4(geo: bool
|
|
352
|
+
def _freeipapi_com_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
412
353
|
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
413
354
|
"""
|
|
414
355
|
Get public IP and geolocation using freeipapi.com.
|
|
@@ -417,8 +358,9 @@ def _freeipapi_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, floa
|
|
|
417
358
|
:param timeout: timeout value for API
|
|
418
359
|
"""
|
|
419
360
|
try:
|
|
420
|
-
data =
|
|
361
|
+
data = _get_json_force_ip(url="https://freeipapi.com/api/json", timeout=timeout, version="ipv4")
|
|
421
362
|
result = {"status": True, "data": {"ip": data["ipAddress"], "api": "freeipapi.com"}}
|
|
363
|
+
tzs = data.get("timeZones", [])
|
|
422
364
|
if geo:
|
|
423
365
|
geo_data = {
|
|
424
366
|
"city": data.get("cityName"),
|
|
@@ -427,8 +369,8 @@ def _freeipapi_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, floa
|
|
|
427
369
|
"country_code": data.get("countryCode"),
|
|
428
370
|
"latitude": data.get("latitude"),
|
|
429
371
|
"longitude": data.get("longitude"),
|
|
430
|
-
"organization":
|
|
431
|
-
"timezone":
|
|
372
|
+
"organization": data.get("asnOrganization"),
|
|
373
|
+
"timezone": tzs[0] if len(tzs) > 0 else None
|
|
432
374
|
}
|
|
433
375
|
result["data"].update(geo_data)
|
|
434
376
|
return result
|
|
@@ -436,7 +378,7 @@ def _freeipapi_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, floa
|
|
|
436
378
|
return {"status": False, "error": str(e)}
|
|
437
379
|
|
|
438
380
|
|
|
439
|
-
def _ipquery_io_ipv4(geo: bool
|
|
381
|
+
def _ipquery_io_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
440
382
|
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
441
383
|
"""
|
|
442
384
|
Get public IP and geolocation using ipquery.io.
|
|
@@ -445,7 +387,7 @@ def _ipquery_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
445
387
|
:param timeout: timeout value for API
|
|
446
388
|
"""
|
|
447
389
|
try:
|
|
448
|
-
data =
|
|
390
|
+
data = _get_json_force_ip(url="https://api.ipquery.io/?format=json", timeout=timeout, version="ipv4")
|
|
449
391
|
result = {"status": True, "data": {"ip": data["ip"], "api": "ipquery.io"}}
|
|
450
392
|
if geo:
|
|
451
393
|
loc = data.get("location", {})
|
|
@@ -466,7 +408,7 @@ def _ipquery_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
466
408
|
return {"status": False, "error": str(e)}
|
|
467
409
|
|
|
468
410
|
|
|
469
|
-
def _ipwho_is_ipv4(geo: bool
|
|
411
|
+
def _ipwho_is_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
470
412
|
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
471
413
|
"""
|
|
472
414
|
Get public IP and geolocation using ipwho.is.
|
|
@@ -475,7 +417,7 @@ def _ipwho_is_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
|
|
|
475
417
|
:param timeout: timeout value for API
|
|
476
418
|
"""
|
|
477
419
|
try:
|
|
478
|
-
data =
|
|
420
|
+
data = _get_json_force_ip(url="https://ipwho.is", timeout=timeout, version="ipv4")
|
|
479
421
|
result = {"status": True, "data": {"ip": data["ip"], "api": "ipwho.is"}}
|
|
480
422
|
if geo:
|
|
481
423
|
connection = data.get("connection", {})
|
|
@@ -496,7 +438,7 @@ def _ipwho_is_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
|
|
|
496
438
|
return {"status": False, "error": str(e)}
|
|
497
439
|
|
|
498
440
|
|
|
499
|
-
def _wtfismyip_com_ipv4(geo: bool
|
|
441
|
+
def _wtfismyip_com_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
500
442
|
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
501
443
|
"""
|
|
502
444
|
Get public IP and geolocation using wtfismyip.com.
|
|
@@ -606,7 +548,8 @@ IPV4_API_MAP = {
|
|
|
606
548
|
def get_public_ipv4(api: IPv4API=IPv4API.AUTO_SAFE, geo: bool=False,
|
|
607
549
|
timeout: Union[float, Tuple[float, float]]=5,
|
|
608
550
|
max_retries: int = 0,
|
|
609
|
-
retry_delay: float = 1.0
|
|
551
|
+
retry_delay: float = 1.0,
|
|
552
|
+
backoff_factor: float = 1.0) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
610
553
|
"""
|
|
611
554
|
Get public IPv4 and geolocation info based on the selected API.
|
|
612
555
|
|
|
@@ -615,6 +558,7 @@ def get_public_ipv4(api: IPv4API=IPv4API.AUTO_SAFE, geo: bool=False,
|
|
|
615
558
|
:param timeout: timeout value for API
|
|
616
559
|
:param max_retries: number of retries
|
|
617
560
|
:param retry_delay: delay between retries (in seconds)
|
|
561
|
+
:param backoff_factor: backoff factor
|
|
618
562
|
"""
|
|
619
563
|
if api in [IPv4API.AUTO, IPv4API.AUTO_SAFE]:
|
|
620
564
|
for _, api_data in IPV4_API_MAP.items():
|
|
@@ -625,6 +569,7 @@ def get_public_ipv4(api: IPv4API=IPv4API.AUTO_SAFE, geo: bool=False,
|
|
|
625
569
|
func=func,
|
|
626
570
|
max_retries=max_retries,
|
|
627
571
|
retry_delay=retry_delay,
|
|
572
|
+
backoff_factor=backoff_factor,
|
|
628
573
|
geo=geo,
|
|
629
574
|
timeout=timeout)
|
|
630
575
|
if result["status"]:
|
|
@@ -638,6 +583,7 @@ def get_public_ipv4(api: IPv4API=IPv4API.AUTO_SAFE, geo: bool=False,
|
|
|
638
583
|
func=func,
|
|
639
584
|
max_retries=max_retries,
|
|
640
585
|
retry_delay=retry_delay,
|
|
586
|
+
backoff_factor=backoff_factor,
|
|
641
587
|
geo=geo,
|
|
642
588
|
timeout=timeout)
|
|
643
589
|
return {"status": False, "error": "Unsupported API: {api}".format(api=api)}
|
ipspot/ipv6.py
CHANGED
|
@@ -4,7 +4,8 @@ import ipaddress
|
|
|
4
4
|
import socket
|
|
5
5
|
from typing import Union, Dict, List, Tuple
|
|
6
6
|
from .params import IPv6API
|
|
7
|
-
from .utils import is_loopback,
|
|
7
|
+
from .utils import is_loopback, _attempt_with_retries
|
|
8
|
+
from .utils import _get_json_standard, _get_json_force_ip
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def is_ipv6(ip: str) -> bool:
|
|
@@ -36,8 +37,8 @@ def get_private_ipv6() -> Dict[str, Union[bool, Dict[str, str], str]]:
|
|
|
36
37
|
return {"status": False, "error": str(e)}
|
|
37
38
|
|
|
38
39
|
|
|
39
|
-
def _ip_sb_ipv6(geo: bool
|
|
40
|
-
|
|
40
|
+
def _ip_sb_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
41
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
41
42
|
"""
|
|
42
43
|
Get public IP and geolocation using ip.sb.
|
|
43
44
|
|
|
@@ -64,8 +65,8 @@ def _ip_sb_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
64
65
|
return {"status": False, "error": str(e)}
|
|
65
66
|
|
|
66
67
|
|
|
67
|
-
def _ident_me_ipv6(geo: bool
|
|
68
|
-
|
|
68
|
+
def _ident_me_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
69
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
69
70
|
"""
|
|
70
71
|
Get public IP and geolocation using ident.me.
|
|
71
72
|
|
|
@@ -92,8 +93,8 @@ def _ident_me_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
92
93
|
return {"status": False, "error": str(e)}
|
|
93
94
|
|
|
94
95
|
|
|
95
|
-
def _tnedi_me_ipv6(geo: bool
|
|
96
|
-
|
|
96
|
+
def _tnedi_me_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
97
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
97
98
|
"""
|
|
98
99
|
Get public IP and geolocation using tnedi.me.
|
|
99
100
|
|
|
@@ -120,8 +121,8 @@ def _tnedi_me_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
120
121
|
return {"status": False, "error": str(e)}
|
|
121
122
|
|
|
122
123
|
|
|
123
|
-
def _ipleak_net_ipv6(geo: bool
|
|
124
|
-
|
|
124
|
+
def _ipleak_net_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
125
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
125
126
|
"""
|
|
126
127
|
Get public IP and geolocation using ipleak.net.
|
|
127
128
|
|
|
@@ -148,8 +149,8 @@ def _ipleak_net_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
148
149
|
return {"status": False, "error": str(e)}
|
|
149
150
|
|
|
150
151
|
|
|
151
|
-
def _my_ip_io_ipv6(geo: bool
|
|
152
|
-
|
|
152
|
+
def _my_ip_io_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
153
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
153
154
|
"""
|
|
154
155
|
Get public IP and geolocation using my-ip.io.
|
|
155
156
|
|
|
@@ -176,6 +177,121 @@ def _my_ip_io_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
176
177
|
return {"status": False, "error": str(e)}
|
|
177
178
|
|
|
178
179
|
|
|
180
|
+
# very low rate limit
|
|
181
|
+
def _ifconfig_co_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
182
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
183
|
+
"""
|
|
184
|
+
Get public IP and geolocation using ifconfig.co.
|
|
185
|
+
|
|
186
|
+
:param geo: geolocation flag
|
|
187
|
+
:param timeout: timeout value for API
|
|
188
|
+
"""
|
|
189
|
+
try:
|
|
190
|
+
data = _get_json_force_ip(url="https://ifconfig.co/json", timeout=timeout, version="ipv6")
|
|
191
|
+
result = {"status": True, "data": {"ip": data["ip"], "api": "ifconfig.co"}}
|
|
192
|
+
if geo:
|
|
193
|
+
geo_data = {
|
|
194
|
+
"city": data.get("city"),
|
|
195
|
+
"region": data.get("region_name"),
|
|
196
|
+
"country": data.get("country"),
|
|
197
|
+
"country_code": data.get("country_iso"),
|
|
198
|
+
"latitude": data.get("latitude"),
|
|
199
|
+
"longitude": data.get("longitude"),
|
|
200
|
+
"organization": data.get("asn_org"),
|
|
201
|
+
"timezone": data.get("time_zone")
|
|
202
|
+
}
|
|
203
|
+
result["data"].update(geo_data)
|
|
204
|
+
return result
|
|
205
|
+
except Exception as e:
|
|
206
|
+
return {"status": False, "error": str(e)}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _reallyfreegeoip_org_ipv6(
|
|
210
|
+
geo: bool, timeout: Union[float, Tuple[float, float]]) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
211
|
+
"""
|
|
212
|
+
Get public IP and geolocation using reallyfreegeoip.org.
|
|
213
|
+
|
|
214
|
+
:param geo: geolocation flag
|
|
215
|
+
:param timeout: timeout value for API
|
|
216
|
+
"""
|
|
217
|
+
try:
|
|
218
|
+
data = _get_json_force_ip(url="https://reallyfreegeoip.org/json/", timeout=timeout, version="ipv6")
|
|
219
|
+
result = {"status": True, "data": {"ip": data["ip"], "api": "reallyfreegeoip.org"}}
|
|
220
|
+
if geo:
|
|
221
|
+
geo_data = {
|
|
222
|
+
"city": data.get("city"),
|
|
223
|
+
"region": data.get("region_name"),
|
|
224
|
+
"country": data.get("country_name"),
|
|
225
|
+
"country_code": data.get("country_code"),
|
|
226
|
+
"latitude": data.get("latitude"),
|
|
227
|
+
"longitude": data.get("longitude"),
|
|
228
|
+
"organization": None, # does not provide organization
|
|
229
|
+
"timezone": data.get("time_zone")
|
|
230
|
+
}
|
|
231
|
+
result["data"].update(geo_data)
|
|
232
|
+
return result
|
|
233
|
+
except Exception as e:
|
|
234
|
+
return {"status": False, "error": str(e)}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _myip_la_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
238
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
239
|
+
"""
|
|
240
|
+
Get public IP and geolocation using myip.la.
|
|
241
|
+
|
|
242
|
+
:param geo: geolocation flag
|
|
243
|
+
:param timeout: timeout value for API
|
|
244
|
+
"""
|
|
245
|
+
try:
|
|
246
|
+
data = _get_json_force_ip(url="https://api.myip.la/en?json", timeout=timeout, version="ipv6")
|
|
247
|
+
result = {"status": True, "data": {"ip": data["ip"], "api": "myip.la"}}
|
|
248
|
+
if geo:
|
|
249
|
+
loc = data.get("location", {})
|
|
250
|
+
geo_data = {
|
|
251
|
+
"city": loc.get("city"),
|
|
252
|
+
"region": loc.get("province"),
|
|
253
|
+
"country": loc.get("country_name"),
|
|
254
|
+
"country_code": loc.get("country_code"),
|
|
255
|
+
"latitude": float(loc.get("latitude")) if loc.get("latitude") else None,
|
|
256
|
+
"longitude": float(loc.get("longitude")) if loc.get("longitude") else None,
|
|
257
|
+
"organization": None,
|
|
258
|
+
"timezone": None
|
|
259
|
+
}
|
|
260
|
+
result["data"].update(geo_data)
|
|
261
|
+
return result
|
|
262
|
+
except Exception as e:
|
|
263
|
+
return {"status": False, "error": str(e)}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _freeipapi_com_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
|
|
267
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
268
|
+
"""
|
|
269
|
+
Get public IP and geolocation using freeipapi.com.
|
|
270
|
+
|
|
271
|
+
:param geo: geolocation flag
|
|
272
|
+
:param timeout: timeout value for API
|
|
273
|
+
"""
|
|
274
|
+
try:
|
|
275
|
+
data = _get_json_force_ip(url="https://free.freeipapi.com/api/json", timeout=timeout, version="ipv6")
|
|
276
|
+
result = {"status": True, "data": {"ip": data["ipAddress"], "api": "freeipapi.com"}}
|
|
277
|
+
tzs = data.get("timeZones", [])
|
|
278
|
+
if geo:
|
|
279
|
+
geo_data = {
|
|
280
|
+
"city": data.get("cityName"),
|
|
281
|
+
"region": data.get("regionName"),
|
|
282
|
+
"country": data.get("countryName"),
|
|
283
|
+
"country_code": data.get("countryCode"),
|
|
284
|
+
"latitude": data.get("latitude"),
|
|
285
|
+
"longitude": data.get("longitude"),
|
|
286
|
+
"organization": data.get("asnOrganization"),
|
|
287
|
+
"timezone": tzs[0] if len(tzs) > 0 else None
|
|
288
|
+
}
|
|
289
|
+
result["data"].update(geo_data)
|
|
290
|
+
return result
|
|
291
|
+
except Exception as e:
|
|
292
|
+
return {"status": False, "error": str(e)}
|
|
293
|
+
|
|
294
|
+
|
|
179
295
|
IPV6_API_MAP = {
|
|
180
296
|
IPv6API.IP_SB: {
|
|
181
297
|
"thread_safe": True,
|
|
@@ -202,13 +318,34 @@ IPV6_API_MAP = {
|
|
|
202
318
|
"geo": True,
|
|
203
319
|
"function": _my_ip_io_ipv6
|
|
204
320
|
},
|
|
321
|
+
IPv6API.IFCONFIG_CO: {
|
|
322
|
+
"thread_safe": False,
|
|
323
|
+
"geo": True,
|
|
324
|
+
"function": _ifconfig_co_ipv6
|
|
325
|
+
},
|
|
326
|
+
IPv6API.REALLYFREEGEOIP_ORG: {
|
|
327
|
+
"thread_safe": False,
|
|
328
|
+
"geo": True,
|
|
329
|
+
"function": _reallyfreegeoip_org_ipv6
|
|
330
|
+
},
|
|
331
|
+
IPv6API.MYIP_LA: {
|
|
332
|
+
"thread_safe": False,
|
|
333
|
+
"geo": True,
|
|
334
|
+
"function": _myip_la_ipv6
|
|
335
|
+
},
|
|
336
|
+
IPv6API.FREEIPAPI_COM: {
|
|
337
|
+
"thread_safe": False,
|
|
338
|
+
"geo": True,
|
|
339
|
+
"function": _freeipapi_com_ipv6
|
|
340
|
+
}
|
|
205
341
|
}
|
|
206
342
|
|
|
207
343
|
|
|
208
344
|
def get_public_ipv6(api: IPv6API=IPv6API.AUTO_SAFE, geo: bool=False,
|
|
209
345
|
timeout: Union[float, Tuple[float, float]]=5,
|
|
210
346
|
max_retries: int = 0,
|
|
211
|
-
retry_delay: float = 1.0
|
|
347
|
+
retry_delay: float = 1.0,
|
|
348
|
+
backoff_factor: float = 1.0) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
212
349
|
"""
|
|
213
350
|
Get public IPv6 and geolocation info based on the selected API.
|
|
214
351
|
|
|
@@ -217,6 +354,7 @@ def get_public_ipv6(api: IPv6API=IPv6API.AUTO_SAFE, geo: bool=False,
|
|
|
217
354
|
:param timeout: timeout value for API
|
|
218
355
|
:param max_retries: number of retries
|
|
219
356
|
:param retry_delay: delay between retries (in seconds)
|
|
357
|
+
:param backoff_factor: backoff factor
|
|
220
358
|
"""
|
|
221
359
|
if api in [IPv6API.AUTO, IPv6API.AUTO_SAFE]:
|
|
222
360
|
for _, api_data in IPV6_API_MAP.items():
|
|
@@ -227,6 +365,7 @@ def get_public_ipv6(api: IPv6API=IPv6API.AUTO_SAFE, geo: bool=False,
|
|
|
227
365
|
func=func,
|
|
228
366
|
max_retries=max_retries,
|
|
229
367
|
retry_delay=retry_delay,
|
|
368
|
+
backoff_factor=backoff_factor,
|
|
230
369
|
geo=geo,
|
|
231
370
|
timeout=timeout)
|
|
232
371
|
if result["status"]:
|
|
@@ -240,6 +379,7 @@ def get_public_ipv6(api: IPv6API=IPv6API.AUTO_SAFE, geo: bool=False,
|
|
|
240
379
|
func=func,
|
|
241
380
|
max_retries=max_retries,
|
|
242
381
|
retry_delay=retry_delay,
|
|
382
|
+
backoff_factor=backoff_factor,
|
|
243
383
|
geo=geo,
|
|
244
384
|
timeout=timeout)
|
|
245
385
|
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.
|
|
5
|
+
IPSPOT_VERSION = "0.7"
|
|
6
6
|
|
|
7
7
|
IPSPOT_OVERVIEW = '''
|
|
8
8
|
IPSpot is a Python library for retrieving the current system's IP address and location information.
|
|
@@ -50,6 +50,10 @@ class IPv6API(Enum):
|
|
|
50
50
|
TNEDI_ME = "tnedi.me"
|
|
51
51
|
IPLEAK_NET = "ipleak.net"
|
|
52
52
|
MY_IP_IO = "my-ip.io"
|
|
53
|
+
IFCONFIG_CO = "ifconfig.co"
|
|
54
|
+
REALLYFREEGEOIP_ORG = "reallyfreegeoip.org"
|
|
55
|
+
MYIP_LA = "myip.la"
|
|
56
|
+
FREEIPAPI_COM = "freeipapi.com"
|
|
53
57
|
|
|
54
58
|
|
|
55
59
|
PARAMETERS_NAME_MAP = {
|
|
@@ -64,3 +68,8 @@ PARAMETERS_NAME_MAP = {
|
|
|
64
68
|
"organization": "Organization",
|
|
65
69
|
"api": "API"
|
|
66
70
|
}
|
|
71
|
+
|
|
72
|
+
PUBLIC_IPV4_ERROR = "Unable to retrieve public IPv4 information."
|
|
73
|
+
PRIVATE_IPV4_ERROR = "Unable to retrieve private IPv4 address."
|
|
74
|
+
PUBLIC_IPV6_ERROR = "Unable to retrieve public IPv6 information."
|
|
75
|
+
PRIVATE_IPV6_ERROR = "Unable to retrieve private IPv6 address."
|
ipspot/utils.py
CHANGED
|
@@ -2,31 +2,99 @@
|
|
|
2
2
|
"""ipspot utils."""
|
|
3
3
|
import time
|
|
4
4
|
import ipaddress
|
|
5
|
+
import socket
|
|
5
6
|
import requests
|
|
7
|
+
from requests.adapters import HTTPAdapter
|
|
6
8
|
from typing import Callable, Dict
|
|
7
|
-
from typing import Union, Tuple, Any
|
|
9
|
+
from typing import Union, Tuple, Any, List
|
|
8
10
|
from .params import REQUEST_HEADERS
|
|
9
11
|
|
|
10
12
|
|
|
13
|
+
class ForceIPHTTPAdapter(HTTPAdapter):
|
|
14
|
+
"""A custom HTTPAdapter that enforces IPv4 or IPv6 DNS resolution for HTTP(S) requests."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, version: str = "ipv4", *args: list, **kwargs: dict) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Initialize the adapter with the desired IP version.
|
|
19
|
+
|
|
20
|
+
:param version: 'ipv4' or 'ipv6' to select address family
|
|
21
|
+
:param args: additional list arguments for the HTTPAdapter
|
|
22
|
+
:param kwargs: additional keyword arguments for the HTTPAdapter
|
|
23
|
+
"""
|
|
24
|
+
self.version = version.lower()
|
|
25
|
+
if self.version not in ("ipv4", "ipv6"):
|
|
26
|
+
raise ValueError("version must be either 'ipv4' or 'ipv6'")
|
|
27
|
+
super().__init__(*args, **kwargs)
|
|
28
|
+
|
|
29
|
+
def send(self, *args: list, **kwargs: dict) -> Any:
|
|
30
|
+
"""
|
|
31
|
+
Override send method to apply the monkey patch only during the request.
|
|
32
|
+
|
|
33
|
+
:param args: additional list arguments for the send method
|
|
34
|
+
:param kwargs: additional keyword arguments for the send method
|
|
35
|
+
"""
|
|
36
|
+
family = socket.AF_INET if self.version == "ipv4" else socket.AF_INET6
|
|
37
|
+
original_getaddrinfo = socket.getaddrinfo
|
|
38
|
+
|
|
39
|
+
def filtered_getaddrinfo(*gargs: list, **gkwargs: dict) -> List[Tuple]:
|
|
40
|
+
"""
|
|
41
|
+
Filter getaddrinfo.
|
|
42
|
+
|
|
43
|
+
:param gargs: additional list arguments for the original_getaddrinfo function
|
|
44
|
+
:param gkwargs: additional keyword arguments for the original_getaddrinfo function
|
|
45
|
+
"""
|
|
46
|
+
results = original_getaddrinfo(*gargs, **gkwargs)
|
|
47
|
+
return [res for res in results if res[0] == family]
|
|
48
|
+
|
|
49
|
+
socket.getaddrinfo = filtered_getaddrinfo
|
|
50
|
+
try:
|
|
51
|
+
response = super().send(*args, **kwargs)
|
|
52
|
+
finally:
|
|
53
|
+
socket.getaddrinfo = original_getaddrinfo
|
|
54
|
+
return response
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _get_json_force_ip(url: str, timeout: Union[float, Tuple[float, float]],
|
|
58
|
+
version: str = "ipv4") -> dict:
|
|
59
|
+
"""
|
|
60
|
+
Send GET request with forced IPv4/IPv6 using ForceIPHTTPAdapter that returns JSON response.
|
|
61
|
+
|
|
62
|
+
:param url: API url
|
|
63
|
+
:param timeout: timeout value for API
|
|
64
|
+
:param version: 'ipv4' or 'ipv6' to select address family
|
|
65
|
+
"""
|
|
66
|
+
with requests.Session() as session:
|
|
67
|
+
session.mount("http://", ForceIPHTTPAdapter(version=version))
|
|
68
|
+
session.mount("https://", ForceIPHTTPAdapter(version=version))
|
|
69
|
+
response = session.get(url, headers=REQUEST_HEADERS, timeout=timeout)
|
|
70
|
+
response.raise_for_status()
|
|
71
|
+
return response.json()
|
|
72
|
+
|
|
73
|
+
|
|
11
74
|
def _attempt_with_retries(
|
|
12
75
|
func: Callable,
|
|
13
76
|
max_retries: int,
|
|
14
|
-
retry_delay: float,
|
|
77
|
+
retry_delay: float,
|
|
78
|
+
backoff_factor: float,
|
|
79
|
+
**kwargs: dict) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
15
80
|
"""
|
|
16
81
|
Attempt a function call with retries and delay.
|
|
17
82
|
|
|
18
83
|
:param func: function to execute
|
|
19
84
|
:param max_retries: number of retries
|
|
20
85
|
:param retry_delay: delay between retries (in seconds)
|
|
86
|
+
:param backoff_factor: backoff factor
|
|
21
87
|
:param kwargs: keyword arguments to pass to the function
|
|
22
88
|
"""
|
|
23
89
|
max_retries = max(0, max_retries)
|
|
24
90
|
result = {"status": False, "error": ""}
|
|
91
|
+
next_delay = retry_delay
|
|
25
92
|
for attempt in range(max_retries + 1):
|
|
26
93
|
result = func(**kwargs)
|
|
27
94
|
if result["status"]:
|
|
28
95
|
break
|
|
29
|
-
time.sleep(
|
|
96
|
+
time.sleep(next_delay)
|
|
97
|
+
next_delay *= backoff_factor
|
|
30
98
|
return result
|
|
31
99
|
|
|
32
100
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipspot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7
|
|
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.
|
|
6
|
+
Download-URL: https://github.com/openscilab/ipspot/tarball/v0.7
|
|
7
7
|
Author: IPSpot Development Team
|
|
8
8
|
Author-email: ipspot@openscilab.com
|
|
9
9
|
License: MIT
|
|
10
10
|
Project-URL: Source, https://github.com/openscilab/ipspot
|
|
11
11
|
Keywords: ip ipv4 geo geolocation network location ipspot cli
|
|
12
|
-
Classifier: Development Status ::
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Natural Language :: English
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
24
|
Classifier: Intended Audience :: Developers
|
|
24
25
|
Classifier: Intended Audience :: Education
|
|
25
26
|
Classifier: Intended Audience :: End Users/Desktop
|
|
@@ -52,6 +53,7 @@ Dynamic: summary
|
|
|
52
53
|
<img src="https://github.com/openscilab/ipspot/raw/main/otherfiles/logo.png" width="350">
|
|
53
54
|
<h1>IPSpot: A Python Tool to Fetch the System's IP Address</h1>
|
|
54
55
|
<br/>
|
|
56
|
+
<a href="https://codecov.io/gh/openscilab/ipspot"><img src="https://codecov.io/gh/openscilab/ipspot/graph/badge.svg?token=XCFKASULS8"></a>
|
|
55
57
|
<a href="https://badge.fury.io/py/ipspot"><img src="https://badge.fury.io/py/ipspot.svg" alt="PyPI version"></a>
|
|
56
58
|
<a href="https://www.python.org/"><img src="https://img.shields.io/badge/built%20with-Python3-green.svg" alt="built with Python3"></a>
|
|
57
59
|
<a href="https://github.com/openscilab/ipspot"><img alt="GitHub repo size" src="https://img.shields.io/github/repo-size/openscilab/ipspot"></a>
|
|
@@ -102,13 +104,13 @@ Dynamic: summary
|
|
|
102
104
|
## Installation
|
|
103
105
|
|
|
104
106
|
### Source Code
|
|
105
|
-
- Download [Version 0.
|
|
107
|
+
- Download [Version 0.7](https://github.com/openscilab/ipspot/archive/v0.7.zip) or [Latest Source](https://github.com/openscilab/ipspot/archive/dev.zip)
|
|
106
108
|
- `pip install .`
|
|
107
109
|
|
|
108
110
|
### PyPI
|
|
109
111
|
|
|
110
112
|
- Check [Python Packaging User Guide](https://packaging.python.org/installing/)
|
|
111
|
-
- `pip install ipspot==0.
|
|
113
|
+
- `pip install ipspot==0.7`
|
|
112
114
|
|
|
113
115
|
|
|
114
116
|
## Usage
|
|
@@ -123,7 +125,7 @@ Dynamic: summary
|
|
|
123
125
|
{'status': True, 'data': {'ip': 'xx.xx.xx.xx', 'api': 'ip-api.com'}}
|
|
124
126
|
>>> get_public_ipv4(api=IPv4API.IP_API_COM, geo=True, timeout=10)
|
|
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
|
-
>>> get_public_ipv4(api=IPv4API.IP_API_COM, geo=True, timeout=10, max_retries=5, retry_delay=4)
|
|
128
|
+
>>> get_public_ipv4(api=IPv4API.IP_API_COM, geo=True, timeout=10, max_retries=5, retry_delay=4, backoff_factor=1.2)
|
|
127
129
|
{'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}
|
|
128
130
|
```
|
|
129
131
|
|
|
@@ -143,7 +145,7 @@ Dynamic: summary
|
|
|
143
145
|
{'data': {'api': 'ip.sb', 'ip': 'xx:xx:xx:xx::xx'}, 'status': True}
|
|
144
146
|
>>> get_public_ipv6(api=IPv6API.IP_SB, geo=True, timeout=10)
|
|
145
147
|
{'data': {'latitude': 51.2993, 'region': None, 'city': None, 'country_code': 'DE', 'api': 'ip.sb', 'longitude': 9.491, 'country': 'Germany', 'organization': 'Hetzner Online', 'timezone': 'Europe/Berlin', 'ip': 'xx:xx:xx:xx::xx'}, 'status': True}
|
|
146
|
-
>>> get_public_ipv6(api=IPv6API.IP_SB, geo=True, timeout=10, max_retries=5, retry_delay=4)
|
|
148
|
+
>>> get_public_ipv6(api=IPv6API.IP_SB, geo=True, timeout=10, max_retries=5, retry_delay=4, backoff_factor=1.2)
|
|
147
149
|
{'data': {'latitude': 51.2993, 'region': None, 'city': None, 'country_code': 'DE', 'api': 'ip.sb', 'longitude': 9.491, 'country': 'Germany', 'organization': 'Hetzner Online', 'timezone': 'Europe/Berlin', 'ip': 'xx:xx:xx:xx::xx'}, 'status': True}
|
|
148
150
|
```
|
|
149
151
|
|
|
@@ -164,7 +166,7 @@ Dynamic: summary
|
|
|
164
166
|
```console
|
|
165
167
|
> ipspot --version
|
|
166
168
|
|
|
167
|
-
0.
|
|
169
|
+
0.7
|
|
168
170
|
```
|
|
169
171
|
|
|
170
172
|
#### Info
|
|
@@ -172,18 +174,18 @@ Dynamic: summary
|
|
|
172
174
|
```console
|
|
173
175
|
> ipspot --info
|
|
174
176
|
|
|
175
|
-
___ ____ ____ _
|
|
176
|
-
|_ _|| _ \ / ___| _ __ ___ | |_
|
|
177
|
+
___ ____ ____ _
|
|
178
|
+
|_ _|| _ \ / ___| _ __ ___ | |_
|
|
177
179
|
| | | |_) |\___ \ | '_ \ / _ \ | __|
|
|
178
|
-
| | | __/ ___) || |_) || (_) || |_
|
|
180
|
+
| | | __/ ___) || |_) || (_) || |_
|
|
179
181
|
|___||_| |____/ | .__/ \___/ \__|
|
|
180
|
-
|_|
|
|
182
|
+
|_|
|
|
181
183
|
|
|
182
|
-
__ __ ___
|
|
183
|
-
\ \ / / _ / _ \ |
|
|
184
|
-
\ \ / / (_)| | | |
|
|
185
|
-
\ V / _ | |_| | _
|
|
186
|
-
\_/ (_) \___/ (_)
|
|
184
|
+
__ __ ___ _____
|
|
185
|
+
\ \ / / _ / _ \ |___ |
|
|
186
|
+
\ \ / / (_)| | | | / /
|
|
187
|
+
\ V / _ | |_| | _ / /
|
|
188
|
+
\_/ (_) \___/ (_) /_/
|
|
187
189
|
|
|
188
190
|
|
|
189
191
|
|
|
@@ -207,16 +209,31 @@ Private IP:
|
|
|
207
209
|
|
|
208
210
|
Public IP and Location Info:
|
|
209
211
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
212
|
+
IPv4:
|
|
213
|
+
|
|
214
|
+
API: ipinfo.io
|
|
215
|
+
City: Nuremberg
|
|
216
|
+
Country: Germany
|
|
217
|
+
Country Code: DE
|
|
218
|
+
IP: xx.xx.xx.xx
|
|
219
|
+
Latitude: 49.4527
|
|
220
|
+
Longitude: 11.0783
|
|
221
|
+
Organization: Hetzner Online GmbH
|
|
222
|
+
Region: Bavaria
|
|
223
|
+
Timezone: Europe/Berlin
|
|
224
|
+
|
|
225
|
+
IPv6:
|
|
226
|
+
|
|
227
|
+
API: ip.sb
|
|
228
|
+
City: N/A
|
|
229
|
+
Country: Germany
|
|
230
|
+
Country Code: DE
|
|
231
|
+
IP: xx:xx:xx:xx::xx
|
|
232
|
+
Latitude: 51.2993
|
|
233
|
+
Longitude: 9.491
|
|
234
|
+
Organization: Hetzner Online
|
|
235
|
+
Region: N/A
|
|
236
|
+
Timezone: Europe/Berlin
|
|
220
237
|
```
|
|
221
238
|
|
|
222
239
|
#### IPv4 API
|
|
@@ -264,7 +281,7 @@ Public IP and Location Info:
|
|
|
264
281
|
|
|
265
282
|
#### IPv6 API
|
|
266
283
|
|
|
267
|
-
ℹ️ `ipv6-api` valid choices: [`auto-safe`, `auto`, `ip.sb`, `ident.me`, `tnedi.me`, `ipleak.net`, `my-ip.io`]
|
|
284
|
+
ℹ️ `ipv6-api` valid choices: [`auto-safe`, `auto`, `ip.sb`, `ident.me`, `tnedi.me`, `ipleak.net`, `my-ip.io`, `ifconfig.co`, `reallyfreegeoip.org`, `myip.la`, `freeipapi.com`]
|
|
268
285
|
|
|
269
286
|
ℹ️ The default value: `auto-safe`
|
|
270
287
|
|
|
@@ -360,6 +377,31 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
|
360
377
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
361
378
|
|
|
362
379
|
## [Unreleased]
|
|
380
|
+
## [0.7] - 2025-12-09
|
|
381
|
+
### Added
|
|
382
|
+
- `--backoff-factor` argument
|
|
383
|
+
### Changed
|
|
384
|
+
- CLI messages updated
|
|
385
|
+
- `Python 3.14` added to `test.yml`
|
|
386
|
+
- Internal functions default values removed
|
|
387
|
+
- `README.md` updated
|
|
388
|
+
- Test system modified
|
|
389
|
+
- `ipspot_info` function renamed to `_print_ipspot_info`
|
|
390
|
+
- `display_ip_info` function renamed to `_print_report`
|
|
391
|
+
## [0.6] - 2025-11-18
|
|
392
|
+
### Added
|
|
393
|
+
- `ForceIPHTTPAdapter` class
|
|
394
|
+
- `_get_json_force_ip` function
|
|
395
|
+
- Support [ifconfig.co](https://ifconfig.co/json) IPv6 API
|
|
396
|
+
- Support [reallyfreegeoip.org](https://reallyfreegeoip.org/json/) IPv6 API
|
|
397
|
+
- Support [myip.la](https://api.myip.la/en?json) IPv6 API
|
|
398
|
+
- Support [freeipapi.com](https://freeipapi.com/api/json) IPv6 API
|
|
399
|
+
### Changed
|
|
400
|
+
- [freeipapi.com](https://freeipapi.com/api/json) IPv4 API bug fixed
|
|
401
|
+
- `README.md` updated
|
|
402
|
+
### Removed
|
|
403
|
+
- `IPv4HTTPAdapter` class
|
|
404
|
+
- `_get_json_ipv4_forced` function
|
|
363
405
|
## [0.5] - 2025-10-17
|
|
364
406
|
### Added
|
|
365
407
|
- `setup-warp` action
|
|
@@ -438,7 +480,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
438
480
|
- `--no-geo` argument
|
|
439
481
|
- Logo
|
|
440
482
|
|
|
441
|
-
[Unreleased]: https://github.com/openscilab/ipspot/compare/v0.
|
|
483
|
+
[Unreleased]: https://github.com/openscilab/ipspot/compare/v0.7...dev
|
|
484
|
+
[0.7]: https://github.com/openscilab/ipspot/compare/v0.6...v0.7
|
|
485
|
+
[0.6]: https://github.com/openscilab/ipspot/compare/v0.5...v0.6
|
|
442
486
|
[0.5]: https://github.com/openscilab/ipspot/compare/v0.4...v0.5
|
|
443
487
|
[0.4]: https://github.com/openscilab/ipspot/compare/v0.3...v0.4
|
|
444
488
|
[0.3]: https://github.com/openscilab/ipspot/compare/v0.2...v0.3
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
ipspot/__init__.py,sha256=B47uAOAidLkXps1A4zDig6NN2kB3HcdMC20UOrrEfB4,281
|
|
2
|
+
ipspot/__main__.py,sha256=xKHY_tc94SWktkIHV0O9YADAHPZvT8yXOFSm_L2yMno,105
|
|
3
|
+
ipspot/cli.py,sha256=Y48HA_s6SrTCtjn-iAFNMplAARLwBUx8zpxesG0hARE,4980
|
|
4
|
+
ipspot/ipv4.py,sha256=QGeVZWruziLwe0bxtvo7k0xrnY73-znsXtSsvELYdyE,21613
|
|
5
|
+
ipspot/ipv6.py,sha256=xkgKyTSP_3TTijeM0BftiKL1KlIqMtRucb16-2QrkzA,14081
|
|
6
|
+
ipspot/params.py,sha256=u8eEDVB40Fo52DRhD7-mPg30U62Brtc2aGi9ZhqYuC8,2142
|
|
7
|
+
ipspot/utils.py,sha256=W3HJ61_KVtdg4gWr4m9xXblQc_E30rJoF1aKk36Zqzg,4591
|
|
8
|
+
ipspot-0.7.dist-info/licenses/AUTHORS.md,sha256=5ZvxP1KnuVkurB4psQDSqnqiAyn44q1aeiXUsnYgps4,411
|
|
9
|
+
ipspot-0.7.dist-info/licenses/LICENSE,sha256=0aOd4wzZRoSH_35UZXRHS7alPFTtuFEBJrajLuonEIw,1067
|
|
10
|
+
ipspot-0.7.dist-info/METADATA,sha256=P0uWs_6IZgVWPXJd53rsfvrepgJwLUq7Y8v_FNTDzB0,15395
|
|
11
|
+
ipspot-0.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
ipspot-0.7.dist-info/entry_points.txt,sha256=DJVLepYr8H3UcvWekU5Jy-tcr_xmWrphzgWGNOd3hlk,43
|
|
13
|
+
ipspot-0.7.dist-info/top_level.txt,sha256=v0WgE1z2iCy_bXU53fVcllwHLTvGNBIvq8u3KPC2_Sc,7
|
|
14
|
+
ipspot-0.7.dist-info/RECORD,,
|
ipspot-0.5.dist-info/RECORD
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
ipspot/__init__.py,sha256=B47uAOAidLkXps1A4zDig6NN2kB3HcdMC20UOrrEfB4,281
|
|
2
|
-
ipspot/__main__.py,sha256=xKHY_tc94SWktkIHV0O9YADAHPZvT8yXOFSm_L2yMno,105
|
|
3
|
-
ipspot/cli.py,sha256=MdyMh-9miuT8zQgklVrvsimaXnqD17sviWRJbUWlk58,4637
|
|
4
|
-
ipspot/ipv4.py,sha256=-HGv-yAsFI56I06qNFDquVVGP9ZhfWwih1Vs3JtemZQ,23825
|
|
5
|
-
ipspot/ipv6.py,sha256=svGgydUUSoXeuT2hLgi5CDDy3n2PtV-b6uDvP5t_iKI,8754
|
|
6
|
-
ipspot/params.py,sha256=PVnz3F1jB5aIle_fScGoF-0MfREhMxcTCXGEXmimUQs,1741
|
|
7
|
-
ipspot/utils.py,sha256=YtkyWGljpFc72rSGljsARjlVrF9hkE7Gl78_5uex1yI,1870
|
|
8
|
-
ipspot-0.5.dist-info/licenses/AUTHORS.md,sha256=5ZvxP1KnuVkurB4psQDSqnqiAyn44q1aeiXUsnYgps4,411
|
|
9
|
-
ipspot-0.5.dist-info/licenses/LICENSE,sha256=0aOd4wzZRoSH_35UZXRHS7alPFTtuFEBJrajLuonEIw,1067
|
|
10
|
-
ipspot-0.5.dist-info/METADATA,sha256=Sbzs3OQ0QZH22X8bNrcLxWbYb1zKBtKwbPf9AVAEon0,13824
|
|
11
|
-
ipspot-0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
-
ipspot-0.5.dist-info/entry_points.txt,sha256=DJVLepYr8H3UcvWekU5Jy-tcr_xmWrphzgWGNOd3hlk,43
|
|
13
|
-
ipspot-0.5.dist-info/top_level.txt,sha256=v0WgE1z2iCy_bXU53fVcllwHLTvGNBIvq8u3KPC2_Sc,7
|
|
14
|
-
ipspot-0.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|