Skip to content

Instantly share code, notes, and snippets.

@EricsonWillians
Created February 12, 2025 11:21
Show Gist options
  • Save EricsonWillians/77a6e7568c8a72aff22ab44b23ec56fb to your computer and use it in GitHub Desktop.
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 …
#!/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