Skip to content

Commit 202c7b4

Browse files
fix: v1.4.2
+ Added new attributes to `WHOIS` class: `emails`, `phone_numbers` and `fax_numbers` + Added a new property that contains expiration date(`expiration_date`) to `WHOIS` class + Improved WHOIS data parsing (For both human eyes and computer results)
1 parent 72387f9 commit 202c7b4

4 files changed

Lines changed: 95 additions & 63 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ Help this project by [Donation](DONATE.md)
66
Changes log
77
-----------
88

9+
### 1.4.2
10+
11+
+ Added new attributes to `WHOIS` class: `emails`, `phone_numbers` and `fax_numbers`
12+
+ Added a new property that contains expiration date(`expiration_date`) to `WHOIS` class
13+
+ Improved WHOIS data parsing (For both human eyes and computer results)
14+
915
### 1.4.1
1016

1117
+ Made whois run after initializing the `WHOIS` object optional.

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,11 @@ python -m build .
8484
Changes
8585
-------
8686

87-
### 1.4.1
87+
### 1.4.2
8888

89-
+ Made whois run after initializing the `WHOIS` object optional.
90-
+ Tried to decrease the complexity of the code.
89+
+ Added new attributes to `WHOIS` class: `emails`, `phone_numbers` and `fax_numbers`
90+
+ Added a new property that contains expiration date(`expiration_date`) to `WHOIS` class
91+
+ Improved WHOIS data parsing (For both human eyes and computer results)
9192

9293
Usage Examples:
9394
---------------

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ dependencies = [
2424
"requests>=2.28.2",
2525
"importlib_resources>=5.10.2"
2626
]
27-
version = "1.4.1"
27+
version = "1.4.2"
2828

2929
[tool.setuptools]
3030
packages = ["whois21"]

whois21/__init__.py

Lines changed: 84 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
# whois21.__init__.py
22

33
import os
4-
import re
54
import json
65
import socket
6+
import string
77
from typing import Any, Set, Dict, Tuple, Union, Optional, Sequence
88
from datetime import datetime
99

1010
import log21
1111
import chardet
1212
import requests
1313
import importlib_resources
14+
from log21.Colors import (RED, BLUE, GREEN, RESET, LIGHT_RED as LRED,
15+
LIGHT_BLUE as LBLUE, LIGHT_CYAN as LCYAN,
16+
LIGHT_GREEN as LGREEN)
1417

1518
from whois21.IP import (validate_ip, get_ipv4_services, get_ipv6_services,
1619
download_ipv4_json, download_ipv6_json,
@@ -23,7 +26,7 @@
2326
domain_registration_data_lookup,
2427
domain_registration_data_lookup_)
2528

26-
__version__ = '1.4.1'
29+
__version__ = '1.4.2'
2730
__github__ = 'https://github.com/MPCodeWriter21/whois21'
2831
__author__ = 'CodeWriter21'
2932
__email__ = 'CodeWriter21@gmail.com'
@@ -41,15 +44,7 @@
4144
'whois_servers', 'vcard_map', 'lookup_ip_ip_api', 'batch_lookup_ip_ip_api'
4245
]
4346

44-
LRED = log21.get_color('Light Red')
45-
LGREEN = log21.get_color('Light Green')
46-
LBLUE = log21.get_color('Light Blue')
47-
LCYAN = log21.get_color('Light Cyan')
48-
RED = log21.get_color('Red')
49-
GREEN = log21.get_color('Green')
50-
BLUE = log21.get_color('Blue')
51-
CYAN = log21.get_color('Cyan')
52-
RESET = log21.get_color('Reset')
47+
STRIP_CHARS = string.whitespace + '<>'
5348

5449

5550
def download_whois_servers(
@@ -211,13 +206,16 @@ def __init__(
211206
self.registry_domain_id = None
212207
self.registrar_whois_server = None
213208
self.registrar_url = None
214-
self.updated_date = None
215-
self.creation_date = None
216-
self.expires_date = None
217-
self.registrar_name = None
209+
self.updated_date: Optional[datetime] = None
210+
self.creation_date: Optional[datetime] = None
211+
self.expires_date: Optional[datetime] = None
212+
self.registrar_name: Union[str, set] = ''
218213
self.registrar_iana_id = None
219214
self.registrar_abuse_contact_email = None
220215
self.registrar_abuse_contact_phone = None
216+
self.emails: set = set()
217+
self.phone_numbers: set = set()
218+
self.fax_numbers: set = set()
221219
self.status = None
222220
self.name_servers = None
223221
self.__domain = domain.lower() if domain else self.__domain
@@ -288,49 +286,74 @@ def whois(
288286
self.__set_attrs()
289287

290288
def __set_attrs(self):
289+
data = self.__whois_data
290+
291291
# Save the whois information in object attributes.
292-
self.registry_domain_id = self.__whois_data.get('REGISTRY DOMAIN ID', '')
293-
self.registrar_whois_server = self.__whois_data.get(
294-
'REGISTRAR WHOIS SERVER', ''
295-
)
296-
self.registrar_url = self.__whois_data.get('REGISTRAR URL', '')
297-
self.updated_date = self.__whois_data.get('UPDATED DATE', '')
298-
self.creation_date = self.__whois_data.get('CREATION DATE', '')
299-
self.expires_date = (
300-
self.__whois_data.get('REGISTRY EXPIRY DATE', '')
301-
or self.__whois_data.get('REGISTRAR REGISTRATION EXPIRATION DATE', '')
302-
)
303-
self.registrar_name = self.__whois_data.get('REGISTRAR', '')
304-
self.registrar_iana_id = self.__whois_data.get('REGISTRAR IANA ID', '')
305-
self.registrar_abuse_contact_email = self.__whois_data.get(
292+
self.registry_domain_id = data.get('REGISTRY DOMAIN ID', '')
293+
self.registrar_whois_server = data.get('REGISTRAR WHOIS SERVER', '')
294+
self.registrar_url = data.get('REGISTRAR URL', '')
295+
self.registrar_name = data.get('REGISTRAR', '')
296+
if isinstance(self.registrar_name, list):
297+
self.registrar_name = set(self.registrar_name)
298+
self.registrar_iana_id = data.get('REGISTRAR IANA ID', '')
299+
self.registrar_abuse_contact_email = data.get(
306300
'REGISTRAR ABUSE CONTACT EMAIL', ''
307301
)
308-
self.registrar_abuse_contact_phone = self.__whois_data.get(
302+
self.registrar_abuse_contact_phone = data.get(
309303
'REGISTRAR ABUSE CONTACT PHONE', ''
310304
)
311-
self.status = self.__whois_data.get('DOMAIN STATUS', [])
312-
self.name_servers = self.__whois_data.get('NAME SERVER', [])
305+
mails = []
306+
emails = (
307+
([mails] if isinstance(mails := data.get('EMAIL', []), str) else mails) +
308+
([mails] if isinstance(mails := data.get('E-MAIL', []), str) else mails)
309+
)
310+
if isinstance(emails, list):
311+
self.emails = set(emails)
312+
phone_numbers = data.get('PHONE', [])
313+
if isinstance(phone_numbers, str):
314+
self.phone_numbers = {phone_numbers}
315+
if isinstance(phone_numbers, list):
316+
self.phone_numbers = set(phone_numbers)
317+
fax = []
318+
fax_numbers = (
319+
([fax] if isinstance(fax := data.get('FAX', []), str) else fax) +
320+
([fax] if isinstance(fax := data.get('FAX-NO', []), str) else fax)
321+
)
322+
if isinstance(fax_numbers, list):
323+
self.fax_numbers = set(fax_numbers)
324+
self.status = data.get('DOMAIN STATUS', [])
325+
self.name_servers = data.get('NAME SERVER', []) + data.get('NSERVER', [])
313326

314327
def parse_time(date_time: str) -> Union[datetime, None]:
315328
"""Parses a date time string.
316329
317330
:param date_time: The date time string.
318331
:return: The parsed date time.
319332
"""
320-
result = re.findall(
321-
r'(\d{4}\-\d{2}\-\d{2}).(\d{2}:\d{2}:\d{2})*', date_time
322-
)
323-
if result:
324-
return datetime.strptime(' '.join(result[0]), '%Y-%m-%d %H:%M:%S')
325-
return None
333+
try:
334+
return datetime.fromisoformat(date_time)
335+
except ValueError:
336+
return None
326337

327338
# Convert the dates to datetime objects.
328-
if self.updated_date:
329-
self.updated_date = parse_time(self.updated_date)
330-
if self.creation_date:
331-
self.creation_date = parse_time(self.creation_date)
332-
if self.expires_date:
333-
self.expires_date = parse_time(self.expires_date)
339+
updated_date = (
340+
data.get('UPDATED DATE', '') or data.get('UPDATED', '')
341+
or data.get('LAST UPDATED', '')
342+
)
343+
creation_date = (
344+
data.get('CREATION DATE', '') or data.get('CREATED DATE', '')
345+
or data.get('CREATED', '')
346+
)
347+
expires_date = (
348+
data.get('REGISTRY EXPIRY DATE', '') or data.get('EXPIRY DATE', '')
349+
or data.get('REGISTRAR REGISTRATION EXPIRATION DATE', '')
350+
)
351+
if updated_date:
352+
self.updated_date = parse_time(updated_date)
353+
if creation_date:
354+
self.creation_date = parse_time(creation_date)
355+
if expires_date:
356+
self.expires_date = parse_time(expires_date)
334357

335358
def __whois(self):
336359
if not self.__servers:
@@ -530,7 +553,7 @@ def __parse_whois_data(self):
530553
:return: True if the data was parsed successfully, False otherwise.
531554
"""
532555
log21.debug('Parsing data...')
533-
self.__whois_data = {}
556+
data = self.__whois_data = {}
534557
i = 0
535558
lines = self.__raw.decode(
536559
self.__get_decode_encoding(self.__raw), errors=self.__encoding_errors
@@ -541,35 +564,32 @@ def __parse_whois_data(self):
541564
i += 1
542565
continue
543566
if ':' in line:
544-
key, value = line.split(':', 1)
567+
key_name, value = line.split(':', 1)
545568
if not value:
546569
value = ''
547570
for j in range(i + 1, len(lines)):
548571
if lines[j].startswith('%') or lines[j].startswith('#'):
549572
continue
550573
if ':' in lines[j]:
551574
break
552-
value += lines[j].strip() + '\n'
575+
value += lines[j].strip(STRIP_CHARS) + '\n'
553576
i = j
554-
if key.strip().upper() not in self.__whois_data:
555-
self.__whois_data[key.strip().upper()] = value.strip()
577+
if (key := key_name.strip(STRIP_CHARS).upper()) not in data:
578+
data[key] = value.strip(STRIP_CHARS)
556579
else:
557-
if isinstance(self.__whois_data[key.strip().upper()], list):
558-
self.__whois_data[key.strip().upper()].append(value.strip())
559-
elif isinstance(self.__whois_data[key.strip().upper()], str):
560-
self.__whois_data[key.strip().upper()] = [
561-
self.__whois_data[key.strip().upper()],
562-
value.strip()
563-
]
580+
if isinstance(data[key], list):
581+
data[key].append(value.strip(STRIP_CHARS))
582+
elif isinstance(data[key], str):
583+
data[key] = [data[key], value.strip(STRIP_CHARS)]
564584
i += 1
565585

566-
if not self.__whois_data:
567-
log21.debug(f'{LRED}No data found.{RESET}')
586+
if not data:
587+
log21.debug(f'{LRED}No data found.')
568588
self.__error = ('No whois data found.', None)
569589
return False
570590

571591
# Check if the whois server returned any error messages.
572-
if 'ERROR' in self.__whois_data or 'WHOIS ERROR' in self.__whois_data:
592+
if 'ERROR' in data or 'WHOIS ERROR' in data:
573593
log21.debug(f'{LRED}Error{RESET} found in whois data.')
574594
self.__error = ('Error found in whois data.', None)
575595
return False
@@ -744,6 +764,11 @@ def domain(self) -> Union[str, int]:
744764
"""The domain/ip/asn that was queried."""
745765
return self.__domain
746766

767+
@property
768+
def expiration_date(self) -> Optional[datetime]:
769+
"""The expiration date of the domain (if available)."""
770+
return self.expires_date
771+
747772
@property
748773
def success(self) -> bool:
749774
"""A boolean indicating whether the query was successful."""

0 commit comments

Comments
 (0)