ipspot 0.2__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/__init__.py +2 -1
- ipspot/__main__.py +1 -1
- ipspot/cli.py +88 -0
- ipspot/ipv4.py +540 -0
- ipspot/params.py +14 -4
- ipspot/utils.py +69 -0
- {ipspot-0.2.dist-info → ipspot-0.4.dist-info}/METADATA +60 -16
- ipspot-0.4.dist-info/RECORD +13 -0
- {ipspot-0.2.dist-info → ipspot-0.4.dist-info}/WHEEL +1 -1
- ipspot-0.4.dist-info/entry_points.txt +2 -0
- {ipspot-0.2.dist-info → ipspot-0.4.dist-info}/licenses/AUTHORS.md +2 -0
- ipspot/functions.py +0 -218
- ipspot-0.2.dist-info/RECORD +0 -11
- ipspot-0.2.dist-info/entry_points.txt +0 -2
- {ipspot-0.2.dist-info → ipspot-0.4.dist-info}/licenses/LICENSE +0 -0
- {ipspot-0.2.dist-info → ipspot-0.4.dist-info}/top_level.txt +0 -0
ipspot/__init__.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""ipspot modules."""
|
|
3
3
|
from .params import IPSPOT_VERSION, IPv4API
|
|
4
|
-
from .
|
|
4
|
+
from .ipv4 import get_private_ipv4, get_public_ipv4, is_ipv4
|
|
5
|
+
from .utils import is_loopback
|
|
5
6
|
__version__ = IPSPOT_VERSION
|
ipspot/__main__.py
CHANGED
ipspot/cli.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""ipspot CLI."""
|
|
3
|
+
import argparse
|
|
4
|
+
from typing import Union, Tuple
|
|
5
|
+
from art import tprint
|
|
6
|
+
from .ipv4 import get_public_ipv4, get_private_ipv4
|
|
7
|
+
from .utils import _filter_parameter
|
|
8
|
+
from .params import IPv4API, PARAMETERS_NAME_MAP
|
|
9
|
+
from .params import IPSPOT_OVERVIEW, IPSPOT_REPO, IPSPOT_VERSION
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def ipspot_info() -> None: # pragma: no cover
|
|
13
|
+
"""Print ipspot details."""
|
|
14
|
+
tprint("IPSpot")
|
|
15
|
+
tprint("V:" + IPSPOT_VERSION)
|
|
16
|
+
print(IPSPOT_OVERVIEW)
|
|
17
|
+
print("Repo : " + IPSPOT_REPO)
|
|
18
|
+
|
|
19
|
+
|
|
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
|
|
23
|
+
"""
|
|
24
|
+
Print collected IP and location data.
|
|
25
|
+
|
|
26
|
+
:param ipv4_api: public IPv4 API
|
|
27
|
+
:param geo: geolocation flag
|
|
28
|
+
:param timeout: timeout value for API
|
|
29
|
+
:param max_retries: number of retries
|
|
30
|
+
:param retry_delay: delay between retries (in seconds)
|
|
31
|
+
"""
|
|
32
|
+
private_result = get_private_ipv4()
|
|
33
|
+
print("Private IP:\n")
|
|
34
|
+
print(" IP: {private_result[data][ip]}".format(private_result=private_result) if private_result["status"]
|
|
35
|
+
else " Error: {private_result[error]}".format(private_result=private_result))
|
|
36
|
+
|
|
37
|
+
public_title = "\nPublic IP"
|
|
38
|
+
if geo:
|
|
39
|
+
public_title += " and Location Info"
|
|
40
|
+
public_title += ":\n"
|
|
41
|
+
print(public_title)
|
|
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)
|
|
48
|
+
if public_result["status"]:
|
|
49
|
+
for name, parameter in sorted(public_result["data"].items()):
|
|
50
|
+
print(
|
|
51
|
+
" {name}: {parameter}".format(
|
|
52
|
+
name=PARAMETERS_NAME_MAP[name],
|
|
53
|
+
parameter=_filter_parameter(parameter)))
|
|
54
|
+
else:
|
|
55
|
+
print(" Error: {public_result[error]}".format(public_result=public_result))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main() -> None: # pragma: no cover
|
|
59
|
+
"""CLI main function."""
|
|
60
|
+
parser = argparse.ArgumentParser()
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
'--ipv4-api',
|
|
63
|
+
help='public IPv4 API',
|
|
64
|
+
type=str.lower,
|
|
65
|
+
choices=[
|
|
66
|
+
x.value for x in IPv4API],
|
|
67
|
+
default=IPv4API.AUTO_SAFE.value)
|
|
68
|
+
parser.add_argument('--info', help='info', nargs="?", const=1)
|
|
69
|
+
parser.add_argument('--version', help='version', nargs="?", const=1)
|
|
70
|
+
parser.add_argument('--no-geo', help='no geolocation data', nargs="?", const=1, default=False)
|
|
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)
|
|
74
|
+
|
|
75
|
+
args = parser.parse_args()
|
|
76
|
+
if args.version:
|
|
77
|
+
print(IPSPOT_VERSION)
|
|
78
|
+
elif args.info:
|
|
79
|
+
ipspot_info()
|
|
80
|
+
else:
|
|
81
|
+
ipv4_api = IPv4API(args.ipv4_api)
|
|
82
|
+
geo = not args.no_geo
|
|
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
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""ipspot ipv4 functions."""
|
|
3
|
+
import ipaddress
|
|
4
|
+
import socket
|
|
5
|
+
from typing import Union, Dict, List, Tuple
|
|
6
|
+
import requests
|
|
7
|
+
from requests.adapters import HTTPAdapter
|
|
8
|
+
from urllib3.poolmanager import PoolManager
|
|
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()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def is_ipv4(ip: str) -> bool:
|
|
72
|
+
"""
|
|
73
|
+
Check if the given input is a valid IPv4 address.
|
|
74
|
+
|
|
75
|
+
:param ip: input IP
|
|
76
|
+
"""
|
|
77
|
+
if not isinstance(ip, str):
|
|
78
|
+
return False
|
|
79
|
+
try:
|
|
80
|
+
_ = ipaddress.IPv4Address(ip)
|
|
81
|
+
return True
|
|
82
|
+
except Exception:
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_private_ipv4() -> Dict[str, Union[bool, Dict[str, str], str]]:
|
|
87
|
+
"""Retrieve the private IPv4 address."""
|
|
88
|
+
try:
|
|
89
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
|
90
|
+
s.connect(('192.168.1.1', 1))
|
|
91
|
+
private_ip = s.getsockname()[0]
|
|
92
|
+
if is_ipv4(private_ip) and not is_loopback(private_ip):
|
|
93
|
+
return {"status": True, "data": {"ip": private_ip}}
|
|
94
|
+
return {"status": False, "error": "Could not identify a non-loopback IPv4 address for this system."}
|
|
95
|
+
except Exception as e:
|
|
96
|
+
return {"status": False, "error": str(e)}
|
|
97
|
+
|
|
98
|
+
|
|
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]]:
|
|
101
|
+
"""
|
|
102
|
+
Get public IP and geolocation using ip.sb.
|
|
103
|
+
|
|
104
|
+
:param geo: geolocation flag
|
|
105
|
+
:param timeout: timeout value for API
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
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
|
|
123
|
+
except Exception as e:
|
|
124
|
+
return {"status": False, "error": str(e)}
|
|
125
|
+
|
|
126
|
+
|
|
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]]:
|
|
241
|
+
"""
|
|
242
|
+
Get public IP and geolocation using ip-api.com.
|
|
243
|
+
|
|
244
|
+
:param geo: geolocation flag
|
|
245
|
+
:param timeout: timeout value for API
|
|
246
|
+
"""
|
|
247
|
+
try:
|
|
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
|
|
265
|
+
except Exception as e:
|
|
266
|
+
return {"status": False, "error": str(e)}
|
|
267
|
+
|
|
268
|
+
|
|
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]]:
|
|
271
|
+
"""
|
|
272
|
+
Get public IP and geolocation using ipinfo.io.
|
|
273
|
+
|
|
274
|
+
:param geo: geolocation flag
|
|
275
|
+
:param timeout: timeout value for API
|
|
276
|
+
"""
|
|
277
|
+
try:
|
|
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
|
|
322
|
+
except Exception as e:
|
|
323
|
+
return {"status": False, "error": str(e)}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _ident_me_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
327
|
+
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
328
|
+
"""
|
|
329
|
+
Get public IP and geolocation using ident.me.
|
|
330
|
+
|
|
331
|
+
:param geo: geolocation flag
|
|
332
|
+
:param timeout: timeout value for API
|
|
333
|
+
"""
|
|
334
|
+
try:
|
|
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
|
|
350
|
+
except Exception as e:
|
|
351
|
+
return {"status": False, "error": str(e)}
|
|
352
|
+
|
|
353
|
+
|
|
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]]:
|
|
356
|
+
"""
|
|
357
|
+
Get public IP and geolocation using tnedi.me.
|
|
358
|
+
|
|
359
|
+
:param geo: geolocation flag
|
|
360
|
+
:param timeout: timeout value for API
|
|
361
|
+
"""
|
|
362
|
+
try:
|
|
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
|
|
435
|
+
except Exception as e:
|
|
436
|
+
return {"status": False, "error": str(e)}
|
|
437
|
+
|
|
438
|
+
|
|
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]]:
|
|
507
|
+
"""
|
|
508
|
+
Get public IPv4 and geolocation info based on the selected API.
|
|
509
|
+
|
|
510
|
+
:param api: public IPv4 API
|
|
511
|
+
:param geo: geolocation flag
|
|
512
|
+
:param timeout: timeout value for API
|
|
513
|
+
:param max_retries: number of retries
|
|
514
|
+
:param retry_delay: delay between retries (in seconds)
|
|
515
|
+
"""
|
|
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)
|
|
527
|
+
if result["status"]:
|
|
528
|
+
return result
|
|
529
|
+
return {"status": False, "error": "All attempts failed."}
|
|
530
|
+
else:
|
|
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)
|
|
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.
|
|
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,9 +22,19 @@ class IPv4API(Enum):
|
|
|
22
22
|
"""Public IPv4 API enum."""
|
|
23
23
|
|
|
24
24
|
AUTO = "auto"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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"
|
|
28
38
|
|
|
29
39
|
|
|
30
40
|
PARAMETERS_NAME_MAP = {
|
ipspot/utils.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""ipspot utils."""
|
|
3
|
+
import time
|
|
4
|
+
import ipaddress
|
|
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
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def is_loopback(ip: str) -> bool:
|
|
34
|
+
"""
|
|
35
|
+
Check if the given input IP is a loopback address.
|
|
36
|
+
|
|
37
|
+
:param ip: input IP
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
ip_object = ipaddress.ip_address(ip)
|
|
41
|
+
return ip_object.is_loopback
|
|
42
|
+
except Exception:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _filter_parameter(parameter: Any) -> Any:
|
|
47
|
+
"""
|
|
48
|
+
Filter input parameter.
|
|
49
|
+
|
|
50
|
+
:param parameter: input parameter
|
|
51
|
+
"""
|
|
52
|
+
if parameter is None:
|
|
53
|
+
return "N/A"
|
|
54
|
+
if isinstance(parameter, str) and len(parameter.strip()) == 0:
|
|
55
|
+
return "N/A"
|
|
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
|
+
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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`, `
|
|
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,46 @@ 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
|
|
295
|
+
## [0.3] - 2025-05-19
|
|
296
|
+
### Added
|
|
297
|
+
- `is_ipv4` function
|
|
298
|
+
- `is_loopback` function
|
|
299
|
+
- `IPv4HTTPAdapter` class
|
|
300
|
+
- Support [ident.me](https://ident.me/json)
|
|
301
|
+
- Support [tnedi.me](https://tnedi.me/json)
|
|
302
|
+
### Changed
|
|
303
|
+
- `get_private_ipv4` function modified
|
|
304
|
+
- `get_public_ipv4` function modified
|
|
305
|
+
- `_ipsb_ipv4` function modified
|
|
306
|
+
- `_ipapi_ipv4` function modified
|
|
307
|
+
- `_ipinfo_ipv4` function modified
|
|
308
|
+
- `functions.py` renamed to `utils.py`
|
|
309
|
+
- CLI functions moved to `cli.py`
|
|
310
|
+
- IPv4 functions moved to `ipv4.py`
|
|
311
|
+
- Test system modified
|
|
270
312
|
## [0.2] - 2025-05-04
|
|
271
313
|
### Added
|
|
272
314
|
- Support [ip.sb](https://api.ip.sb/geoip)
|
|
@@ -286,7 +328,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
286
328
|
- `--no-geo` argument
|
|
287
329
|
- Logo
|
|
288
330
|
|
|
289
|
-
[Unreleased]: https://github.com/openscilab/ipspot/compare/v0.
|
|
331
|
+
[Unreleased]: https://github.com/openscilab/ipspot/compare/v0.4...dev
|
|
332
|
+
[0.4]: https://github.com/openscilab/ipspot/compare/v0.3...v0.4
|
|
333
|
+
[0.3]: https://github.com/openscilab/ipspot/compare/v0.2...v0.3
|
|
290
334
|
[0.2]: https://github.com/openscilab/ipspot/compare/v0.1...v0.2
|
|
291
335
|
[0.1]: https://github.com/openscilab/ipspot/compare/3216fb7...v0.1
|
|
292
336
|
|
|
@@ -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,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
|
|
ipspot/functions.py
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""ipspot functions."""
|
|
3
|
-
import argparse
|
|
4
|
-
import socket
|
|
5
|
-
from typing import Union, Dict, Tuple, Any
|
|
6
|
-
import requests
|
|
7
|
-
from art import tprint
|
|
8
|
-
from .params import REQUEST_HEADERS, IPv4API, PARAMETERS_NAME_MAP
|
|
9
|
-
from .params import IPSPOT_OVERVIEW, IPSPOT_REPO, IPSPOT_VERSION
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def ipspot_info() -> None: # pragma: no cover
|
|
13
|
-
"""Print ipspot details."""
|
|
14
|
-
tprint("IPSpot")
|
|
15
|
-
tprint("V:" + IPSPOT_VERSION)
|
|
16
|
-
print(IPSPOT_OVERVIEW)
|
|
17
|
-
print("Repo : " + IPSPOT_REPO)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def get_private_ipv4() -> Dict[str, Union[bool, Dict[str, str], str]]:
|
|
21
|
-
"""Retrieve the private IPv4 address."""
|
|
22
|
-
try:
|
|
23
|
-
hostname = socket.gethostname()
|
|
24
|
-
private_ip = socket.gethostbyname(hostname)
|
|
25
|
-
return {"status": True, "data": {"ip": private_ip}}
|
|
26
|
-
except Exception as e:
|
|
27
|
-
return {"status": False, "error": str(e)}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _ipsb_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
31
|
-
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
32
|
-
"""
|
|
33
|
-
Get public IP and geolocation using ip.sb.
|
|
34
|
-
|
|
35
|
-
:param geo: geolocation flag
|
|
36
|
-
:param timeout: timeout value for API
|
|
37
|
-
"""
|
|
38
|
-
try:
|
|
39
|
-
response = requests.get("https://api.ip.sb/geoip", headers=REQUEST_HEADERS, timeout=timeout)
|
|
40
|
-
response.raise_for_status()
|
|
41
|
-
data = response.json()
|
|
42
|
-
result = {"status": True, "data": {"ip": data.get("ip"), "api": "ip.sb"}}
|
|
43
|
-
if geo:
|
|
44
|
-
geo_data = {
|
|
45
|
-
"city": data.get("city"),
|
|
46
|
-
"region": data.get("region"),
|
|
47
|
-
"country": data.get("country"),
|
|
48
|
-
"country_code": data.get("country_code"),
|
|
49
|
-
"latitude": data.get("latitude"),
|
|
50
|
-
"longitude": data.get("longitude"),
|
|
51
|
-
"organization": data.get("organization"),
|
|
52
|
-
"timezone": data.get("timezone")
|
|
53
|
-
}
|
|
54
|
-
result["data"].update(geo_data)
|
|
55
|
-
return result
|
|
56
|
-
except Exception as e:
|
|
57
|
-
return {"status": False, "error": str(e)}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _ipapi_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
61
|
-
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
62
|
-
"""
|
|
63
|
-
Get public IP and geolocation using ip-api.com.
|
|
64
|
-
|
|
65
|
-
:param geo: geolocation flag
|
|
66
|
-
:param timeout: timeout value for API
|
|
67
|
-
"""
|
|
68
|
-
try:
|
|
69
|
-
response = requests.get("http://ip-api.com/json/", headers=REQUEST_HEADERS, timeout=timeout)
|
|
70
|
-
response.raise_for_status()
|
|
71
|
-
data = response.json()
|
|
72
|
-
|
|
73
|
-
if data.get("status") != "success":
|
|
74
|
-
return {"status": False, "error": "ip-api lookup failed"}
|
|
75
|
-
result = {"status": True, "data": {"ip": data.get("query"), "api": "ip-api.com"}}
|
|
76
|
-
if geo:
|
|
77
|
-
geo_data = {
|
|
78
|
-
"city": data.get("city"),
|
|
79
|
-
"region": data.get("regionName"),
|
|
80
|
-
"country": data.get("country"),
|
|
81
|
-
"country_code": data.get("countryCode"),
|
|
82
|
-
"latitude": data.get("lat"),
|
|
83
|
-
"longitude": data.get("lon"),
|
|
84
|
-
"organization": data.get("org"),
|
|
85
|
-
"timezone": data.get("timezone")
|
|
86
|
-
}
|
|
87
|
-
result["data"].update(geo_data)
|
|
88
|
-
return result
|
|
89
|
-
except Exception as e:
|
|
90
|
-
return {"status": False, "error": str(e)}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _ipinfo_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
94
|
-
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
95
|
-
"""
|
|
96
|
-
Get public IP and geolocation using ipinfo.io.
|
|
97
|
-
|
|
98
|
-
:param geo: geolocation flag
|
|
99
|
-
:param timeout: timeout value for API
|
|
100
|
-
"""
|
|
101
|
-
try:
|
|
102
|
-
response = requests.get("https://ipinfo.io/json", headers=REQUEST_HEADERS, timeout=timeout)
|
|
103
|
-
response.raise_for_status()
|
|
104
|
-
data = response.json()
|
|
105
|
-
result = {"status": True, "data": {"ip": data.get("ip"), "api": "ipinfo.io"}}
|
|
106
|
-
if geo:
|
|
107
|
-
loc = data.get("loc", "").split(",")
|
|
108
|
-
geo_data = {
|
|
109
|
-
"city": data.get("city"),
|
|
110
|
-
"region": data.get("region"),
|
|
111
|
-
"country": None,
|
|
112
|
-
"country_code": data.get("country"),
|
|
113
|
-
"latitude": float(loc[0]) if len(loc) == 2 else None,
|
|
114
|
-
"longitude": float(loc[1]) if len(loc) == 2 else None,
|
|
115
|
-
"organization": data.get("org"),
|
|
116
|
-
"timezone": data.get("timezone")
|
|
117
|
-
}
|
|
118
|
-
result["data"].update(geo_data)
|
|
119
|
-
return result
|
|
120
|
-
except Exception as e:
|
|
121
|
-
return {"status": False, "error": str(e)}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def get_public_ipv4(api: IPv4API=IPv4API.AUTO, geo: bool=False,
|
|
125
|
-
timeout: Union[float, Tuple[float, float]]=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
126
|
-
"""
|
|
127
|
-
Get public IPv4 and geolocation info based on the selected API.
|
|
128
|
-
|
|
129
|
-
:param api: public IPv4 API
|
|
130
|
-
:param geo: geolocation flag
|
|
131
|
-
:param timeout: timeout value for API
|
|
132
|
-
"""
|
|
133
|
-
api_map = {
|
|
134
|
-
IPv4API.IPAPI: _ipapi_ipv4,
|
|
135
|
-
IPv4API.IPINFO: _ipinfo_ipv4,
|
|
136
|
-
IPv4API.IPSB: _ipsb_ipv4
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if api == IPv4API.AUTO:
|
|
140
|
-
for _, func in api_map.items():
|
|
141
|
-
result = func(geo=geo, timeout=timeout)
|
|
142
|
-
if result["status"]:
|
|
143
|
-
return result
|
|
144
|
-
return {"status": False, "error": "All attempts failed."}
|
|
145
|
-
else:
|
|
146
|
-
func = api_map.get(api)
|
|
147
|
-
if func:
|
|
148
|
-
return func(geo=geo, timeout=timeout)
|
|
149
|
-
return {"status": False, "error": "Unsupported API: {api}".format(api=api)}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def filter_parameter(parameter: Any) -> Any:
|
|
153
|
-
"""
|
|
154
|
-
Filter input parameter.
|
|
155
|
-
|
|
156
|
-
:param parameter: input parameter
|
|
157
|
-
"""
|
|
158
|
-
if parameter is None:
|
|
159
|
-
return "N/A"
|
|
160
|
-
if isinstance(parameter, str) and len(parameter.strip()) == 0:
|
|
161
|
-
return "N/A"
|
|
162
|
-
return parameter
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO, geo: bool=False,
|
|
166
|
-
timeout: Union[float, Tuple[float, float]]=5) -> None: # pragma: no cover
|
|
167
|
-
"""
|
|
168
|
-
Print collected IP and location data.
|
|
169
|
-
|
|
170
|
-
:param ipv4_api: public IPv4 API
|
|
171
|
-
:param geo: geolocation flag
|
|
172
|
-
:param timeout: timeout value for API
|
|
173
|
-
"""
|
|
174
|
-
private_result = get_private_ipv4()
|
|
175
|
-
print("Private IP:\n")
|
|
176
|
-
print(" IP: {private_result[data][ip]}".format(private_result=private_result) if private_result["status"]
|
|
177
|
-
else " Error: {private_result[error]}".format(private_result=private_result))
|
|
178
|
-
|
|
179
|
-
public_title = "\nPublic IP"
|
|
180
|
-
if geo:
|
|
181
|
-
public_title += " and Location Info"
|
|
182
|
-
public_title += ":\n"
|
|
183
|
-
print(public_title)
|
|
184
|
-
public_result = get_public_ipv4(ipv4_api, geo=geo, timeout=timeout)
|
|
185
|
-
if public_result["status"]:
|
|
186
|
-
for name, parameter in sorted(public_result["data"].items()):
|
|
187
|
-
print(
|
|
188
|
-
" {name}: {parameter}".format(
|
|
189
|
-
name=PARAMETERS_NAME_MAP[name],
|
|
190
|
-
parameter=filter_parameter(parameter)))
|
|
191
|
-
else:
|
|
192
|
-
print(" Error: {public_result[error]}".format(public_result=public_result))
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def main() -> None: # pragma: no cover
|
|
196
|
-
"""CLI main function."""
|
|
197
|
-
parser = argparse.ArgumentParser()
|
|
198
|
-
parser.add_argument(
|
|
199
|
-
'--ipv4-api',
|
|
200
|
-
help='public IPv4 API',
|
|
201
|
-
type=str.lower,
|
|
202
|
-
choices=[
|
|
203
|
-
x.value for x in IPv4API],
|
|
204
|
-
default=IPv4API.AUTO.value)
|
|
205
|
-
parser.add_argument('--info', help='info', nargs="?", const=1)
|
|
206
|
-
parser.add_argument('--version', help='version', nargs="?", const=1)
|
|
207
|
-
parser.add_argument('--no-geo', help='no geolocation data', nargs="?", const=1, default=False)
|
|
208
|
-
parser.add_argument('--timeout', help='timeout for the API request', type=float, default=5.0)
|
|
209
|
-
|
|
210
|
-
args = parser.parse_args()
|
|
211
|
-
if args.version:
|
|
212
|
-
print(IPSPOT_VERSION)
|
|
213
|
-
elif args.info:
|
|
214
|
-
ipspot_info()
|
|
215
|
-
else:
|
|
216
|
-
ipv4_api = IPv4API(args.ipv4_api)
|
|
217
|
-
geo = not args.no_geo
|
|
218
|
-
display_ip_info(ipv4_api=ipv4_api, geo=geo, timeout=args.timeout)
|
ipspot-0.2.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
ipspot/__init__.py,sha256=cxty-JGyo_sLeQd7bHrXUoEabl3QhLtgA9UtLcStp6o,176
|
|
2
|
-
ipspot/__main__.py,sha256=17x2Q5vGORstO8mZ8B9aDixcOK5Gl71Ej5fC5P1EBKY,111
|
|
3
|
-
ipspot/functions.py,sha256=JNeLJw-zIZzQQhul1SHPBzBNiFtoJvjzMUIqkeJNn3A,7862
|
|
4
|
-
ipspot/params.py,sha256=I-waqgQ83px4ybrgpD1MAYFi7Zre1T9GSBN25-eZrVk,1099
|
|
5
|
-
ipspot-0.2.dist-info/licenses/AUTHORS.md,sha256=AgFL2paPd70ItL_YK18lWm_NjNWytKZHmR57o-et8kU,236
|
|
6
|
-
ipspot-0.2.dist-info/licenses/LICENSE,sha256=0aOd4wzZRoSH_35UZXRHS7alPFTtuFEBJrajLuonEIw,1067
|
|
7
|
-
ipspot-0.2.dist-info/METADATA,sha256=yQNY-arJj8IQfVT2vvySrEcytBVzvYCiac5QhLFK1Dk,8798
|
|
8
|
-
ipspot-0.2.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
|
9
|
-
ipspot-0.2.dist-info/entry_points.txt,sha256=SYgm9eGlJY7AGqi4_PmFVXwjEoylPPnAHeAeJPtYdjk,49
|
|
10
|
-
ipspot-0.2.dist-info/top_level.txt,sha256=v0WgE1z2iCy_bXU53fVcllwHLTvGNBIvq8u3KPC2_Sc,7
|
|
11
|
-
ipspot-0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|