ipspot 0.4__py3-none-any.whl → 0.5__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/cli.py +50 -10
- ipspot/ipv4.py +104 -1
- ipspot/ipv6.py +245 -0
- ipspot/params.py +16 -1
- {ipspot-0.4.dist-info → ipspot-0.5.dist-info}/METADATA +139 -28
- ipspot-0.5.dist-info/RECORD +14 -0
- ipspot-0.4.dist-info/RECORD +0 -13
- {ipspot-0.4.dist-info → ipspot-0.5.dist-info}/WHEEL +0 -0
- {ipspot-0.4.dist-info → ipspot-0.5.dist-info}/entry_points.txt +0 -0
- {ipspot-0.4.dist-info → ipspot-0.5.dist-info}/licenses/AUTHORS.md +0 -0
- {ipspot-0.4.dist-info → ipspot-0.5.dist-info}/licenses/LICENSE +0 -0
- {ipspot-0.4.dist-info → ipspot-0.5.dist-info}/top_level.txt +0 -0
ipspot/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""ipspot modules."""
|
|
3
|
-
from .params import IPSPOT_VERSION, IPv4API
|
|
3
|
+
from .params import IPSPOT_VERSION, IPv4API, IPv6API
|
|
4
4
|
from .ipv4 import get_private_ipv4, get_public_ipv4, is_ipv4
|
|
5
|
+
from .ipv6 import get_private_ipv6, get_public_ipv6, is_ipv6
|
|
5
6
|
from .utils import is_loopback
|
|
6
7
|
__version__ = IPSPOT_VERSION
|
ipspot/cli.py
CHANGED
|
@@ -4,8 +4,9 @@ import argparse
|
|
|
4
4
|
from typing import Union, Tuple
|
|
5
5
|
from art import tprint
|
|
6
6
|
from .ipv4 import get_public_ipv4, get_private_ipv4
|
|
7
|
+
from .ipv6 import get_public_ipv6, get_private_ipv6
|
|
7
8
|
from .utils import _filter_parameter
|
|
8
|
-
from .params import IPv4API, PARAMETERS_NAME_MAP
|
|
9
|
+
from .params import IPv4API, IPv6API, PARAMETERS_NAME_MAP
|
|
9
10
|
from .params import IPSPOT_OVERVIEW, IPSPOT_REPO, IPSPOT_VERSION
|
|
10
11
|
|
|
11
12
|
|
|
@@ -17,42 +18,72 @@ def ipspot_info() -> None: # pragma: no cover
|
|
|
17
18
|
print("Repo : " + IPSPOT_REPO)
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE,
|
|
21
|
+
def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE,
|
|
22
|
+
ipv6_api: IPv6API = IPv6API.AUTO_SAFE,
|
|
23
|
+
geo: bool=False,
|
|
21
24
|
timeout: Union[float, Tuple[float, float]]=5,
|
|
22
25
|
max_retries: int = 0, retry_delay: float = 1.0) -> None: # pragma: no cover
|
|
23
26
|
"""
|
|
24
27
|
Print collected IP and location data.
|
|
25
28
|
|
|
26
29
|
:param ipv4_api: public IPv4 API
|
|
30
|
+
:param ipv6_api: public IPv6 API
|
|
27
31
|
:param geo: geolocation flag
|
|
28
32
|
:param timeout: timeout value for API
|
|
29
33
|
:param max_retries: number of retries
|
|
30
34
|
:param retry_delay: delay between retries (in seconds)
|
|
31
35
|
"""
|
|
32
|
-
private_result = get_private_ipv4()
|
|
33
36
|
print("Private IP:\n")
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
private_ipv4_result = get_private_ipv4()
|
|
38
|
+
if private_ipv4_result["status"]:
|
|
39
|
+
private_ipv4 = private_ipv4_result["data"]["ip"]
|
|
40
|
+
else:
|
|
41
|
+
private_ipv4 = private_ipv4_result["error"]
|
|
42
|
+
print(" IPv4: {private_ipv4}\n".format(private_ipv4=private_ipv4))
|
|
43
|
+
|
|
44
|
+
private_ipv6_result = get_private_ipv6()
|
|
45
|
+
if private_ipv6_result["status"]:
|
|
46
|
+
private_ipv6 = private_ipv6_result["data"]["ip"]
|
|
47
|
+
else:
|
|
48
|
+
private_ipv6 = private_ipv6_result["error"]
|
|
49
|
+
print(" IPv6: {private_ipv6}".format(private_ipv6=private_ipv6))
|
|
36
50
|
|
|
37
51
|
public_title = "\nPublic IP"
|
|
38
52
|
if geo:
|
|
39
53
|
public_title += " and Location Info"
|
|
40
54
|
public_title += ":\n"
|
|
41
55
|
print(public_title)
|
|
42
|
-
|
|
56
|
+
print(" IPv4:\n")
|
|
57
|
+
public_ipv4_result = get_public_ipv4(
|
|
43
58
|
ipv4_api,
|
|
44
59
|
geo=geo,
|
|
45
60
|
timeout=timeout,
|
|
46
61
|
max_retries=max_retries,
|
|
47
62
|
retry_delay=retry_delay)
|
|
48
|
-
if
|
|
49
|
-
for name, parameter in sorted(
|
|
63
|
+
if public_ipv4_result["status"]:
|
|
64
|
+
for name, parameter in sorted(public_ipv4_result["data"].items()):
|
|
50
65
|
print(
|
|
51
|
-
"
|
|
66
|
+
" {name}: {parameter}".format(
|
|
52
67
|
name=PARAMETERS_NAME_MAP[name],
|
|
53
68
|
parameter=_filter_parameter(parameter)))
|
|
54
69
|
else:
|
|
55
|
-
print("
|
|
70
|
+
print(" Error: {public_ipv4_result[error]}".format(public_ipv4_result=public_ipv4_result))
|
|
71
|
+
|
|
72
|
+
print("\n IPv6:\n")
|
|
73
|
+
public_ipv6_result = get_public_ipv6(
|
|
74
|
+
ipv6_api,
|
|
75
|
+
geo=geo,
|
|
76
|
+
timeout=timeout,
|
|
77
|
+
max_retries=max_retries,
|
|
78
|
+
retry_delay=retry_delay)
|
|
79
|
+
if public_ipv6_result["status"]:
|
|
80
|
+
for name, parameter in sorted(public_ipv6_result["data"].items()):
|
|
81
|
+
print(
|
|
82
|
+
" {name}: {parameter}".format(
|
|
83
|
+
name=PARAMETERS_NAME_MAP[name],
|
|
84
|
+
parameter=_filter_parameter(parameter)))
|
|
85
|
+
else:
|
|
86
|
+
print(" Error: {public_ipv6_result[error]}".format(public_ipv6_result=public_ipv6_result))
|
|
56
87
|
|
|
57
88
|
|
|
58
89
|
def main() -> None: # pragma: no cover
|
|
@@ -65,6 +96,13 @@ def main() -> None: # pragma: no cover
|
|
|
65
96
|
choices=[
|
|
66
97
|
x.value for x in IPv4API],
|
|
67
98
|
default=IPv4API.AUTO_SAFE.value)
|
|
99
|
+
parser.add_argument(
|
|
100
|
+
'--ipv6-api',
|
|
101
|
+
help='public IPv6 API',
|
|
102
|
+
type=str.lower,
|
|
103
|
+
choices=[
|
|
104
|
+
x.value for x in IPv6API],
|
|
105
|
+
default=IPv6API.AUTO_SAFE.value)
|
|
68
106
|
parser.add_argument('--info', help='info', nargs="?", const=1)
|
|
69
107
|
parser.add_argument('--version', help='version', nargs="?", const=1)
|
|
70
108
|
parser.add_argument('--no-geo', help='no geolocation data', nargs="?", const=1, default=False)
|
|
@@ -79,9 +117,11 @@ def main() -> None: # pragma: no cover
|
|
|
79
117
|
ipspot_info()
|
|
80
118
|
else:
|
|
81
119
|
ipv4_api = IPv4API(args.ipv4_api)
|
|
120
|
+
ipv6_api = IPv6API(args.ipv6_api)
|
|
82
121
|
geo = not args.no_geo
|
|
83
122
|
display_ip_info(
|
|
84
123
|
ipv4_api=ipv4_api,
|
|
124
|
+
ipv6_api=ipv6_api,
|
|
85
125
|
geo=geo,
|
|
86
126
|
timeout=args.timeout,
|
|
87
127
|
max_retries=args.max_retries,
|
ipspot/ipv4.py
CHANGED
|
@@ -180,7 +180,7 @@ def _my_ip_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
|
180
180
|
return {"status": False, "error": str(e)}
|
|
181
181
|
|
|
182
182
|
|
|
183
|
-
def _ifconfig_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
183
|
+
def _ifconfig_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]] # very low rate limit
|
|
184
184
|
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
185
185
|
"""
|
|
186
186
|
Get public IP and geolocation using ifconfig.co.
|
|
@@ -436,6 +436,94 @@ def _freeipapi_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, floa
|
|
|
436
436
|
return {"status": False, "error": str(e)}
|
|
437
437
|
|
|
438
438
|
|
|
439
|
+
def _ipquery_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
|
|
440
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
441
|
+
"""
|
|
442
|
+
Get public IP and geolocation using ipquery.io.
|
|
443
|
+
|
|
444
|
+
:param geo: geolocation flag
|
|
445
|
+
:param timeout: timeout value for API
|
|
446
|
+
"""
|
|
447
|
+
try:
|
|
448
|
+
data = _get_json_ipv4_forced(url="https://api.ipquery.io/?format=json", timeout=timeout)
|
|
449
|
+
result = {"status": True, "data": {"ip": data["ip"], "api": "ipquery.io"}}
|
|
450
|
+
if geo:
|
|
451
|
+
loc = data.get("location", {})
|
|
452
|
+
isp = data.get("isp", {})
|
|
453
|
+
geo_data = {
|
|
454
|
+
"city": loc.get("city"),
|
|
455
|
+
"region": loc.get("state"),
|
|
456
|
+
"country": loc.get("country"),
|
|
457
|
+
"country_code": loc.get("country_code"),
|
|
458
|
+
"latitude": loc.get("latitude"),
|
|
459
|
+
"longitude": loc.get("longitude"),
|
|
460
|
+
"timezone": loc.get("timezone"),
|
|
461
|
+
"organization": isp.get("org"),
|
|
462
|
+
}
|
|
463
|
+
result["data"].update(geo_data)
|
|
464
|
+
return result
|
|
465
|
+
except Exception as e:
|
|
466
|
+
return {"status": False, "error": str(e)}
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _ipwho_is_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
|
|
470
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
471
|
+
"""
|
|
472
|
+
Get public IP and geolocation using ipwho.is.
|
|
473
|
+
|
|
474
|
+
:param geo: geolocation flag
|
|
475
|
+
:param timeout: timeout value for API
|
|
476
|
+
"""
|
|
477
|
+
try:
|
|
478
|
+
data = _get_json_ipv4_forced(url="https://ipwho.is", timeout=timeout)
|
|
479
|
+
result = {"status": True, "data": {"ip": data["ip"], "api": "ipwho.is"}}
|
|
480
|
+
if geo:
|
|
481
|
+
connection = data.get("connection", {})
|
|
482
|
+
timezone = data.get("timezone", {})
|
|
483
|
+
geo_data = {
|
|
484
|
+
"city": data.get("city"),
|
|
485
|
+
"region": data.get("region"),
|
|
486
|
+
"country": data.get("country"),
|
|
487
|
+
"country_code": data.get("country_code"),
|
|
488
|
+
"latitude": data.get("latitude"),
|
|
489
|
+
"longitude": data.get("longitude"),
|
|
490
|
+
"organization": connection.get("org"),
|
|
491
|
+
"timezone": timezone.get("id")
|
|
492
|
+
}
|
|
493
|
+
result["data"].update(geo_data)
|
|
494
|
+
return result
|
|
495
|
+
except Exception as e:
|
|
496
|
+
return {"status": False, "error": str(e)}
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _wtfismyip_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
|
|
500
|
+
) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
501
|
+
"""
|
|
502
|
+
Get public IP and geolocation using wtfismyip.com.
|
|
503
|
+
|
|
504
|
+
:param geo: geolocation flag
|
|
505
|
+
:param timeout: timeout value for API
|
|
506
|
+
"""
|
|
507
|
+
try:
|
|
508
|
+
data = _get_json_standard(url="https://json.ipv4.wtfismyip.com", timeout=timeout)
|
|
509
|
+
result = {"status": True, "data": {"ip": data["YourFuckingIPAddress"], "api": "wtfismyip.com"}}
|
|
510
|
+
if geo:
|
|
511
|
+
geo_data = {
|
|
512
|
+
"city": data.get("YourFuckingCity"),
|
|
513
|
+
"region": None,
|
|
514
|
+
"country": data.get("YourFuckingCountry"),
|
|
515
|
+
"country_code": data.get("YourFuckingCountryCode"),
|
|
516
|
+
"latitude": None,
|
|
517
|
+
"longitude": None,
|
|
518
|
+
"organization": data.get("YourFuckingISP"),
|
|
519
|
+
"timezone": None
|
|
520
|
+
}
|
|
521
|
+
result["data"].update(geo_data)
|
|
522
|
+
return result
|
|
523
|
+
except Exception as e:
|
|
524
|
+
return {"status": False, "error": str(e)}
|
|
525
|
+
|
|
526
|
+
|
|
439
527
|
IPV4_API_MAP = {
|
|
440
528
|
IPv4API.IFCONFIG_CO: {
|
|
441
529
|
"thread_safe": False,
|
|
@@ -497,6 +585,21 @@ IPV4_API_MAP = {
|
|
|
497
585
|
"geo": True,
|
|
498
586
|
"function": _myip_la_ipv4,
|
|
499
587
|
},
|
|
588
|
+
IPv4API.IPQUERY_IO: {
|
|
589
|
+
"thread_safe": False,
|
|
590
|
+
"geo": True,
|
|
591
|
+
"function": _ipquery_io_ipv4,
|
|
592
|
+
},
|
|
593
|
+
IPv4API.IPWHO_IS: {
|
|
594
|
+
"thread_safe": False,
|
|
595
|
+
"geo": True,
|
|
596
|
+
"function": _ipwho_is_ipv4,
|
|
597
|
+
},
|
|
598
|
+
IPv4API.WTFISMYIP_COM: {
|
|
599
|
+
"thread_safe": True,
|
|
600
|
+
"geo": True,
|
|
601
|
+
"function": _wtfismyip_com_ipv4
|
|
602
|
+
},
|
|
500
603
|
}
|
|
501
604
|
|
|
502
605
|
|
ipspot/ipv6.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""ipspot ipv6 functions."""
|
|
3
|
+
import ipaddress
|
|
4
|
+
import socket
|
|
5
|
+
from typing import Union, Dict, List, Tuple
|
|
6
|
+
from .params import IPv6API
|
|
7
|
+
from .utils import is_loopback, _get_json_standard, _attempt_with_retries
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def is_ipv6(ip: str) -> bool:
|
|
11
|
+
"""
|
|
12
|
+
Check if the given input is a valid IPv6 address.
|
|
13
|
+
|
|
14
|
+
:param ip: input IP
|
|
15
|
+
"""
|
|
16
|
+
if not isinstance(ip, str):
|
|
17
|
+
return False
|
|
18
|
+
try:
|
|
19
|
+
_ = ipaddress.IPv6Address(ip)
|
|
20
|
+
return True
|
|
21
|
+
except Exception:
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_private_ipv6() -> Dict[str, Union[bool, Dict[str, str], str]]:
|
|
26
|
+
"""Retrieve the private IPv6 address."""
|
|
27
|
+
try:
|
|
28
|
+
with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as s:
|
|
29
|
+
s.connect(("2001:4860:4860::8888", 80))
|
|
30
|
+
private_ip = s.getsockname()[0]
|
|
31
|
+
private_ip = private_ip.split("%")[0]
|
|
32
|
+
if is_ipv6(private_ip) and not is_loopback(private_ip):
|
|
33
|
+
return {"status": True, "data": {"ip": private_ip}}
|
|
34
|
+
return {"status": False, "error": "Could not identify a non-loopback IPv6 address for this system."}
|
|
35
|
+
except Exception as e:
|
|
36
|
+
return {"status": False, "error": str(e)}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _ip_sb_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
40
|
+
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
41
|
+
"""
|
|
42
|
+
Get public IP and geolocation using ip.sb.
|
|
43
|
+
|
|
44
|
+
:param geo: geolocation flag
|
|
45
|
+
:param timeout: timeout value for API
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
data = _get_json_standard(url="https://api-ipv6.ip.sb/geoip", timeout=timeout)
|
|
49
|
+
result = {"status": True, "data": {"ip": data["ip"], "api": "ip.sb"}}
|
|
50
|
+
if geo:
|
|
51
|
+
geo_data = {
|
|
52
|
+
"city": data.get("city"),
|
|
53
|
+
"region": data.get("region"),
|
|
54
|
+
"country": data.get("country"),
|
|
55
|
+
"country_code": data.get("country_code"),
|
|
56
|
+
"latitude": data.get("latitude"),
|
|
57
|
+
"longitude": data.get("longitude"),
|
|
58
|
+
"organization": data.get("organization"),
|
|
59
|
+
"timezone": data.get("timezone")
|
|
60
|
+
}
|
|
61
|
+
result["data"].update(geo_data)
|
|
62
|
+
return result
|
|
63
|
+
except Exception as e:
|
|
64
|
+
return {"status": False, "error": str(e)}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _ident_me_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
68
|
+
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
69
|
+
"""
|
|
70
|
+
Get public IP and geolocation using ident.me.
|
|
71
|
+
|
|
72
|
+
:param geo: geolocation flag
|
|
73
|
+
:param timeout: timeout value for API
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
data = _get_json_standard(url="https://6.ident.me/json", timeout=timeout)
|
|
77
|
+
result = {"status": True, "data": {"ip": data["ip"], "api": "ident.me"}}
|
|
78
|
+
if geo:
|
|
79
|
+
geo_data = {
|
|
80
|
+
"city": data.get("city"),
|
|
81
|
+
"region": None,
|
|
82
|
+
"country": data.get("country"),
|
|
83
|
+
"country_code": data.get("cc"),
|
|
84
|
+
"latitude": data.get("latitude"),
|
|
85
|
+
"longitude": data.get("longitude"),
|
|
86
|
+
"organization": data.get("aso"),
|
|
87
|
+
"timezone": data.get("tz")
|
|
88
|
+
}
|
|
89
|
+
result["data"].update(geo_data)
|
|
90
|
+
return result
|
|
91
|
+
except Exception as e:
|
|
92
|
+
return {"status": False, "error": str(e)}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _tnedi_me_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
96
|
+
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
97
|
+
"""
|
|
98
|
+
Get public IP and geolocation using tnedi.me.
|
|
99
|
+
|
|
100
|
+
:param geo: geolocation flag
|
|
101
|
+
:param timeout: timeout value for API
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
data = _get_json_standard(url="https://6.tnedi.me/json", timeout=timeout)
|
|
105
|
+
result = {"status": True, "data": {"ip": data["ip"], "api": "tnedi.me"}}
|
|
106
|
+
if geo:
|
|
107
|
+
geo_data = {
|
|
108
|
+
"city": data.get("city"),
|
|
109
|
+
"region": None,
|
|
110
|
+
"country": data.get("country"),
|
|
111
|
+
"country_code": data.get("cc"),
|
|
112
|
+
"latitude": data.get("latitude"),
|
|
113
|
+
"longitude": data.get("longitude"),
|
|
114
|
+
"organization": data.get("aso"),
|
|
115
|
+
"timezone": data.get("tz")
|
|
116
|
+
}
|
|
117
|
+
result["data"].update(geo_data)
|
|
118
|
+
return result
|
|
119
|
+
except Exception as e:
|
|
120
|
+
return {"status": False, "error": str(e)}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _ipleak_net_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
124
|
+
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
125
|
+
"""
|
|
126
|
+
Get public IP and geolocation using ipleak.net.
|
|
127
|
+
|
|
128
|
+
:param geo: geolocation flag
|
|
129
|
+
:param timeout: timeout value for API
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
data = _get_json_standard(url="https://ipv6.ipleak.net/json/", timeout=timeout)
|
|
133
|
+
result = {"status": True, "data": {"ip": data["ip"], "api": "ipleak.net"}}
|
|
134
|
+
if geo:
|
|
135
|
+
geo_data = {
|
|
136
|
+
"city": data.get("city_name"),
|
|
137
|
+
"region": data.get("region_name"),
|
|
138
|
+
"country": data.get("country_name"),
|
|
139
|
+
"country_code": data.get("country_code"),
|
|
140
|
+
"latitude": data.get("latitude"),
|
|
141
|
+
"longitude": data.get("longitude"),
|
|
142
|
+
"organization": data.get("isp_name"),
|
|
143
|
+
"timezone": data.get("time_zone")
|
|
144
|
+
}
|
|
145
|
+
result["data"].update(geo_data)
|
|
146
|
+
return result
|
|
147
|
+
except Exception as e:
|
|
148
|
+
return {"status": False, "error": str(e)}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _my_ip_io_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
|
|
152
|
+
=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
153
|
+
"""
|
|
154
|
+
Get public IP and geolocation using my-ip.io.
|
|
155
|
+
|
|
156
|
+
:param geo: geolocation flag
|
|
157
|
+
:param timeout: timeout value for API
|
|
158
|
+
"""
|
|
159
|
+
try:
|
|
160
|
+
data = _get_json_standard(url="https://api6.my-ip.io/v2/ip.json", timeout=timeout)
|
|
161
|
+
result = {"status": True, "data": {"ip": data["ip"], "api": "my-ip.io"}}
|
|
162
|
+
if geo:
|
|
163
|
+
geo_data = {
|
|
164
|
+
"city": data.get("city"),
|
|
165
|
+
"region": data.get("region"),
|
|
166
|
+
"country": data.get("country", {}).get("name"),
|
|
167
|
+
"country_code": data.get("country", {}).get("code"),
|
|
168
|
+
"latitude": data.get("location", {}).get("lat"),
|
|
169
|
+
"longitude": data.get("location", {}).get("lon"),
|
|
170
|
+
"organization": data.get("asn", {}).get("name"),
|
|
171
|
+
"timezone": data.get("timeZone")
|
|
172
|
+
}
|
|
173
|
+
result["data"].update(geo_data)
|
|
174
|
+
return result
|
|
175
|
+
except Exception as e:
|
|
176
|
+
return {"status": False, "error": str(e)}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
IPV6_API_MAP = {
|
|
180
|
+
IPv6API.IP_SB: {
|
|
181
|
+
"thread_safe": True,
|
|
182
|
+
"geo": True,
|
|
183
|
+
"function": _ip_sb_ipv6
|
|
184
|
+
},
|
|
185
|
+
IPv6API.IDENT_ME: {
|
|
186
|
+
"thread_safe": True,
|
|
187
|
+
"geo": True,
|
|
188
|
+
"function": _ident_me_ipv6
|
|
189
|
+
},
|
|
190
|
+
IPv6API.TNEDI_ME: {
|
|
191
|
+
"thread_safe": True,
|
|
192
|
+
"geo": True,
|
|
193
|
+
"function": _tnedi_me_ipv6
|
|
194
|
+
},
|
|
195
|
+
IPv6API.IPLEAK_NET: {
|
|
196
|
+
"thread_safe": True,
|
|
197
|
+
"geo": True,
|
|
198
|
+
"function": _ipleak_net_ipv6
|
|
199
|
+
},
|
|
200
|
+
IPv6API.MY_IP_IO: {
|
|
201
|
+
"thread_safe": True,
|
|
202
|
+
"geo": True,
|
|
203
|
+
"function": _my_ip_io_ipv6
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def get_public_ipv6(api: IPv6API=IPv6API.AUTO_SAFE, geo: bool=False,
|
|
209
|
+
timeout: Union[float, Tuple[float, float]]=5,
|
|
210
|
+
max_retries: int = 0,
|
|
211
|
+
retry_delay: float = 1.0) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
|
|
212
|
+
"""
|
|
213
|
+
Get public IPv6 and geolocation info based on the selected API.
|
|
214
|
+
|
|
215
|
+
:param api: public IPv6 API
|
|
216
|
+
:param geo: geolocation flag
|
|
217
|
+
:param timeout: timeout value for API
|
|
218
|
+
:param max_retries: number of retries
|
|
219
|
+
:param retry_delay: delay between retries (in seconds)
|
|
220
|
+
"""
|
|
221
|
+
if api in [IPv6API.AUTO, IPv6API.AUTO_SAFE]:
|
|
222
|
+
for _, api_data in IPV6_API_MAP.items():
|
|
223
|
+
if api == IPv6API.AUTO_SAFE and not api_data["thread_safe"]:
|
|
224
|
+
continue
|
|
225
|
+
func = api_data["function"]
|
|
226
|
+
result = _attempt_with_retries(
|
|
227
|
+
func=func,
|
|
228
|
+
max_retries=max_retries,
|
|
229
|
+
retry_delay=retry_delay,
|
|
230
|
+
geo=geo,
|
|
231
|
+
timeout=timeout)
|
|
232
|
+
if result["status"]:
|
|
233
|
+
return result
|
|
234
|
+
return {"status": False, "error": "All attempts failed."}
|
|
235
|
+
else:
|
|
236
|
+
api_data = IPV6_API_MAP.get(api)
|
|
237
|
+
if api_data:
|
|
238
|
+
func = api_data["function"]
|
|
239
|
+
return _attempt_with_retries(
|
|
240
|
+
func=func,
|
|
241
|
+
max_retries=max_retries,
|
|
242
|
+
retry_delay=retry_delay,
|
|
243
|
+
geo=geo,
|
|
244
|
+
timeout=timeout)
|
|
245
|
+
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.5"
|
|
6
6
|
|
|
7
7
|
IPSPOT_OVERVIEW = '''
|
|
8
8
|
IPSpot is a Python library for retrieving the current system's IP address and location information.
|
|
@@ -35,6 +35,21 @@ class IPv4API(Enum):
|
|
|
35
35
|
REALLYFREEGEOIP_ORG = "reallyfreegeoip.org"
|
|
36
36
|
MYIP_LA = "myip.la"
|
|
37
37
|
FREEIPAPI_COM = "freeipapi.com"
|
|
38
|
+
IPQUERY_IO = "ipquery.io"
|
|
39
|
+
IPWHO_IS = "ipwho.is"
|
|
40
|
+
WTFISMYIP_COM = "wtfismyip.com"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class IPv6API(Enum):
|
|
44
|
+
"""Public IPv6 API enum."""
|
|
45
|
+
|
|
46
|
+
AUTO = "auto"
|
|
47
|
+
AUTO_SAFE = "auto-safe"
|
|
48
|
+
IP_SB = "ip.sb"
|
|
49
|
+
IDENT_ME = "ident.me"
|
|
50
|
+
TNEDI_ME = "tnedi.me"
|
|
51
|
+
IPLEAK_NET = "ipleak.net"
|
|
52
|
+
MY_IP_IO = "my-ip.io"
|
|
38
53
|
|
|
39
54
|
|
|
40
55
|
PARAMETERS_NAME_MAP = {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipspot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5
|
|
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.5
|
|
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 :: 3 - Alpha
|
|
13
13
|
Classifier: Natural Language :: English
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
@@ -102,13 +102,13 @@ Dynamic: summary
|
|
|
102
102
|
## Installation
|
|
103
103
|
|
|
104
104
|
### Source Code
|
|
105
|
-
- Download [Version 0.
|
|
105
|
+
- Download [Version 0.5](https://github.com/openscilab/ipspot/archive/v0.5.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.5`
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
## Usage
|
|
@@ -135,6 +135,26 @@ Dynamic: summary
|
|
|
135
135
|
{'status': True, 'data': {'ip': '10.36.18.154'}}
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
+
#### Public IPv6
|
|
139
|
+
|
|
140
|
+
```pycon
|
|
141
|
+
>>> from ipspot import get_public_ipv6, IPv6API
|
|
142
|
+
>>> get_public_ipv6(api=IPv6API.IP_SB)
|
|
143
|
+
{'data': {'api': 'ip.sb', 'ip': 'xx:xx:xx:xx::xx'}, 'status': True}
|
|
144
|
+
>>> get_public_ipv6(api=IPv6API.IP_SB, geo=True, timeout=10)
|
|
145
|
+
{'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)
|
|
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}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### Private IPv6
|
|
151
|
+
|
|
152
|
+
```pycon
|
|
153
|
+
>>> from ipspot import get_private_ipv6
|
|
154
|
+
>>> get_private_ipv6()
|
|
155
|
+
{'status': True, 'data': {'ip': 'fe80::e1bd:f78:b233:21c9'}}
|
|
156
|
+
```
|
|
157
|
+
|
|
138
158
|
### CLI
|
|
139
159
|
|
|
140
160
|
ℹ️ You can use `ipspot` or `python -m ipspot` to run this program
|
|
@@ -144,7 +164,7 @@ Dynamic: summary
|
|
|
144
164
|
```console
|
|
145
165
|
> ipspot --version
|
|
146
166
|
|
|
147
|
-
0.
|
|
167
|
+
0.5
|
|
148
168
|
```
|
|
149
169
|
|
|
150
170
|
#### Info
|
|
@@ -159,11 +179,11 @@ Dynamic: summary
|
|
|
159
179
|
|___||_| |____/ | .__/ \___/ \__|
|
|
160
180
|
|_|
|
|
161
181
|
|
|
162
|
-
__ __ ___
|
|
163
|
-
\ \ / / _ / _ \ |
|
|
164
|
-
\ \ / / (_)| | | | |
|
|
165
|
-
\ V / _ | |_| | _ |
|
|
166
|
-
\_/ (_) \___/ (_)
|
|
182
|
+
__ __ ___ ____
|
|
183
|
+
\ \ / / _ / _ \ | ___|
|
|
184
|
+
\ \ / / (_)| | | | |___ \
|
|
185
|
+
\ V / _ | |_| | _ ___) |
|
|
186
|
+
\_/ (_) \___/ (_)|____/
|
|
167
187
|
|
|
168
188
|
|
|
169
189
|
|
|
@@ -181,7 +201,9 @@ Repo : https://github.com/openscilab/ipspot
|
|
|
181
201
|
> ipspot
|
|
182
202
|
Private IP:
|
|
183
203
|
|
|
184
|
-
|
|
204
|
+
IPv4: 192.168.1.35
|
|
205
|
+
|
|
206
|
+
IPv6: fe80::e1bd:f78:b233:21c9
|
|
185
207
|
|
|
186
208
|
Public IP and Location Info:
|
|
187
209
|
|
|
@@ -199,7 +221,7 @@ Public IP and Location Info:
|
|
|
199
221
|
|
|
200
222
|
#### IPv4 API
|
|
201
223
|
|
|
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`]
|
|
224
|
+
ℹ️ `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`, `ipquery.io`, `ipwho.is`, `wtfismyip.com`]
|
|
203
225
|
|
|
204
226
|
ℹ️ The default value: `auto-safe`
|
|
205
227
|
|
|
@@ -207,20 +229,80 @@ Public IP and Location Info:
|
|
|
207
229
|
> ipspot --ipv4-api="ipinfo.io"
|
|
208
230
|
Private IP:
|
|
209
231
|
|
|
210
|
-
|
|
232
|
+
IPv4: 192.168.1.35
|
|
233
|
+
|
|
234
|
+
IPv6: fe80::e1bd:f78:b233:21c9
|
|
211
235
|
|
|
212
236
|
Public IP and Location Info:
|
|
213
237
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
238
|
+
IPv4:
|
|
239
|
+
|
|
240
|
+
API: ipinfo.io
|
|
241
|
+
City: Nuremberg
|
|
242
|
+
Country: Germany
|
|
243
|
+
Country Code: DE
|
|
244
|
+
IP: xx.xx.xx.xx
|
|
245
|
+
Latitude: 49.4527
|
|
246
|
+
Longitude: 11.0783
|
|
247
|
+
Organization: Hetzner Online GmbH
|
|
248
|
+
Region: Bavaria
|
|
249
|
+
Timezone: Europe/Berlin
|
|
250
|
+
|
|
251
|
+
IPv6:
|
|
252
|
+
|
|
253
|
+
API: ip.sb
|
|
254
|
+
City: N/A
|
|
255
|
+
Country: Germany
|
|
256
|
+
Country Code: DE
|
|
257
|
+
IP: xx:xx:xx:xx::xx
|
|
258
|
+
Latitude: 51.2993
|
|
259
|
+
Longitude: 9.491
|
|
260
|
+
Organization: Hetzner Online
|
|
261
|
+
Region: N/A
|
|
262
|
+
Timezone: Europe/Berlin
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### IPv6 API
|
|
266
|
+
|
|
267
|
+
ℹ️ `ipv6-api` valid choices: [`auto-safe`, `auto`, `ip.sb`, `ident.me`, `tnedi.me`, `ipleak.net`, `my-ip.io`]
|
|
268
|
+
|
|
269
|
+
ℹ️ The default value: `auto-safe`
|
|
270
|
+
|
|
271
|
+
```console
|
|
272
|
+
> ipspot --ipv6-api="ip.sb"
|
|
273
|
+
Private IP:
|
|
274
|
+
|
|
275
|
+
IPv4: 192.168.1.35
|
|
276
|
+
|
|
277
|
+
IPv6: fe80::e1bd:f78:b233:21c9
|
|
278
|
+
|
|
279
|
+
Public IP and Location Info:
|
|
280
|
+
|
|
281
|
+
IPv4:
|
|
282
|
+
|
|
283
|
+
API: ipinfo.io
|
|
284
|
+
City: Nuremberg
|
|
285
|
+
Country: Germany
|
|
286
|
+
Country Code: DE
|
|
287
|
+
IP: xx.xx.xx.xx
|
|
288
|
+
Latitude: 49.4527
|
|
289
|
+
Longitude: 11.0783
|
|
290
|
+
Organization: Hetzner Online GmbH
|
|
291
|
+
Region: Bavaria
|
|
292
|
+
Timezone: Europe/Berlin
|
|
293
|
+
|
|
294
|
+
IPv6:
|
|
295
|
+
|
|
296
|
+
API: ip.sb
|
|
297
|
+
City: N/A
|
|
298
|
+
Country: Germany
|
|
299
|
+
Country Code: DE
|
|
300
|
+
IP: xx:xx:xx:xx::xx
|
|
301
|
+
Latitude: 51.2993
|
|
302
|
+
Longitude: 9.491
|
|
303
|
+
Organization: Hetzner Online
|
|
304
|
+
Region: N/A
|
|
305
|
+
Timezone: Europe/Berlin
|
|
224
306
|
```
|
|
225
307
|
|
|
226
308
|
#### No Geolocation
|
|
@@ -229,12 +311,21 @@ Public IP and Location Info:
|
|
|
229
311
|
> ipspot --no-geo
|
|
230
312
|
Private IP:
|
|
231
313
|
|
|
232
|
-
|
|
314
|
+
IPv4: 192.168.1.35
|
|
315
|
+
|
|
316
|
+
IPv6: fe80::5c40:769f:22de:c196
|
|
233
317
|
|
|
234
318
|
Public IP:
|
|
235
319
|
|
|
236
|
-
|
|
237
|
-
|
|
320
|
+
IPv4:
|
|
321
|
+
|
|
322
|
+
API: tnedi.me
|
|
323
|
+
IP: xx.xx.xx.xx
|
|
324
|
+
|
|
325
|
+
IPv6:
|
|
326
|
+
|
|
327
|
+
API: ip.sb
|
|
328
|
+
IP: xx:xx:xx:xx::xx
|
|
238
329
|
```
|
|
239
330
|
|
|
240
331
|
## Issues & Bug Reports
|
|
@@ -269,6 +360,25 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
|
269
360
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
270
361
|
|
|
271
362
|
## [Unreleased]
|
|
363
|
+
## [0.5] - 2025-10-17
|
|
364
|
+
### Added
|
|
365
|
+
- `setup-warp` action
|
|
366
|
+
- Support [ipwho.is](https://ipwho.is/)
|
|
367
|
+
- Support [ipquery.io](http://api.ipquery.io/?format=json)
|
|
368
|
+
- Support [wtfismyip.com](https://wtfismyip.com/json)
|
|
369
|
+
- Support [ident.me](https://ident.me/json) IPv6 API
|
|
370
|
+
- Support [tnedi.me](https://tnedi.me/json) IPv6 API
|
|
371
|
+
- Support [ip.sb](https://api.ip.sb/geoip) IPv6 API
|
|
372
|
+
- Support [ipleak.net](https://ipleak.net/json/) IPv6 API
|
|
373
|
+
- Support [my-ip.io](https://www.my-ip.io/) IPv6 API
|
|
374
|
+
- `is_ipv6` function
|
|
375
|
+
- `get_private_ipv6` function
|
|
376
|
+
- `get_public_ipv6` function
|
|
377
|
+
- `IPv6API` enum
|
|
378
|
+
- `--ipv6-api` argument
|
|
379
|
+
### Changed
|
|
380
|
+
- Test system modified
|
|
381
|
+
- `README.md` updated
|
|
272
382
|
## [0.4] - 2025-06-09
|
|
273
383
|
### Added
|
|
274
384
|
- Support [ipapi.co](https://ipapi.co/json/)
|
|
@@ -328,7 +438,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
328
438
|
- `--no-geo` argument
|
|
329
439
|
- Logo
|
|
330
440
|
|
|
331
|
-
[Unreleased]: https://github.com/openscilab/ipspot/compare/v0.
|
|
441
|
+
[Unreleased]: https://github.com/openscilab/ipspot/compare/v0.5...dev
|
|
442
|
+
[0.5]: https://github.com/openscilab/ipspot/compare/v0.4...v0.5
|
|
332
443
|
[0.4]: https://github.com/openscilab/ipspot/compare/v0.3...v0.4
|
|
333
444
|
[0.3]: https://github.com/openscilab/ipspot/compare/v0.2...v0.3
|
|
334
445
|
[0.2]: https://github.com/openscilab/ipspot/compare/v0.1...v0.2
|
|
@@ -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=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,,
|
ipspot-0.4.dist-info/RECORD
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
ipspot/__init__.py,sha256=e_1FqqNDD9eoDVw9eKL_1pS7du-Sei3lm6A4UFjYiUo,211
|
|
2
|
-
ipspot/__main__.py,sha256=xKHY_tc94SWktkIHV0O9YADAHPZvT8yXOFSm_L2yMno,105
|
|
3
|
-
ipspot/cli.py,sha256=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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|