know-your-ip 0.2.1__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.
@@ -0,0 +1,50 @@
1
+ """Know Your IP
2
+
3
+ A Python package to get comprehensive data about IP addresses including:
4
+ - Geolocation (latitude/longitude, country, city, timezone)
5
+ - Security analysis (blacklist status via multiple services)
6
+ - Network information (open ports, running services)
7
+ - Network diagnostics (ping, traceroute)
8
+
9
+ Supports multiple data sources including MaxMind, AbuseIPDB, VirusTotal,
10
+ Shodan, Censys, and more.
11
+ """
12
+
13
+ from importlib.metadata import version
14
+
15
+ __version__ = version("know_your_ip")
16
+
17
+ from .config import KnowYourIPConfig, load_config
18
+ from .know_your_ip import (
19
+ abuseipdb_api,
20
+ abuseipdb_web,
21
+ apivoid_api,
22
+ censys_api,
23
+ geonames_timezone,
24
+ ipvoid_scan,
25
+ maxmind_geocode_ip,
26
+ ping,
27
+ query_ip,
28
+ shodan_api,
29
+ traceroute,
30
+ tzwhere_timezone,
31
+ virustotal_api,
32
+ )
33
+
34
+ __all__ = [
35
+ "KnowYourIPConfig",
36
+ "load_config",
37
+ "maxmind_geocode_ip",
38
+ "geonames_timezone",
39
+ "tzwhere_timezone",
40
+ "ipvoid_scan",
41
+ "abuseipdb_web",
42
+ "abuseipdb_api",
43
+ "censys_api",
44
+ "shodan_api",
45
+ "virustotal_api",
46
+ "ping",
47
+ "traceroute",
48
+ "query_ip",
49
+ "apivoid_api",
50
+ ]
know_your_ip/config.py ADDED
@@ -0,0 +1,415 @@
1
+ """Modern configuration system using Pydantic for validation and type safety."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import tomllib
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from pydantic import BaseModel, Field, field_validator
11
+
12
+
13
+ class MaxMindConfig(BaseModel):
14
+ """MaxMind GeoIP configuration."""
15
+
16
+ enabled: bool = True
17
+ db_path: Path = Path("./db")
18
+
19
+ @field_validator("db_path", mode="before")
20
+ @classmethod
21
+ def resolve_db_path(cls, v: str | Path) -> Path:
22
+ """Resolve relative paths to absolute paths."""
23
+ path = Path(v)
24
+ if not path.is_absolute():
25
+ # If relative, make it relative to package directory
26
+ package_dir = Path(__file__).parent
27
+ path = package_dir / path
28
+ return path
29
+
30
+
31
+ class GeoNamesConfig(BaseModel):
32
+ """GeoNames.org configuration."""
33
+
34
+ enabled: bool = False
35
+ username: str | None = None
36
+
37
+ @field_validator("username")
38
+ @classmethod
39
+ def validate_username(cls, v: str | None) -> str | None:
40
+ """Validate that username is provided if enabled."""
41
+ if v and v.startswith("<<<"):
42
+ return None # Placeholder value
43
+ return v
44
+
45
+
46
+ class AbuseIPDBConfig(BaseModel):
47
+ """AbuseIPDB configuration."""
48
+
49
+ enabled: bool = False
50
+ api_key: str | None = None
51
+ days: int = 180
52
+
53
+ @field_validator("api_key")
54
+ @classmethod
55
+ def validate_api_key(cls, v: str | None) -> str | None:
56
+ """Validate that API key is provided if enabled."""
57
+ if v and v.startswith("<<<"):
58
+ return None # Placeholder value
59
+ return v
60
+
61
+
62
+ class PingConfig(BaseModel):
63
+ """Ping configuration."""
64
+
65
+ enabled: bool = False
66
+ timeout: int = 3000
67
+ count: int = 3
68
+
69
+
70
+ class TracerouteConfig(BaseModel):
71
+ """Traceroute configuration."""
72
+
73
+ enabled: bool = False
74
+ max_hops: int = 30
75
+
76
+
77
+ class TzwhereConfig(BaseModel):
78
+ """tzwhere configuration."""
79
+
80
+ enabled: bool = True
81
+
82
+
83
+ class IPVoidConfig(BaseModel):
84
+ """IPVoid configuration."""
85
+
86
+ enabled: bool = True
87
+
88
+
89
+ class APIVoidConfig(BaseModel):
90
+ """APIVoid configuration."""
91
+
92
+ enabled: bool = False
93
+ api_key: str | None = None
94
+
95
+ @field_validator("api_key")
96
+ @classmethod
97
+ def validate_api_key(cls, v: str | None) -> str | None:
98
+ """Validate that API key is provided if enabled."""
99
+ if v and v.startswith("<<<"):
100
+ return None # Placeholder value
101
+ return v
102
+
103
+
104
+ class CensysConfig(BaseModel):
105
+ """Censys Platform API configuration.
106
+
107
+ Note: Legacy Censys Search v1/v2 APIs are deprecated as of 2025.
108
+ This uses the new Censys Platform API.
109
+ """
110
+
111
+ enabled: bool = False
112
+ api_url: str = "https://search.censys.io/api"
113
+ api_key: str | None = None
114
+
115
+ @field_validator("api_key")
116
+ @classmethod
117
+ def validate_api_key(cls, v: str | None) -> str | None:
118
+ """Validate that API key is provided if enabled."""
119
+ if v and v.startswith("<<<"):
120
+ return None # Placeholder value
121
+ return v
122
+
123
+
124
+ class ShodanConfig(BaseModel):
125
+ """Shodan configuration."""
126
+
127
+ enabled: bool = False
128
+ api_key: str | None = None
129
+
130
+ @field_validator("api_key")
131
+ @classmethod
132
+ def validate_api_key(cls, v: str | None) -> str | None:
133
+ """Validate that API key is provided if enabled."""
134
+ if v and v.startswith("<<<"):
135
+ return None # Placeholder value
136
+ return v
137
+
138
+
139
+ class VirusTotalConfig(BaseModel):
140
+ """VirusTotal configuration."""
141
+
142
+ enabled: bool = False
143
+ api_key: str | None = None
144
+
145
+ @field_validator("api_key")
146
+ @classmethod
147
+ def validate_api_key(cls, v: str | None) -> str | None:
148
+ """Validate that API key is provided if enabled."""
149
+ if v and v.startswith("<<<"):
150
+ return None # Placeholder value
151
+ return v
152
+
153
+
154
+ class OutputConfig(BaseModel):
155
+ """Output configuration."""
156
+
157
+ columns: list[str] = Field(
158
+ default=[
159
+ "ip",
160
+ "maxmind.continent.names.en",
161
+ "maxmind.country.names.en",
162
+ "maxmind.location.time_zone",
163
+ "maxmind.postal.code",
164
+ "maxmind.registered_country.names.en",
165
+ "tzwhere.timezone",
166
+ "abuseipdb.bad_isp",
167
+ "abuseipdb.categories",
168
+ "ipvoid.blacklist_status",
169
+ "ipvoid.reverse_dns",
170
+ "apivoid.anonymity.is_hosting",
171
+ "apivoid.anonymity.is_proxy",
172
+ "apivoid.anonymity.is_tor",
173
+ "apivoid.anonymity.is_vpn",
174
+ "apivoid.anonymity.is_webproxy",
175
+ "apivoid.blacklists.detection_rate",
176
+ "apivoid.blacklists.detections",
177
+ "apivoid.blacklists.engines_count",
178
+ "apivoid.blacklists.scantime",
179
+ "apivoid.information.city_name",
180
+ "apivoid.information.continent_code",
181
+ "apivoid.information.continent_name",
182
+ "apivoid.information.country_calling_code",
183
+ "apivoid.information.country_code",
184
+ "apivoid.information.country_currency",
185
+ "apivoid.information.country_name",
186
+ "apivoid.information.isp",
187
+ "apivoid.information.latitude",
188
+ "apivoid.information.longitude",
189
+ "apivoid.information.region_name",
190
+ "apivoid.information.reverse_dns",
191
+ "shodan.asn",
192
+ "shodan.isp",
193
+ "shodan.vulns",
194
+ "shodan.os",
195
+ "shodan.ports",
196
+ ]
197
+ )
198
+
199
+
200
+ class KnowYourIPConfig(BaseModel):
201
+ """Main configuration for Know Your IP."""
202
+
203
+ maxmind: MaxMindConfig = MaxMindConfig()
204
+ geonames: GeoNamesConfig = GeoNamesConfig()
205
+ abuseipdb: AbuseIPDBConfig = AbuseIPDBConfig()
206
+ ping: PingConfig = PingConfig()
207
+ traceroute: TracerouteConfig = TracerouteConfig()
208
+ tzwhere: TzwhereConfig = TzwhereConfig()
209
+ ipvoid: IPVoidConfig = IPVoidConfig()
210
+ apivoid: APIVoidConfig = APIVoidConfig()
211
+ censys: CensysConfig = CensysConfig()
212
+ shodan: ShodanConfig = ShodanConfig()
213
+ virustotal: VirusTotalConfig = VirusTotalConfig()
214
+ output: OutputConfig = OutputConfig()
215
+
216
+
217
+ def load_from_env() -> dict[str, Any]:
218
+ """Load configuration from environment variables.
219
+
220
+ Environment variables should follow the pattern:
221
+ KNOW_YOUR_IP_<SECTION>_<KEY>=value
222
+
223
+ Examples:
224
+ KNOW_YOUR_IP_MAXMIND_ENABLED=true
225
+ KNOW_YOUR_IP_GEONAMES_USERNAME=myusername
226
+ KNOW_YOUR_IP_ABUSEIPDB_API_KEY=myapikey
227
+ """
228
+ config = {}
229
+ prefix = "KNOW_YOUR_IP_"
230
+
231
+ for key, value in os.environ.items():
232
+ if not key.startswith(prefix):
233
+ continue
234
+
235
+ # Remove prefix and split into section and field
236
+ config_key = key[len(prefix) :].lower()
237
+ parts = config_key.split("_", 1)
238
+
239
+ if len(parts) != 2:
240
+ continue
241
+
242
+ section, field = parts
243
+
244
+ # Convert string values to appropriate types
245
+ match value.lower():
246
+ case "true" | "1" | "yes" | "on":
247
+ value = True
248
+ case "false" | "0" | "no" | "off":
249
+ value = False
250
+ case _ if value.isdigit():
251
+ value = int(value)
252
+
253
+ # Create nested dict structure
254
+ if section not in config:
255
+ config[section] = {}
256
+ config[section][field] = value
257
+
258
+ return config
259
+
260
+
261
+ def find_config_file() -> Path | None:
262
+ """Find configuration file in standard locations.
263
+
264
+ Search order:
265
+ 1. ./know_your_ip.toml (current directory)
266
+ 2. ~/.config/know-your-ip/config.toml (XDG config)
267
+ 3. ~/.know-your-ip.toml (home directory)
268
+ """
269
+ candidates = [
270
+ Path.cwd() / "know_your_ip.toml",
271
+ Path.home() / ".config" / "know-your-ip" / "config.toml",
272
+ Path.home() / ".know-your-ip.toml",
273
+ ]
274
+
275
+ for candidate in candidates:
276
+ if candidate.exists():
277
+ return candidate
278
+
279
+ return None
280
+
281
+
282
+ def load_config(config_file: Path | None = None) -> KnowYourIPConfig:
283
+ """Load configuration from multiple sources with proper validation.
284
+
285
+ Sources are loaded in this order (later sources override earlier ones):
286
+ 1. Default configuration (embedded in code)
287
+ 2. Configuration file (TOML format)
288
+ 3. Environment variables
289
+
290
+ Args:
291
+ config_file: Path to configuration file. If None, will search standard locations.
292
+
293
+ Returns:
294
+ Validated configuration object.
295
+
296
+ Raises:
297
+ ConfigurationError: If configuration is invalid.
298
+ """
299
+ config_dict = {}
300
+
301
+ # Load from file if provided or found
302
+ if config_file is None:
303
+ config_file = find_config_file()
304
+
305
+ if config_file and config_file.exists():
306
+ try:
307
+ with open(config_file, "rb") as f:
308
+ file_config = tomllib.load(f)
309
+ config_dict.update(file_config)
310
+ except (OSError, tomllib.TOMLDecodeError) as e:
311
+ raise ConfigurationError(
312
+ f"Failed to load config file {config_file}: {e}"
313
+ ) from e
314
+
315
+ # Override with environment variables
316
+ env_config = load_from_env()
317
+ for section, values in env_config.items():
318
+ if section not in config_dict:
319
+ config_dict[section] = {}
320
+ config_dict[section].update(values)
321
+
322
+ # Validate and return typed config
323
+ try:
324
+ return KnowYourIPConfig(**config_dict)
325
+ except Exception as e:
326
+ raise ConfigurationError(f"Configuration validation failed: {e}") from e
327
+
328
+
329
+ def create_default_config(output_file: Path) -> None:
330
+ """Create a default configuration file with sensible defaults."""
331
+
332
+ toml_content = """# Know Your IP Configuration
333
+ # See https://github.com/themains/know-your-ip for documentation
334
+
335
+ [maxmind]
336
+ enabled = true
337
+ db_path = "./db"
338
+
339
+ [geonames]
340
+ enabled = false
341
+ # username = "your_username_here" # Register at http://www.geonames.org/login
342
+
343
+ [abuseipdb]
344
+ enabled = false
345
+ # api_key = "your_api_key_here" # Register at https://www.abuseipdb.com/register
346
+ days = 180
347
+
348
+ [ping]
349
+ enabled = false
350
+ timeout = 3000
351
+ count = 3
352
+
353
+ [traceroute]
354
+ enabled = false
355
+ max_hops = 30
356
+
357
+ [tzwhere]
358
+ enabled = true
359
+
360
+ [ipvoid]
361
+ enabled = true
362
+
363
+ [apivoid]
364
+ enabled = false
365
+ # api_key = "your_api_key_here" # Register at https://app.apivoid.com/register
366
+
367
+ [censys]
368
+ enabled = false
369
+ api_url = "https://search.censys.io/api"
370
+ # api_key = "your_api_key_here" # Register at https://search.censys.io/register
371
+
372
+ [shodan]
373
+ enabled = false
374
+ # api_key = "your_api_key_here" # Register at https://account.shodan.io/register
375
+
376
+ [virustotal]
377
+ enabled = false
378
+ # api_key = "your_api_key_here" # Register at https://www.virustotal.com/
379
+
380
+ [output]
381
+ columns = [
382
+ "ip",
383
+ "maxmind.continent.names.en",
384
+ "maxmind.country.names.en",
385
+ "maxmind.location.time_zone",
386
+ "maxmind.postal.code",
387
+ "maxmind.registered_country.names.en",
388
+ "tzwhere.timezone",
389
+ "abuseipdb.bad_isp",
390
+ "abuseipdb.categories",
391
+ "ipvoid.blacklist_status",
392
+ "ipvoid.reverse_dns",
393
+ "apivoid.anonymity.is_hosting",
394
+ "apivoid.anonymity.is_proxy",
395
+ "apivoid.anonymity.is_tor",
396
+ "apivoid.anonymity.is_vpn",
397
+ "apivoid.anonymity.is_webproxy",
398
+ "apivoid.blacklists.detection_rate",
399
+ "apivoid.blacklists.detections",
400
+ "shodan.asn",
401
+ "shodan.isp",
402
+ "shodan.vulns",
403
+ "shodan.os",
404
+ "shodan.ports",
405
+ ]
406
+ """
407
+
408
+ output_file.parent.mkdir(parents=True, exist_ok=True)
409
+ output_file.write_text(toml_content)
410
+
411
+
412
+ class ConfigurationError(Exception):
413
+ """Configuration-related errors."""
414
+
415
+ pass