55
66import re
77import ssl
8+ import yaml
89
910from domaintools .constants import (
1011 Endpoint ,
1112 OutputFormat ,
1213 ENDPOINT_TO_SOURCE_MAP ,
1314 RTTF_PRODUCTS_LIST ,
1415 RTTF_PRODUCTS_CMD_MAPPING ,
16+ SPECS_MAPPING ,
1517)
1618from domaintools ._version import current as version
1719from domaintools .results import (
2224 Results ,
2325 FeedsResults ,
2426)
27+ from domaintools .decorators import api_endpoint , auto_patch_docstrings
2528from domaintools .filters import (
2629 filter_by_riskscore ,
2730 filter_by_expire_date ,
@@ -40,6 +43,7 @@ def delimited(items, character="|"):
4043 return character .join (items ) if type (items ) in (list , tuple , set ) else items
4144
4245
46+ @auto_patch_docstrings
4347class API (object ):
4448 """Enables interacting with the DomainTools API via Python:
4549
@@ -94,8 +98,10 @@ def __init__(
9498 self .key_sign_hash = key_sign_hash
9599 self .default_parameters ["app_name" ] = app_name
96100 self .default_parameters ["app_version" ] = app_version
101+ self .specs = {}
97102
98103 self ._build_api_url (api_url , api_port )
104+ self ._initialize_specs ()
99105
100106 if not https :
101107 raise Exception (
@@ -104,8 +110,25 @@ def __init__(
104110 if proxy_url and not isinstance (proxy_url , str ):
105111 raise Exception ("Proxy URL must be a string. For example: '127.0.0.1:8888'" )
106112
113+ def _initialize_specs (self ):
114+ for spec_name , file_path in SPECS_MAPPING .items ():
115+ try :
116+ with open (file_path , "r" , encoding = "utf-8" ) as f :
117+ spec_content = yaml .safe_load (f )
118+ if not spec_content :
119+ raise ValueError ("Spec file is empty or invalid." )
120+
121+ self .specs [spec_name ] = spec_content
122+
123+ except Exception as e :
124+ print (f"Error loading { file_path } : { e } " )
125+
107126 def _get_ssl_default_context (self , verify_ssl : Union [str , bool ]):
108- return ssl .create_default_context (cafile = verify_ssl ) if isinstance (verify_ssl , str ) else verify_ssl
127+ return (
128+ ssl .create_default_context (cafile = verify_ssl )
129+ if isinstance (verify_ssl , str )
130+ else verify_ssl
131+ )
109132
110133 def _build_api_url (self , api_url = None , api_port = None ):
111134 """Build the API url based on the given url and port. Defaults to `https://api.domaintools.com`"""
@@ -133,11 +156,18 @@ def _rate_limit(self, product):
133156 hours = limit_hours and 3600 / float (limit_hours )
134157 minutes = limit_minutes and 60 / float (limit_minutes )
135158
136- self .limits [product ["id" ]] = {"interval" : timedelta (seconds = minutes or hours or default )}
159+ self .limits [product ["id" ]] = {
160+ "interval" : timedelta (seconds = minutes or hours or default )
161+ }
137162
138163 def _results (self , product , path , cls = Results , ** kwargs ):
139164 """Returns _results for the specified API path with the specified **kwargs parameters"""
140- if product != "account-information" and self .rate_limit and not self .limits_set and not self .limits :
165+ if (
166+ product != "account-information"
167+ and self .rate_limit
168+ and not self .limits_set
169+ and not self .limits
170+ ):
141171 always_sign_api_key_previous_value = self .always_sign_api_key
142172 header_authentication_previous_value = self .header_authentication
143173 self ._rate_limit (product )
@@ -181,7 +211,9 @@ def handle_api_key(self, is_rttf_product, path, parameters):
181211 else :
182212 raise ValueError (
183213 "Invalid value '{0}' for 'key_sign_hash'. "
184- "Values available are {1}" .format (self .key_sign_hash , "," .join (AVAILABLE_KEY_SIGN_HASHES ))
214+ "Values available are {1}" .format (
215+ self .key_sign_hash , "," .join (AVAILABLE_KEY_SIGN_HASHES )
216+ )
185217 )
186218
187219 parameters ["timestamp" ] = datetime .now (timezone .utc ).strftime ("%Y-%m-%dT%H:%M:%SZ" )
@@ -193,7 +225,9 @@ def handle_api_key(self, is_rttf_product, path, parameters):
193225
194226 def account_information (self , ** kwargs ):
195227 """Provides a snapshot of your accounts current API usage"""
196- return self ._results ("account-information" , "/v1/account" , items_path = ("products" ,), ** kwargs )
228+ return self ._results (
229+ "account-information" , "/v1/account" , items_path = ("products" ,), ** kwargs
230+ )
197231
198232 def available_api_calls (self ):
199233 """Provides a list of api calls that you can use based on your account information."""
@@ -396,7 +430,9 @@ def reputation(self, query, include_reasons=False, **kwargs):
396430
397431 def reverse_ip (self , domain = None , limit = None , ** kwargs ):
398432 """Pass in a domain name."""
399- return self ._results ("reverse-ip" , "/v1/{0}/reverse-ip" .format (domain ), limit = limit , ** kwargs )
433+ return self ._results (
434+ "reverse-ip" , "/v1/{0}/reverse-ip" .format (domain ), limit = limit , ** kwargs
435+ )
400436
401437 def host_domains (self , ip = None , limit = None , ** kwargs ):
402438 """Pass in an IP address."""
@@ -570,8 +606,12 @@ def iris_enrich(self, *domains, **kwargs):
570606 younger_than_date = kwargs .pop ("younger_than_date" , {}) or None
571607 older_than_date = kwargs .pop ("older_than_date" , {}) or None
572608 updated_after = kwargs .pop ("updated_after" , {}) or None
573- include_domains_with_missing_field = kwargs .pop ("include_domains_with_missing_field" , {}) or None
574- exclude_domains_with_missing_field = kwargs .pop ("exclude_domains_with_missing_field" , {}) or None
609+ include_domains_with_missing_field = (
610+ kwargs .pop ("include_domains_with_missing_field" , {}) or None
611+ )
612+ exclude_domains_with_missing_field = (
613+ kwargs .pop ("exclude_domains_with_missing_field" , {}) or None
614+ )
575615
576616 filtered_results = DTResultFilter (result_set = results ).by (
577617 [
@@ -624,6 +664,7 @@ def iris_enrich_cli(self, domains=None, **kwargs):
624664 ** kwargs ,
625665 )
626666
667+ @api_endpoint (spec_name = "iris" , path = "/v1/iris-investigate/" , methods = "post" )
627668 def iris_investigate (
628669 self ,
629670 domains = None ,
@@ -641,29 +682,6 @@ def iris_investigate(
641682 ** kwargs ,
642683 ):
643684 """Returns back a list of domains based on the provided filters.
644- The following filters are available beyond what is parameterized as kwargs:
645-
646- - ip: Search for domains having this IP.
647- - email: Search for domains with this email in their data.
648- - email_domain: Search for domains where the email address uses this domain.
649- - nameserver_host: Search for domains with this nameserver.
650- - nameserver_domain: Search for domains with a nameserver that has this domain.
651- - nameserver_ip: Search for domains with a nameserver on this IP.
652- - registrar: Search for domains with this registrar.
653- - registrant: Search for domains with this registrant name.
654- - registrant_org: Search for domains with this registrant organization.
655- - mailserver_host: Search for domains with this mailserver.
656- - mailserver_domain: Search for domains with a mailserver that has this domain.
657- - mailserver_ip: Search for domains with a mailserver on this IP.
658- - redirect_domain: Search for domains which redirect to this domain.
659- - ssl_hash: Search for domains which have an SSL certificate with this hash.
660- - ssl_subject: Search for domains which have an SSL certificate with this subject string.
661- - ssl_email: Search for domains which have an SSL certificate with this email in it.
662- - ssl_org: Search for domains which have an SSL certificate with this organization in it.
663- - google_analytics: Search for domains which have this Google Analytics code.
664- - adsense: Search for domains which have this AdSense code.
665- - tld: Filter by TLD. Must be combined with another parameter.
666- - search_hash: Use search hash from Iris to bring back domains.
667685
668686 You can loop over results of your investigation as if it was a native Python list:
669687
0 commit comments