ipspot 0.2__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 CHANGED
@@ -1,5 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """ipspot modules."""
3
3
  from .params import IPSPOT_VERSION, IPv4API
4
- from .functions import get_private_ipv4, get_public_ipv4
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
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """ipspot main."""
3
- from .functions import main
3
+ from .cli import main
4
4
 
5
5
 
6
6
  if __name__ == "__main__":
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.2"
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.
@@ -25,6 +25,8 @@ class IPv4API(Enum):
25
25
  IPAPI = "ipapi"
26
26
  IPINFO = "ipinfo"
27
27
  IPSB = "ipsb"
28
+ IDENTME = "identme"
29
+ TNEDIME = "tnedime"
28
30
 
29
31
 
30
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.2
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.2
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
@@ -102,13 +102,13 @@ Dynamic: summary
102
102
  ## Installation
103
103
 
104
104
  ### Source Code
105
- - Download [Version 0.2](https://github.com/openscilab/ipspot/archive/v0.2.zip) or [Latest Source](https://github.com/openscilab/ipspot/archive/dev.zip)
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)
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.2`
111
+ - `pip install ipspot==0.3`
112
112
 
113
113
 
114
114
  ## Usage
@@ -142,7 +142,7 @@ Dynamic: summary
142
142
  ```console
143
143
  > ipspot --version
144
144
 
145
- 0.2
145
+ 0.3
146
146
  ```
147
147
 
148
148
  #### Info
@@ -157,11 +157,11 @@ Dynamic: summary
157
157
  |___||_| |____/ | .__/ \___/ \__|
158
158
  |_|
159
159
 
160
- __ __ ___ ____
161
- \ \ / / _ / _ \ |___ \
162
- \ \ / / (_)| | | | __) |
163
- \ V / _ | |_| | _ / __/
164
- \_/ (_) \___/ (_)|_____|
160
+ __ __ ___ _____
161
+ \ \ / / _ / _ \ |___ /
162
+ \ \ / / (_)| | | | |_ \
163
+ \ V / _ | |_| | _ ___) |
164
+ \_/ (_) \___/ (_)|____/
165
165
 
166
166
 
167
167
 
@@ -197,7 +197,7 @@ Public IP and Location Info:
197
197
 
198
198
  #### IPv4 API
199
199
 
200
- ℹ️ `ipv4-api` valid choices: [`auto`, `ipapi`, `ipinfo`, `ipsb`]
200
+ ℹ️ `ipv4-api` valid choices: [`auto`, `ipapi`, `ipinfo`, `ipsb`, `identme`, `tnedime`]
201
201
 
202
202
  ℹ️ The default value: `auto`
203
203
 
@@ -267,6 +267,23 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
267
267
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
268
268
 
269
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
270
287
  ## [0.2] - 2025-05-04
271
288
  ### Added
272
289
  - Support [ip.sb](https://api.ip.sb/geoip)
@@ -286,7 +303,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
286
303
  - `--no-geo` argument
287
304
  - Logo
288
305
 
289
- [Unreleased]: https://github.com/openscilab/ipspot/compare/v0.2...dev
306
+ [Unreleased]: https://github.com/openscilab/ipspot/compare/v0.3...dev
307
+ [0.3]: https://github.com/openscilab/ipspot/compare/v0.2...v0.3
290
308
  [0.2]: https://github.com/openscilab/ipspot/compare/v0.1...v0.2
291
309
  [0.1]: https://github.com/openscilab/ipspot/compare/3216fb7...v0.1
292
310
 
@@ -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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ipspot = ipspot.cli:main
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)
@@ -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,,
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- ipspot = ipspot.functions:main