Created
February 12, 2025 11:21
-
-
Save EricsonWillians/77a6e7568c8a72aff22ab44b23ec56fb to your computer and use it in GitHub Desktop.
This script checks the availability of domains for a given company or idea name across multiple TLDs. It uses robust DNS queries (via dnspython) and optionally WHOIS lookups (via python-whois) to determine if a domain is "Taken" or "Available". The results are displayed in a beautiful table using the Rich library, and the command-line interface …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
This script checks the availability of domains for a given company or idea name across multiple TLDs. | |
It uses robust DNS queries (via dnspython) and optionally WHOIS lookups (via python-whois) | |
to determine if a domain is "Taken" or "Available". The results are displayed in a beautiful table | |
using the Rich library, and the command-line interface is handled by Typer. | |
Usage Example: | |
python domain_checker.py mycoolstartup --tlds "com,net,org,io" --whois --timeout 5 | |
""" | |
import concurrent.futures # For concurrent domain checks. | |
import socket # Although imported, it is not used directly in this script. | |
import typer # For building the command-line interface. | |
from rich.console import Console # For rich output to the terminal. | |
from rich.table import Table # For constructing formatted tables. | |
from rich.progress import track # For a progress bar while checking domains. | |
# Import dnspython for performing DNS queries. | |
try: | |
import dns.resolver # dnspython module to perform DNS resolution. | |
except ImportError: | |
# If dnspython is not installed, raise an error with an instruction. | |
raise ImportError("Please install dnspython: pip install dnspython") | |
# Optionally import python-whois for WHOIS lookups. | |
try: | |
import whois # python-whois module to perform WHOIS lookups. | |
WHOIS_AVAILABLE = True | |
except ImportError: | |
# If the module isn't installed, mark WHOIS as unavailable. | |
WHOIS_AVAILABLE = False | |
# Initialize the Typer app for the command-line interface. | |
app = typer.Typer() | |
# Create a Console instance from Rich to print formatted output. | |
console = Console() | |
def perform_dns_check(domain: str, timeout: int = 5) -> dict: | |
""" | |
Perform DNS lookups for a given domain across several record types. | |
Args: | |
domain (str): The domain name to check (e.g., "example.com"). | |
timeout (int): The timeout in seconds for each DNS query. | |
Returns: | |
dict: A dictionary where keys are DNS record types (e.g., "A", "AAAA", "MX", "NS", "CNAME") | |
and values are lists of results or error messages. | |
""" | |
results = {} # Dictionary to store DNS records. | |
# Create a DNS resolver instance and set its timeout. | |
resolver = dns.resolver.Resolver() | |
resolver.timeout = timeout | |
resolver.lifetime = timeout | |
# Define the list of DNS record types to query. | |
record_types = ["A", "AAAA", "MX", "NS", "CNAME"] | |
for rtype in record_types: | |
try: | |
# Attempt to resolve the domain for the current record type. | |
answers = resolver.resolve(domain, rtype) | |
# Convert the response to a list of text strings. | |
results[rtype] = [rdata.to_text() for rdata in answers] | |
except dns.resolver.NoAnswer: | |
# No DNS records of this type were found. | |
results[rtype] = [] | |
except dns.resolver.NXDOMAIN: | |
# The domain does not exist. Set an empty list and break out of the loop. | |
results[rtype] = [] | |
break # Exit early because further queries are unnecessary. | |
except Exception as e: | |
# Catch-all for any other errors that occur during resolution. | |
results[rtype] = [f"Error: {e}"] | |
return results | |
def perform_whois_check(domain: str) -> str: | |
""" | |
Perform a WHOIS lookup for the given domain. | |
Args: | |
domain (str): The domain name to check. | |
Returns: | |
str: Returns 'Taken' if registration info is found, 'Available' if no registration is found, | |
or an error string if an exception occurs. | |
""" | |
try: | |
# Perform the WHOIS lookup. | |
w = whois.whois(domain) | |
# The python-whois library typically populates the domain_name attribute if the domain is registered. | |
if not w.domain_name: | |
return "Available" | |
else: | |
return "Taken" | |
except Exception as e: | |
# If an error occurs during WHOIS lookup, return the error message. | |
return f"Error: {e}" | |
def check_domain(domain: str, do_whois: bool, timeout: int) -> dict: | |
""" | |
Check the status of a domain using DNS queries and, optionally, a WHOIS lookup. | |
Args: | |
domain (str): The full domain name to check (e.g., "example.com"). | |
do_whois (bool): Flag indicating whether to perform a WHOIS lookup. | |
timeout (int): Timeout for DNS queries in seconds. | |
Returns: | |
dict: A dictionary containing: | |
- "domain": The domain name. | |
- "status": "Taken", "Available", or an error message. | |
- "dns": A dictionary of DNS record results. | |
- "whois": The WHOIS lookup result or a status message. | |
""" | |
# Initialize the result dictionary with default values. | |
result = {"domain": domain, "status": "Unknown", "dns": None, "whois": None} | |
# Perform DNS lookups on the domain. | |
dns_results = perform_dns_check(domain, timeout) | |
result["dns"] = dns_results | |
# Heuristically determine if the domain is taken based on key DNS record types. | |
# We check A, AAAA, and NS records to decide if the domain is in use. | |
dns_taken = any( | |
dns_results.get(rtype) and any(not rec.startswith("Error:") for rec in dns_results.get(rtype)) | |
for rtype in ["A", "AAAA", "NS"] | |
) | |
# Set initial status based on DNS results. | |
result["status"] = "Taken" if dns_taken else "Unknown" | |
# If WHOIS lookup is requested and the module is available, perform the WHOIS check. | |
if do_whois and WHOIS_AVAILABLE: | |
whois_status = perform_whois_check(domain) | |
result["whois"] = whois_status | |
# Adjust the final status based on the WHOIS lookup. | |
if whois_status == "Taken": | |
result["status"] = "Taken" | |
elif whois_status == "Available": | |
result["status"] = "Available" | |
else: | |
# In case WHOIS returns an error message, store that message. | |
result["whois"] = whois_status | |
else: | |
# If WHOIS is requested but not available, or if it is disabled, mark accordingly. | |
result["whois"] = "Not checked" if do_whois else "Disabled" | |
# If DNS was inconclusive (status remains "Unknown"), assume the domain is available. | |
if result["status"] == "Unknown": | |
result["status"] = "Available" | |
return result | |
@app.command() | |
def main( | |
name: str = typer.Argument(..., help="Your company or idea name (without TLD)"), | |
tlds: str = typer.Option( | |
"com,net,org,io,co,biz,info,us,tech,ai,dev,app", | |
help="Comma-separated list of TLDs to check." | |
), | |
do_whois: bool = typer.Option( | |
False, "--whois", help="Perform WHOIS lookup for comprehensive checks (requires python-whois)." | |
), | |
timeout: int = typer.Option(5, help="Timeout for DNS queries in seconds.") | |
): | |
""" | |
Main entry point for the domain checker. | |
This function: | |
- Processes the command-line arguments. | |
- Generates a list of domain names from the provided base name and TLDs. | |
- Concurrently checks each domain for DNS records and, optionally, WHOIS data. | |
- Displays the results in a rich table. | |
Args: | |
name (str): The base name for the company or idea (e.g., "mycoolstartup"). | |
tlds (str): Comma-separated list of TLDs to check (e.g., "com,net,org,io"). | |
do_whois (bool): Flag to enable WHOIS lookups for additional verification. | |
timeout (int): Timeout in seconds for each DNS query. | |
""" | |
# Split and clean the list of TLDs provided as a comma-separated string. | |
tld_list = [tld.strip() for tld in tlds.split(",") if tld.strip()] | |
# Generate the full domain names by appending each TLD to the provided base name. | |
domains = [f"{name}.{tld}" for tld in tld_list] | |
# Create a Rich table to display the output with headers and styling. | |
table = Table(title=f"Domain Availability for '{name}'", show_lines=True) | |
table.add_column("Domain", style="cyan", no_wrap=True) | |
table.add_column("Status", style="bold") | |
table.add_column("DNS Records", style="green") | |
table.add_column("WHOIS", style="magenta") | |
results = [] # List to store the results for each domain. | |
# Use a ThreadPoolExecutor to perform domain checks concurrently for better performance. | |
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: | |
# Submit a domain check task for each domain. | |
future_to_domain = { | |
executor.submit(check_domain, domain, do_whois, timeout): domain | |
for domain in domains | |
} | |
# Use Rich's progress tracking to visually monitor the progress of domain checks. | |
for future in track( | |
concurrent.futures.as_completed(future_to_domain), | |
total=len(domains), | |
description="Checking domains..." | |
): | |
domain = future_to_domain[future] | |
try: | |
# Retrieve the result from the future. | |
res = future.result() | |
results.append(res) | |
except Exception as exc: | |
# If an error occurs, record the error in the results. | |
results.append({ | |
"domain": domain, | |
"status": f"Error: {exc}", | |
"dns": {}, | |
"whois": "Error" | |
}) | |
# Populate the Rich table with the results from each domain check. | |
for res in results: | |
domain = res["domain"] | |
status = res["status"] | |
# Format DNS records into a multi-line string for display. | |
dns_records = "" | |
for rtype, values in res["dns"].items(): | |
if values: | |
dns_records += f"{rtype}: " + ", ".join(values) + "\n" | |
# If no DNS records are found, display a dash. | |
dns_records = dns_records.strip() if dns_records else "-" | |
# Determine the WHOIS status text, or use a dash if no information is present. | |
whois_status = res["whois"] if res["whois"] else "-" | |
# Colorize the status output for better visibility. | |
if status == "Taken": | |
status_display = "[red]Taken[/red]" | |
elif status == "Available": | |
status_display = "[green]Available[/green]" | |
else: | |
status_display = f"[yellow]{status}[/yellow]" | |
# Add a row to the table for this domain. | |
table.add_row(domain, status_display, dns_records, whois_status) | |
# Finally, print the table to the console. | |
console.print(table) | |
if __name__ == "__main__": | |
# Run the Typer application. This starts the CLI. | |
app() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment