ipspot 0.1__py3-none-any.whl → 0.3__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 +73 -0
- ipspot/ipv4.py +273 -0
- ipspot/params.py +10 -2
- ipspot/utils.py +30 -0
- {ipspot-0.1.dist-info → ipspot-0.3.dist-info}/METADATA +55 -14
- ipspot-0.3.dist-info/RECORD +13 -0
- {ipspot-0.1.dist-info → ipspot-0.3.dist-info}/WHEEL +1 -1
- ipspot-0.3.dist-info/entry_points.txt +2 -0
- ipspot/functions.py +0 -179
- ipspot-0.1.dist-info/RECORD +0 -11
- ipspot-0.1.dist-info/entry_points.txt +0 -2
- {ipspot-0.1.dist-info → ipspot-0.3.dist-info}/licenses/AUTHORS.md +0 -0
- {ipspot-0.1.dist-info → ipspot-0.3.dist-info}/licenses/LICENSE +0 -0
- {ipspot-0.1.dist-info → ipspot-0.3.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,73 @@
|
|
|
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, geo: bool=False,
|
|
21
|
+
timeout: Union[float, Tuple[float, float]]=5) -> None: # pragma: no cover
|
|
22
|
+
"""
|
|
23
|
+
Print collected IP and location data.
|
|
24
|
+
|
|
25
|
+
:param ipv4_api: public IPv4 API
|
|
26
|
+
:param geo: geolocation flag
|
|
27
|
+
:param timeout: timeout value for API
|
|
28
|
+
"""
|
|
29
|
+
private_result = get_private_ipv4()
|
|
30
|
+
print("Private IP:\n")
|
|
31
|
+
print(" IP: {private_result[data][ip]}".format(private_result=private_result) if private_result["status"]
|
|
32
|
+
else " Error: {private_result[error]}".format(private_result=private_result))
|
|
33
|
+
|
|
34
|
+
public_title = "\nPublic IP"
|
|
35
|
+
if geo:
|
|
36
|
+
public_title += " and Location Info"
|
|
37
|
+
public_title += ":\n"
|
|
38
|
+
print(public_title)
|
|
39
|
+
public_result = get_public_ipv4(ipv4_api, geo=geo, timeout=timeout)
|
|
40
|
+
if public_result["status"]:
|
|
41
|
+
for name, parameter in sorted(public_result["data"].items()):
|
|
42
|
+
print(
|
|
43
|
+
" {name}: {parameter}".format(
|
|
44
|
+
name=PARAMETERS_NAME_MAP[name],
|
|
45
|
+
parameter=filter_parameter(parameter)))
|
|
46
|
+
else:
|
|
47
|
+
print(" Error: {public_result[error]}".format(public_result=public_result))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main() -> None: # pragma: no cover
|
|
51
|
+
"""CLI main function."""
|
|
52
|
+
parser = argparse.ArgumentParser()
|
|
53
|
+
parser.add_argument(
|
|
54
|
+
'--ipv4-api',
|
|
55
|
+
help='public IPv4 API',
|
|
56
|
+
type=str.lower,
|
|
57
|
+
choices=[
|
|
58
|
+
x.value for x in IPv4API],
|
|
59
|
+
default=IPv4API.AUTO.value)
|
|
60
|
+
parser.add_argument('--info', help='info', nargs="?", const=1)
|
|
61
|
+
parser.add_argument('--version', help='version', nargs="?", const=1)
|
|
62
|
+
parser.add_argument('--no-geo', help='no geolocation data', nargs="?", const=1, default=False)
|
|
63
|
+
parser.add_argument('--timeout', help='timeout for the API request', type=float, default=5.0)
|
|
64
|
+
|
|
65
|
+
args = parser.parse_args()
|
|
66
|
+
if args.version:
|
|
67
|
+
print(IPSPOT_VERSION)
|
|
68
|
+
elif args.info:
|
|
69
|
+
ipspot_info()
|
|
70
|
+
else:
|
|
71
|
+
ipv4_api = IPv4API(args.ipv4_api)
|
|
72
|
+
geo = not args.no_geo
|
|
73
|
+
display_ip_info(ipv4_api=ipv4_api, geo=geo, timeout=args.timeout)
|
ipspot/ipv4.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
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
|
|
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 is_ipv4(ip: str) -> bool:
|
|
57
|
+
"""
|
|
58
|
+
Check if the given input is a valid IPv4 address.
|
|
59
|
+
|
|
60
|
+
:param ip: input IP
|
|
61
|
+
"""
|
|
62
|
+
if not isinstance(ip, str):
|
|
63
|
+
return False
|
|
64
|
+
try:
|
|
65
|
+
_ = ipaddress.IPv4Address(ip)
|
|
66
|
+
return True
|
|
67
|
+
except Exception:
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_private_ipv4() -> Dict[str, Union[bool, Dict[str, str], str]]:
|
|
72
|
+
"""Retrieve the private IPv4 address."""
|
|
73
|
+
try:
|
|
74
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
|
75
|
+
s.connect(('192.168.1.1', 1))
|
|
76
|
+
private_ip = s.getsockname()[0]
|
|
77
|
+
if is_ipv4(private_ip) and not is_loopback(private_ip):
|
|
78
|
+
return {"status": True, "data": {"ip": private_ip}}
|
|
79
|
+
return {"status": False, "error": "Could not identify a non-loopback IPv4 address for this system."}
|
|
80
|
+
except Exception as e:
|
|
81
|
+
return {"status": False, "error": str(e)}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _ipsb_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
85
|
+
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
86
|
+
"""
|
|
87
|
+
Get public IP and geolocation using ip.sb.
|
|
88
|
+
|
|
89
|
+
:param geo: geolocation flag
|
|
90
|
+
:param timeout: timeout value for API
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
with requests.Session() as session:
|
|
94
|
+
response = session.get("https://api-ipv4.ip.sb/geoip", headers=REQUEST_HEADERS, timeout=timeout)
|
|
95
|
+
response.raise_for_status()
|
|
96
|
+
data = response.json()
|
|
97
|
+
result = {"status": True, "data": {"ip": data.get("ip"), "api": "ip.sb"}}
|
|
98
|
+
if geo:
|
|
99
|
+
geo_data = {
|
|
100
|
+
"city": data.get("city"),
|
|
101
|
+
"region": data.get("region"),
|
|
102
|
+
"country": data.get("country"),
|
|
103
|
+
"country_code": data.get("country_code"),
|
|
104
|
+
"latitude": data.get("latitude"),
|
|
105
|
+
"longitude": data.get("longitude"),
|
|
106
|
+
"organization": data.get("organization"),
|
|
107
|
+
"timezone": data.get("timezone")
|
|
108
|
+
}
|
|
109
|
+
result["data"].update(geo_data)
|
|
110
|
+
return result
|
|
111
|
+
except Exception as e:
|
|
112
|
+
return {"status": False, "error": str(e)}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _ipapi_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
116
|
+
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
117
|
+
"""
|
|
118
|
+
Get public IP and geolocation using ip-api.com.
|
|
119
|
+
|
|
120
|
+
:param geo: geolocation flag
|
|
121
|
+
:param timeout: timeout value for API
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
with requests.Session() as session:
|
|
125
|
+
session.mount("http://", IPv4HTTPAdapter())
|
|
126
|
+
session.mount("https://", IPv4HTTPAdapter())
|
|
127
|
+
response = session.get("http://ip-api.com/json/", headers=REQUEST_HEADERS, timeout=timeout)
|
|
128
|
+
response.raise_for_status()
|
|
129
|
+
data = response.json()
|
|
130
|
+
if data.get("status") != "success":
|
|
131
|
+
return {"status": False, "error": "ip-api lookup failed"}
|
|
132
|
+
result = {"status": True, "data": {"ip": data.get("query"), "api": "ip-api.com"}}
|
|
133
|
+
if geo:
|
|
134
|
+
geo_data = {
|
|
135
|
+
"city": data.get("city"),
|
|
136
|
+
"region": data.get("regionName"),
|
|
137
|
+
"country": data.get("country"),
|
|
138
|
+
"country_code": data.get("countryCode"),
|
|
139
|
+
"latitude": data.get("lat"),
|
|
140
|
+
"longitude": data.get("lon"),
|
|
141
|
+
"organization": data.get("org"),
|
|
142
|
+
"timezone": data.get("timezone")
|
|
143
|
+
}
|
|
144
|
+
result["data"].update(geo_data)
|
|
145
|
+
return result
|
|
146
|
+
except Exception as e:
|
|
147
|
+
return {"status": False, "error": str(e)}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _ipinfo_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
151
|
+
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
152
|
+
"""
|
|
153
|
+
Get public IP and geolocation using ipinfo.io.
|
|
154
|
+
|
|
155
|
+
:param geo: geolocation flag
|
|
156
|
+
:param timeout: timeout value for API
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
with requests.Session() as session:
|
|
160
|
+
session.mount("http://", IPv4HTTPAdapter())
|
|
161
|
+
session.mount("https://", IPv4HTTPAdapter())
|
|
162
|
+
response = session.get("https://ipinfo.io/json", headers=REQUEST_HEADERS, timeout=timeout)
|
|
163
|
+
response.raise_for_status()
|
|
164
|
+
data = response.json()
|
|
165
|
+
result = {"status": True, "data": {"ip": data.get("ip"), "api": "ipinfo.io"}}
|
|
166
|
+
if geo:
|
|
167
|
+
loc = data.get("loc", "").split(",")
|
|
168
|
+
geo_data = {
|
|
169
|
+
"city": data.get("city"),
|
|
170
|
+
"region": data.get("region"),
|
|
171
|
+
"country": None,
|
|
172
|
+
"country_code": data.get("country"),
|
|
173
|
+
"latitude": float(loc[0]) if len(loc) == 2 else None,
|
|
174
|
+
"longitude": float(loc[1]) if len(loc) == 2 else None,
|
|
175
|
+
"organization": data.get("org"),
|
|
176
|
+
"timezone": data.get("timezone")
|
|
177
|
+
}
|
|
178
|
+
result["data"].update(geo_data)
|
|
179
|
+
return result
|
|
180
|
+
except Exception as e:
|
|
181
|
+
return {"status": False, "error": str(e)}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _ident_me_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
185
|
+
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
186
|
+
"""
|
|
187
|
+
Get public IP and geolocation using ident.me.
|
|
188
|
+
|
|
189
|
+
:param geo: geolocation flag
|
|
190
|
+
:param timeout: timeout value for API
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
with requests.Session() as session:
|
|
194
|
+
response = session.get("https://4.ident.me/json", headers=REQUEST_HEADERS, timeout=timeout)
|
|
195
|
+
response.raise_for_status()
|
|
196
|
+
data = response.json()
|
|
197
|
+
result = {"status": True, "data": {"ip": data.get("ip"), "api": "ident.me"}}
|
|
198
|
+
if geo:
|
|
199
|
+
geo_data = {
|
|
200
|
+
"city": data.get("city"),
|
|
201
|
+
"region": None,
|
|
202
|
+
"country": data.get("country"),
|
|
203
|
+
"country_code": data.get("cc"),
|
|
204
|
+
"latitude": data.get("latitude"),
|
|
205
|
+
"longitude": data.get("longitude"),
|
|
206
|
+
"organization": data.get("aso"),
|
|
207
|
+
"timezone": data.get("tz")
|
|
208
|
+
}
|
|
209
|
+
result["data"].update(geo_data)
|
|
210
|
+
return result
|
|
211
|
+
except Exception as e:
|
|
212
|
+
return {"status": False, "error": str(e)}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _tnedime_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
216
|
+
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
217
|
+
"""
|
|
218
|
+
Get public IP and geolocation using tnedi.me.
|
|
219
|
+
|
|
220
|
+
:param geo: geolocation flag
|
|
221
|
+
:param timeout: timeout value for API
|
|
222
|
+
"""
|
|
223
|
+
try:
|
|
224
|
+
with requests.Session() as session:
|
|
225
|
+
response = session.get("https://4.tnedi.me/json", headers=REQUEST_HEADERS, timeout=timeout)
|
|
226
|
+
response.raise_for_status()
|
|
227
|
+
data = response.json()
|
|
228
|
+
result = {"status": True, "data": {"ip": data.get("ip"), "api": "tnedi.me"}}
|
|
229
|
+
if geo:
|
|
230
|
+
geo_data = {
|
|
231
|
+
"city": data.get("city"),
|
|
232
|
+
"region": None,
|
|
233
|
+
"country": data.get("country"),
|
|
234
|
+
"country_code": data.get("cc"),
|
|
235
|
+
"latitude": data.get("latitude"),
|
|
236
|
+
"longitude": data.get("longitude"),
|
|
237
|
+
"organization": data.get("aso"),
|
|
238
|
+
"timezone": data.get("tz")
|
|
239
|
+
}
|
|
240
|
+
result["data"].update(geo_data)
|
|
241
|
+
return result
|
|
242
|
+
except Exception as e:
|
|
243
|
+
return {"status": False, "error": str(e)}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def get_public_ipv4(api: IPv4API=IPv4API.AUTO, geo: bool=False,
|
|
247
|
+
timeout: Union[float, Tuple[float, float]]=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
248
|
+
"""
|
|
249
|
+
Get public IPv4 and geolocation info based on the selected API.
|
|
250
|
+
|
|
251
|
+
:param api: public IPv4 API
|
|
252
|
+
:param geo: geolocation flag
|
|
253
|
+
:param timeout: timeout value for API
|
|
254
|
+
"""
|
|
255
|
+
api_map = {
|
|
256
|
+
IPv4API.IDENTME: _ident_me_ipv4,
|
|
257
|
+
IPv4API.TNEDIME: _tnedime_ipv4,
|
|
258
|
+
IPv4API.IPSB: _ipsb_ipv4,
|
|
259
|
+
IPv4API.IPAPI: _ipapi_ipv4,
|
|
260
|
+
IPv4API.IPINFO: _ipinfo_ipv4,
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if api == IPv4API.AUTO:
|
|
264
|
+
for _, func in api_map.items():
|
|
265
|
+
result = func(geo=geo, timeout=timeout)
|
|
266
|
+
if result["status"]:
|
|
267
|
+
return result
|
|
268
|
+
return {"status": False, "error": "All attempts failed."}
|
|
269
|
+
else:
|
|
270
|
+
func = api_map.get(api)
|
|
271
|
+
if func:
|
|
272
|
+
return func(geo=geo, timeout=timeout)
|
|
273
|
+
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.3"
|
|
6
6
|
|
|
7
7
|
IPSPOT_OVERVIEW = '''
|
|
8
8
|
IPSpot is a Python library for retrieving the current system's IP address and location information.
|
|
@@ -10,7 +10,12 @@ It currently supports public and private IPv4 detection using multiple API provi
|
|
|
10
10
|
Designed with simplicity and modularity in mind, IPSpot offers quick IP and geolocation lookups directly from your machine.
|
|
11
11
|
'''
|
|
12
12
|
|
|
13
|
-
IPSPOT_REPO = "
|
|
13
|
+
IPSPOT_REPO = "https://github.com/openscilab/ipspot"
|
|
14
|
+
|
|
15
|
+
REQUEST_HEADERS = {
|
|
16
|
+
'User-Agent': 'IPSpot/{version} ({repo})'.format(version=IPSPOT_VERSION, repo=IPSPOT_REPO),
|
|
17
|
+
'Accept': 'application/json'
|
|
18
|
+
}
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
class IPv4API(Enum):
|
|
@@ -19,6 +24,9 @@ class IPv4API(Enum):
|
|
|
19
24
|
AUTO = "auto"
|
|
20
25
|
IPAPI = "ipapi"
|
|
21
26
|
IPINFO = "ipinfo"
|
|
27
|
+
IPSB = "ipsb"
|
|
28
|
+
IDENTME = "identme"
|
|
29
|
+
TNEDIME = "tnedime"
|
|
22
30
|
|
|
23
31
|
|
|
24
32
|
PARAMETERS_NAME_MAP = {
|
ipspot/utils.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""ipspot utils."""
|
|
3
|
+
import ipaddress
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def is_loopback(ip: str) -> bool:
|
|
8
|
+
"""
|
|
9
|
+
Check if the given input IP is a loopback address.
|
|
10
|
+
|
|
11
|
+
:param ip: input IP
|
|
12
|
+
"""
|
|
13
|
+
try:
|
|
14
|
+
ip_object = ipaddress.ip_address(ip)
|
|
15
|
+
return ip_object.is_loopback
|
|
16
|
+
except Exception:
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def filter_parameter(parameter: Any) -> Any:
|
|
21
|
+
"""
|
|
22
|
+
Filter input parameter.
|
|
23
|
+
|
|
24
|
+
:param parameter: input parameter
|
|
25
|
+
"""
|
|
26
|
+
if parameter is None:
|
|
27
|
+
return "N/A"
|
|
28
|
+
if isinstance(parameter, str) and len(parameter.strip()) == 0:
|
|
29
|
+
return "N/A"
|
|
30
|
+
return parameter
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipspot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3
|
|
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.3
|
|
7
7
|
Author: IPSpot Development Team
|
|
8
8
|
Author-email: ipspot@openscilab.com
|
|
9
9
|
License: MIT
|
|
@@ -55,6 +55,7 @@ Dynamic: summary
|
|
|
55
55
|
<a href="https://badge.fury.io/py/ipspot"><img src="https://badge.fury.io/py/ipspot.svg" alt="PyPI version"></a>
|
|
56
56
|
<a href="https://www.python.org/"><img src="https://img.shields.io/badge/built%20with-Python3-green.svg" alt="built with Python3"></a>
|
|
57
57
|
<a href="https://github.com/openscilab/ipspot"><img alt="GitHub repo size" src="https://img.shields.io/github/repo-size/openscilab/ipspot"></a>
|
|
58
|
+
<a href="https://discord.gg/yyDV3T4cwU"><img src="https://img.shields.io/discord/1064533716615049236.svg" alt="Discord Channel"></a>
|
|
58
59
|
</div>
|
|
59
60
|
|
|
60
61
|
## Overview
|
|
@@ -89,17 +90,25 @@ Dynamic: summary
|
|
|
89
90
|
</tr>
|
|
90
91
|
</table>
|
|
91
92
|
|
|
93
|
+
<table>
|
|
94
|
+
<tr>
|
|
95
|
+
<td align="center">Code Quality</td>
|
|
96
|
+
<td align="center"><a href="https://app.codacy.com/gh/openscilab/ipspot/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade"><img src="https://app.codacy.com/project/badge/Grade/cb2ab6584eb443b8a33da4d4252480bc"/></a></td>
|
|
97
|
+
<td align="center"><a href="https://www.codefactor.io/repository/github/openscilab/ipspot"><img src="https://www.codefactor.io/repository/github/openscilab/ipspot/badge" alt="CodeFactor"></a></td>
|
|
98
|
+
</tr>
|
|
99
|
+
</table>
|
|
100
|
+
|
|
92
101
|
|
|
93
102
|
## Installation
|
|
94
103
|
|
|
95
104
|
### Source Code
|
|
96
|
-
- Download [Version 0.
|
|
105
|
+
- Download [Version 0.3](https://github.com/openscilab/ipspot/archive/v0.3.zip) or [Latest Source](https://github.com/openscilab/ipspot/archive/dev.zip)
|
|
97
106
|
- `pip install .`
|
|
98
107
|
|
|
99
108
|
### PyPI
|
|
100
109
|
|
|
101
110
|
- Check [Python Packaging User Guide](https://packaging.python.org/installing/)
|
|
102
|
-
- `pip install ipspot==0.
|
|
111
|
+
- `pip install ipspot==0.3`
|
|
103
112
|
|
|
104
113
|
|
|
105
114
|
## Usage
|
|
@@ -112,7 +121,7 @@ Dynamic: summary
|
|
|
112
121
|
>>> from ipspot import get_public_ipv4, IPv4API
|
|
113
122
|
>>> get_public_ipv4(api=IPv4API.IPAPI)
|
|
114
123
|
{'status': True, 'data': {'ip': 'xx.xx.xx.xx', 'api': 'ip-api.com'}}
|
|
115
|
-
>>> get_public_ipv4(api=IPv4API.IPAPI, geo=True)
|
|
124
|
+
>>> get_public_ipv4(api=IPv4API.IPAPI, geo=True, timeout=10)
|
|
116
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}
|
|
117
126
|
```
|
|
118
127
|
|
|
@@ -133,7 +142,7 @@ Dynamic: summary
|
|
|
133
142
|
```console
|
|
134
143
|
> ipspot --version
|
|
135
144
|
|
|
136
|
-
0.
|
|
145
|
+
0.3
|
|
137
146
|
```
|
|
138
147
|
|
|
139
148
|
#### Info
|
|
@@ -148,11 +157,11 @@ Dynamic: summary
|
|
|
148
157
|
|___||_| |____/ | .__/ \___/ \__|
|
|
149
158
|
|_|
|
|
150
159
|
|
|
151
|
-
__ __ ___
|
|
152
|
-
\ \ / / _ / _ \ /
|
|
153
|
-
\ \ / / (_)| | | |
|
|
154
|
-
\ V / _ | |_| | _ |
|
|
155
|
-
\_/ (_) \___/ (_)|
|
|
160
|
+
__ __ ___ _____
|
|
161
|
+
\ \ / / _ / _ \ |___ /
|
|
162
|
+
\ \ / / (_)| | | | |_ \
|
|
163
|
+
\ V / _ | |_| | _ ___) |
|
|
164
|
+
\_/ (_) \___/ (_)|____/
|
|
156
165
|
|
|
157
166
|
|
|
158
167
|
|
|
@@ -188,7 +197,7 @@ Public IP and Location Info:
|
|
|
188
197
|
|
|
189
198
|
#### IPv4 API
|
|
190
199
|
|
|
191
|
-
ℹ️ `ipv4-api` valid choices: [`auto`, `ipapi`, `ipinfo`]
|
|
200
|
+
ℹ️ `ipv4-api` valid choices: [`auto`, `ipapi`, `ipinfo`, `ipsb`, `identme`, `tnedime`]
|
|
192
201
|
|
|
193
202
|
ℹ️ The default value: `auto`
|
|
194
203
|
|
|
@@ -231,7 +240,12 @@ Public IP:
|
|
|
231
240
|
Just fill an issue and describe it. We'll check it ASAP!
|
|
232
241
|
|
|
233
242
|
- Please complete the issue template
|
|
234
|
-
|
|
243
|
+
|
|
244
|
+
You can also join our discord server
|
|
245
|
+
|
|
246
|
+
<a href="https://discord.gg/yyDV3T4cwU">
|
|
247
|
+
<img src="https://img.shields.io/discord/1064533716615049236.svg?style=for-the-badge" alt="Discord Channel">
|
|
248
|
+
</a>
|
|
235
249
|
|
|
236
250
|
## Show Your Support
|
|
237
251
|
|
|
@@ -253,6 +267,31 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
|
253
267
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
254
268
|
|
|
255
269
|
## [Unreleased]
|
|
270
|
+
## [0.3] - 2025-05-19
|
|
271
|
+
### Added
|
|
272
|
+
- `is_ipv4` function
|
|
273
|
+
- `is_loopback` function
|
|
274
|
+
- `IPv4HTTPAdapter` class
|
|
275
|
+
- Support [ident.me](https://ident.me/json)
|
|
276
|
+
- Support [tnedi.me](https://tnedi.me/json)
|
|
277
|
+
### Changed
|
|
278
|
+
- `get_private_ipv4` function modified
|
|
279
|
+
- `get_public_ipv4` function modified
|
|
280
|
+
- `_ipsb_ipv4` function modified
|
|
281
|
+
- `_ipapi_ipv4` function modified
|
|
282
|
+
- `_ipinfo_ipv4` function modified
|
|
283
|
+
- `functions.py` renamed to `utils.py`
|
|
284
|
+
- CLI functions moved to `cli.py`
|
|
285
|
+
- IPv4 functions moved to `ipv4.py`
|
|
286
|
+
- Test system modified
|
|
287
|
+
## [0.2] - 2025-05-04
|
|
288
|
+
### Added
|
|
289
|
+
- Support [ip.sb](https://api.ip.sb/geoip)
|
|
290
|
+
- `--timeout` argument
|
|
291
|
+
### Changed
|
|
292
|
+
- `README.md` updated
|
|
293
|
+
- Requests header updated
|
|
294
|
+
- Test system modified
|
|
256
295
|
## [0.1] - 2025-04-25
|
|
257
296
|
### Added
|
|
258
297
|
- Support [ipinfo.io](https://ipinfo.io)
|
|
@@ -264,7 +303,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
264
303
|
- `--no-geo` argument
|
|
265
304
|
- Logo
|
|
266
305
|
|
|
267
|
-
[Unreleased]: https://github.com/openscilab/ipspot/compare/v0.
|
|
306
|
+
[Unreleased]: https://github.com/openscilab/ipspot/compare/v0.3...dev
|
|
307
|
+
[0.3]: https://github.com/openscilab/ipspot/compare/v0.2...v0.3
|
|
308
|
+
[0.2]: https://github.com/openscilab/ipspot/compare/v0.1...v0.2
|
|
268
309
|
[0.1]: https://github.com/openscilab/ipspot/compare/3216fb7...v0.1
|
|
269
310
|
|
|
270
311
|
|
|
@@ -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=Yq0GVzyZhMUauTMA_BUO8UXa6OqfYDBLLVjERZ3vOgE,2632
|
|
4
|
+
ipspot/ipv4.py,sha256=OhQQnRdkwQ-SrfaqJdhF1Qu_zsW-mi93NEk_N5QAOrk,10757
|
|
5
|
+
ipspot/params.py,sha256=EekA1HmFlxRT-wk0mEKHvPSR_O6UTHWSBgQoiC5-aRU,1147
|
|
6
|
+
ipspot/utils.py,sha256=OaRrNQI8ASUHisMVEhyfy4lwcgugRxXK4JcWqljITfE,636
|
|
7
|
+
ipspot-0.3.dist-info/licenses/AUTHORS.md,sha256=AgFL2paPd70ItL_YK18lWm_NjNWytKZHmR57o-et8kU,236
|
|
8
|
+
ipspot-0.3.dist-info/licenses/LICENSE,sha256=0aOd4wzZRoSH_35UZXRHS7alPFTtuFEBJrajLuonEIw,1067
|
|
9
|
+
ipspot-0.3.dist-info/METADATA,sha256=h2XSvM65GkEAL8COdzOpbuOtiQSj3fEgdYCfON6ZCeA,9400
|
|
10
|
+
ipspot-0.3.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
11
|
+
ipspot-0.3.dist-info/entry_points.txt,sha256=DJVLepYr8H3UcvWekU5Jy-tcr_xmWrphzgWGNOd3hlk,43
|
|
12
|
+
ipspot-0.3.dist-info/top_level.txt,sha256=v0WgE1z2iCy_bXU53fVcllwHLTvGNBIvq8u3KPC2_Sc,7
|
|
13
|
+
ipspot-0.3.dist-info/RECORD,,
|
ipspot/functions.py
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""ipspot functions."""
|
|
3
|
-
import argparse
|
|
4
|
-
import socket
|
|
5
|
-
from typing import Union, Dict, Any
|
|
6
|
-
import requests
|
|
7
|
-
from art import tprint
|
|
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:
|
|
13
|
-
"""Print ipspot details."""
|
|
14
|
-
tprint("IPSpot")
|
|
15
|
-
tprint("V:" + IPSPOT_VERSION)
|
|
16
|
-
print(IPSPOT_OVERVIEW)
|
|
17
|
-
print(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 _ipapi_ipv4(geo: bool=False) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
31
|
-
"""
|
|
32
|
-
Get public IP and geolocation using ip-api.com.
|
|
33
|
-
|
|
34
|
-
:param geo: geolocation flag
|
|
35
|
-
"""
|
|
36
|
-
try:
|
|
37
|
-
response = requests.get("http://ip-api.com/json/", timeout=5)
|
|
38
|
-
response.raise_for_status()
|
|
39
|
-
data = response.json()
|
|
40
|
-
|
|
41
|
-
if data.get("status") != "success":
|
|
42
|
-
return {"status": False, "error": "ip-api lookup failed"}
|
|
43
|
-
result = {"status": True, "data": {"ip": data.get("query"), "api": "ip-api.com"}}
|
|
44
|
-
if geo:
|
|
45
|
-
geo_data = {
|
|
46
|
-
"city": data.get("city"),
|
|
47
|
-
"region": data.get("regionName"),
|
|
48
|
-
"country": data.get("country"),
|
|
49
|
-
"country_code": data.get("countryCode"),
|
|
50
|
-
"latitude": data.get("lat"),
|
|
51
|
-
"longitude": data.get("lon"),
|
|
52
|
-
"organization": data.get("org"),
|
|
53
|
-
"timezone": data.get("timezone")
|
|
54
|
-
}
|
|
55
|
-
result["data"].update(geo_data)
|
|
56
|
-
return result
|
|
57
|
-
except Exception as e:
|
|
58
|
-
return {"status": False, "error": str(e)}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _ipinfo_ipv4(geo: bool=False) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
62
|
-
"""
|
|
63
|
-
Get public IP and geolocation using ipinfo.io.
|
|
64
|
-
|
|
65
|
-
:param geo: geolocation flag
|
|
66
|
-
"""
|
|
67
|
-
try:
|
|
68
|
-
response = requests.get("https://ipinfo.io/json", timeout=5)
|
|
69
|
-
response.raise_for_status()
|
|
70
|
-
data = response.json()
|
|
71
|
-
result = {"status": True, "data": {"ip": data.get("ip"), "api": "ipinfo.io"}}
|
|
72
|
-
if geo:
|
|
73
|
-
loc = data.get("loc", "").split(",")
|
|
74
|
-
geo_data = {
|
|
75
|
-
"city": data.get("city"),
|
|
76
|
-
"region": data.get("region"),
|
|
77
|
-
"country": None,
|
|
78
|
-
"country_code": data.get("country"),
|
|
79
|
-
"latitude": float(loc[0]) if len(loc) == 2 else None,
|
|
80
|
-
"longitude": float(loc[1]) if len(loc) == 2 else None,
|
|
81
|
-
"organization": data.get("org"),
|
|
82
|
-
"timezone": data.get("timezone")
|
|
83
|
-
}
|
|
84
|
-
result["data"].update(geo_data)
|
|
85
|
-
return result
|
|
86
|
-
except Exception as e:
|
|
87
|
-
return {"status": False, "error": str(e)}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def get_public_ipv4(api: IPv4API=IPv4API.AUTO,
|
|
91
|
-
geo: bool=False) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
92
|
-
"""
|
|
93
|
-
Get public IPv4 and geolocation info based on the selected API.
|
|
94
|
-
|
|
95
|
-
:param api: public IPv4 API
|
|
96
|
-
:param geo: geolocation flag
|
|
97
|
-
"""
|
|
98
|
-
api_map = {
|
|
99
|
-
IPv4API.IPAPI: _ipapi_ipv4,
|
|
100
|
-
IPv4API.IPINFO: _ipinfo_ipv4,
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if api == IPv4API.AUTO:
|
|
104
|
-
for _, func in api_map.items():
|
|
105
|
-
result = func(geo=geo)
|
|
106
|
-
if result["status"]:
|
|
107
|
-
return result
|
|
108
|
-
return {"status": False, "error": "All attempts failed."}
|
|
109
|
-
else:
|
|
110
|
-
func = api_map.get(api)
|
|
111
|
-
if func:
|
|
112
|
-
return func(geo=geo)
|
|
113
|
-
return {"status": False, "error": "Unsupported API: {api}".format(api=api)}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def filter_parameter(parameter: Any) -> Any:
|
|
117
|
-
"""
|
|
118
|
-
Filter input parameter.
|
|
119
|
-
|
|
120
|
-
:param parameter: input parameter
|
|
121
|
-
"""
|
|
122
|
-
if parameter is None:
|
|
123
|
-
return "N/A"
|
|
124
|
-
if isinstance(parameter, str) and len(parameter.strip()) == 0:
|
|
125
|
-
return "N/A"
|
|
126
|
-
return parameter
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO, geo: bool=False) -> None:
|
|
130
|
-
"""
|
|
131
|
-
Print collected IP and location data.
|
|
132
|
-
|
|
133
|
-
:param ipv4_api: public IPv4 API
|
|
134
|
-
:param geo: geolocation flag
|
|
135
|
-
"""
|
|
136
|
-
private_result = get_private_ipv4()
|
|
137
|
-
print("Private IP:\n")
|
|
138
|
-
print(" IP: {private_result[data][ip]}".format(private_result=private_result) if private_result["status"]
|
|
139
|
-
else " Error: {private_result[error]}".format(private_result=private_result))
|
|
140
|
-
|
|
141
|
-
public_title = "\nPublic IP"
|
|
142
|
-
if geo:
|
|
143
|
-
public_title += " and Location Info"
|
|
144
|
-
public_title += ":\n"
|
|
145
|
-
print(public_title)
|
|
146
|
-
public_result = get_public_ipv4(ipv4_api, geo=geo)
|
|
147
|
-
if public_result["status"]:
|
|
148
|
-
for name, parameter in sorted(public_result["data"].items()):
|
|
149
|
-
print(
|
|
150
|
-
" {name}: {parameter}".format(
|
|
151
|
-
name=PARAMETERS_NAME_MAP[name],
|
|
152
|
-
parameter=filter_parameter(parameter)))
|
|
153
|
-
else:
|
|
154
|
-
print(" Error: {public_result[error]}".format(public_result=public_result))
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def main() -> None:
|
|
158
|
-
"""CLI main function."""
|
|
159
|
-
parser = argparse.ArgumentParser()
|
|
160
|
-
parser.add_argument(
|
|
161
|
-
'--ipv4-api',
|
|
162
|
-
help='public IPv4 API',
|
|
163
|
-
type=str.lower,
|
|
164
|
-
choices=[
|
|
165
|
-
x.value for x in IPv4API],
|
|
166
|
-
default=IPv4API.AUTO.value)
|
|
167
|
-
parser.add_argument('--info', help='info', nargs="?", const=1)
|
|
168
|
-
parser.add_argument('--version', help='version', nargs="?", const=1)
|
|
169
|
-
parser.add_argument('--no-geo', help='no geolocation data', nargs="?", const=1, default=False)
|
|
170
|
-
|
|
171
|
-
args = parser.parse_args()
|
|
172
|
-
if args.version:
|
|
173
|
-
print(IPSPOT_VERSION)
|
|
174
|
-
elif args.info:
|
|
175
|
-
ipspot_info()
|
|
176
|
-
else:
|
|
177
|
-
ipv4_api = IPv4API(args.ipv4_api)
|
|
178
|
-
geo = not args.no_geo
|
|
179
|
-
display_ip_info(ipv4_api=ipv4_api, geo=geo)
|
ipspot-0.1.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=rXSoVRGDCDeH4anda8_o3rgE0jl5VqvfoDA-DL_bnDo,5936
|
|
4
|
-
ipspot/params.py,sha256=J-O4-qaRLQCAVc9a0yKJpFC0LCOG1SvsHBgjA-T59pY,936
|
|
5
|
-
ipspot-0.1.dist-info/licenses/AUTHORS.md,sha256=AgFL2paPd70ItL_YK18lWm_NjNWytKZHmR57o-et8kU,236
|
|
6
|
-
ipspot-0.1.dist-info/licenses/LICENSE,sha256=0aOd4wzZRoSH_35UZXRHS7alPFTtuFEBJrajLuonEIw,1067
|
|
7
|
-
ipspot-0.1.dist-info/METADATA,sha256=qtGhxEFLspKwu1qFF3IOE157RImRrlOroU2fbBdz7zs,7667
|
|
8
|
-
ipspot-0.1.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
|
9
|
-
ipspot-0.1.dist-info/entry_points.txt,sha256=SYgm9eGlJY7AGqi4_PmFVXwjEoylPPnAHeAeJPtYdjk,49
|
|
10
|
-
ipspot-0.1.dist-info/top_level.txt,sha256=v0WgE1z2iCy_bXU53fVcllwHLTvGNBIvq8u3KPC2_Sc,7
|
|
11
|
-
ipspot-0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|