11# whois21.__init__.py
22
33import os
4- import re
54import json
65import socket
6+ import string
77from typing import Any , Set , Dict , Tuple , Union , Optional , Sequence
88from datetime import datetime
99
1010import log21
1111import chardet
1212import requests
1313import 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
1518from whois21 .IP import (validate_ip , get_ipv4_services , get_ipv6_services ,
1619 download_ipv4_json , download_ipv6_json ,
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'
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
5550def 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