An asynchronous domain checker with WHOIS, RDAP, and DIG support, featuring a beautiful CLI interface and MCP server connectivity.
- Asynchronous Processing: Fast, non-blocking domain lookups
- Triple Protocol Support: WHOIS, RDAP, and DIG lookups
- DNS Record Types: Support for A, AAAA, MX, NS, SOA, TXT, and ANY records
- DNS Propagation Checker: Check DNS resolution across 20 regional ISP resolvers
- Reverse DNS: IP address to hostname lookups
- Bulk Processing: Check multiple domains with rate limiting
- Multiple Output Formats: Rich (default), Plain, JSON, and CSV formats
- Copy/Paste Friendly: Plain text output without formatting or colors
- Beautiful CLI: Rich, colorful terminal interface with tables and panels
- MCP Server: Connect via Model Context Protocol
- Configurable: Extensive configuration options
- Error Handling: Robust error handling and validation
# Clone the repository
git clone https://github.com/TheZacillac/domain-checker.git
cd domain-checker
# Install with pipx (recommended)
pipx install -e .
# Alternative: Install with pip
pip install -e .- Python 3.8+ (required)
# Install with pipx for isolated environment
pipx install -e .
# Or install directly from GitHub
pipx install git+https://github.com/TheZacillac/domain-checker.git# Install dependencies
pip install -r requirements.txt
# Install package
pip install -e .# Clone and install in development mode
git clone https://github.com/TheZacillac/domain-checker.git
cd domain-checker
pipx install -e .
# Install development dependencies (optional)
pip install pytest black flake8 mypyAfter installation, you may need to update your PATH:
# Add pipx to your PATH (if not already there)
pipx ensurepath
# Restart your terminal or source your shell config
source ~/.bashrc # or ~/.zshrcAfter installation, verify everything works:
# Test basic functionality
domch lookup example.com
# Test bulk lookup
domch bulk example.com google.com
# Test interactive mode
domch interactive# Lookup a single domain
domch lookup example.com
# Lookup with specific method
domch lookup example.com --method rdap
# DIG lookup with specific record type
domch dig example.com --record A
domch dig example.com --record MX
domch dig example.com --record NS
# Reverse DNS lookup
domch reverse 8.8.8.8
# Check DNS propagation across regional ISPs
domch prop example.com --record A
# Bulk lookup with DIG
domch bulk example.com google.com --method dig --dig-record A
# Lookup from file
domch file domains.txt
# Compare WHOIS vs RDAP
domch compare example.com
# Interactive mode
domch interactive
# Output Formats - Get copy/paste friendly output
domch lookup example.com --format plain # Clean text output
domch lookup example.com --format json # JSON output
domch bulk example.com google.com --format csv # CSV output for Excel
domch prop example.com --format json # JSON for propagation checkimport asyncio
from domain_checker import DomainChecker
async def main():
checker = DomainChecker()
# Single domain lookup
result = await checker.lookup_domain("example.com")
print(f"Domain: {result.domain}")
print(f"Registrar: {result.data.registrar}")
print(f"Expires: {result.data.expiration_date}")
# Bulk lookup
domains = ["example.com", "google.com", "github.com"]
results = await checker.lookup_domains_bulk(domains)
print(f"Successfully looked up {results.successful_lookups} domains")
asyncio.run(main())# Start MCP server
python -m domain_checker.mcp_serverCreate a configuration file at ~/.config/domain-checker/config.json:
{
"timeout": 30,
"max_concurrent": 10,
"rate_limit": 1.0,
"default_method": "auto",
"prefer_rdap": true,
"show_raw_data": false,
"enable_cache": false,
"log_level": "INFO"
}Or use environment variables:
export DOMAIN_CHECKER_TIMEOUT=30
export DOMAIN_CHECKER_MAX_CONCURRENT=10
export DOMAIN_CHECKER_RATE_LIMIT=1.0
export DOMAIN_CHECKER_DEFAULT_METHOD=auto
export DOMAIN_CHECKER_PREFER_RDAP=trueLookup a single domain with detailed information display.
domch lookup example.com [--method whois|rdap|dig|auto] [--timeout 30] [--raw] [--dig-record A|AAAA|MX|NS|SOA|TXT|ANY] [--format rich|plain|json]Output Formats:
rich(default) - Beautiful, colorful terminal output with tables and panelsplain- Clean, copy/paste friendly text output without formattingjson- Structured JSON output for programmatic use
Lookup multiple domains with progress tracking.
domch bulk domain1.com domain2.com [--method auto] [--concurrent 10] [--rate-limit 1.0] [--dig-record A|AAAA|MX|NS|SOA|TXT|ANY] [--format rich|plain|json|csv]Output Formats:
rich(default) - Beautiful, colorful terminal output with tablesplain- Clean, copy/paste friendly text outputjson- Structured JSON output for programmatic usecsv- Comma-separated values for Excel/spreadsheet import
Lookup domains from a text file (one domain per line).
domch file domains.txt [--method auto] [--concurrent 10] [--rate-limit 1.0] [--dig-record A|AAAA|MX|NS|SOA|TXT|ANY] [--format rich|plain|json|csv]Output Formats:
rich(default) - Beautiful, colorful terminal output with tablesplain- Clean, copy/paste friendly text outputjson- Structured JSON output for programmatic usecsv- Comma-separated values for Excel/spreadsheet import
Perform DIG lookup for a domain with specific DNS record type.
domch dig example.com [--record A|AAAA|MX|NS|SOA|TXT|ANY] [--timeout 30] [--format rich|plain|json]Output Formats:
rich(default) - Beautiful, colorful terminal outputplain- Clean, copy/paste friendly text outputjson- Structured JSON output
Perform reverse DNS lookup for an IP address.
domch reverse 8.8.8.8 [--timeout 30] [--format rich|plain|json]Output Formats:
rich(default) - Beautiful, colorful terminal outputplain- Clean, copy/paste friendly text outputjson- Structured JSON output
Compare WHOIS and RDAP results for a domain.
domch compare example.com [--timeout 30]Start interactive mode for repeated lookups.
domch interactiveLaunch the user-friendly graphical interface.
domch guiFeatures:
- Easy-to-use form-based interface
- Tabbed navigation (Domain Lookup, Bulk Check, Settings, Help, About)
- Real-time results display
- Built-in help and documentation
- No command-line knowledge required
Show version information and credits.
domch aboutExample Output:
ββ About Domain Checker ββ
β Domain Checker v1.1.0 β
β β
β Created by: Zac Roach β
β β
β Description: Asynchronous domain checker with WHOIS, RDAP, and DIG support β
β β
β Features: β
β β’ Fast asynchronous domain lookups β
β β’ WHOIS, RDAP, and DIG protocol support β
β β’ DNS propagation checking β
β β’ Bulk domain processing β
β β’ Beautiful CLI interface β
β β’ MCP server integration β
β β
β Repository: https://github.com/TheZacillac/domain-checker β
β License: MIT β
ββββββββββββββββββββββββββ
Domain Checker supports multiple output formats to suit different needs. Use the --format (or -f) flag to specify your preferred format.
Beautiful, colorful terminal output with tables, panels, and emojis. Best for interactive use.
domch lookup example.com --format rich
# or simply
domch lookup example.comClean, copy/paste friendly text output without any formatting, colors, or emojis. Perfect for:
- Copying data to other applications
- Parsing output in scripts
- Viewing in text editors
- Terminal environments without color support
domch lookup example.com --format plainExample Output:
============================================================
DOMAIN INFORMATION
============================================================
Domain: example.com
Method: RDAP
Lookup Time: 0.45s
Registrar: IANA
Status: active
NAME SERVERS:
a.iana-servers.net
b.iana-servers.net
IMPORTANT DATES:
Creation: 1995-08-14 04:00:00
Expiration: 2024-08-13 04:00:00
Last Updated: 2023-08-14 07:01:38
============================================================
Structured JSON output for programmatic use and integration with other tools.
domch lookup example.com --format jsonExample Output:
{
"domain": "example.com",
"success": true,
"method": "rdap",
"lookup_time": 0.45,
"registration_status": "registered",
"error": null,
"data": {
"domain": "example.com",
"registrar": "IANA",
"creation_date": "1995-08-14T04:00:00",
"expiration_date": "2024-08-13T04:00:00",
"updated_date": "2023-08-14T07:01:38",
"status": ["active"],
"name_servers": ["a.iana-servers.net", "b.iana-servers.net"],
"source": "rdap"
}
}Comma-separated values format, perfect for importing into Excel or other spreadsheet applications.
domch bulk example.com google.com github.com --format csv
# or save to file
domch file domains.txt --format csv > results.csvExample Output:
Domain,Registration Status,Method,Lookup Time (s),Registrar,Creation Date,Expiration Date,Status
example.com,registered,RDAP,0.45,IANA,1995-08-14,2024-08-13,active
google.com,registered,RDAP,0.52,MarkMonitor Inc.,1997-09-15,2028-09-14,clientTransferProhibited
github.com,registered,RDAP,0.48,MarkMonitor Inc.,2007-10-09,2024-10-09,clientTransferProhibited
| Command | Rich | Plain | JSON | CSV |
|---|---|---|---|---|
lookup |
β | β | β | β |
bulk |
β | β | β | β |
file |
β | β | β | β |
dig |
β | β | β | β |
reverse |
β | β | β | β |
prop |
β | β | β | β |
compare |
β | β | β | β |
interactive |
β | β | β | β |
For Copy/Paste:
# Get clean name servers list
domch lookup example.com --format plain | grep -A 10 "NAME SERVERS"
# Get just the IP addresses from propagation check
domch prop example.com --format plain | grep -A 100 "RESOLVED IP"For Scripts:
# Parse JSON with jq
domch lookup example.com --format json | jq '.data.registrar'
# Check if domain is registered
domch lookup example.com --format json | jq -r '.registration_status'For Spreadsheets:
# Export bulk results to Excel
domch file domains.txt --format csv > domains.csv
# Bulk check with CSV output
domch bulk domain1.com domain2.com domain3.com --format csv > results.csvMain class for domain lookups.
checker = DomainChecker(
timeout=30, # Timeout in seconds
max_concurrent=10, # Max concurrent lookups
rate_limit=1.0 # Rate limit (requests/second)
)lookup_domain(domain, method="auto")- Lookup single domainlookup_domains_bulk(domains, method="auto")- Bulk lookuplookup_domains_from_file(file_path, method="auto")- Lookup from filecompare_methods(domain)- Compare WHOIS vs RDAP
Contains domain information from WHOIS or RDAP.
class DomainInfo:
domain: str
registrar: Optional[str]
creation_date: Optional[datetime]
expiration_date: Optional[datetime]
updated_date: Optional[datetime]
status: List[str]
name_servers: List[str]
registrant: Optional[Dict]
admin_contact: Optional[Dict]
tech_contact: Optional[Dict]
source: str # "whois" or "rdap"Result of a domain lookup operation.
class LookupResult:
domain: str
success: bool
data: Optional[DomainInfo]
error: Optional[str]
lookup_time: float
method: strThe MCP server provides programmatic access to domain checking functionality.
lookup_domain- Lookup single domainlookup_domains_bulk- Bulk domain lookupcompare_methods- Compare WHOIS vs RDAPlookup_domains_from_file- Lookup from file
import asyncio
from mcp.client import Client
async def main():
client = Client("domain-checker")
await client.connect()
# Lookup a domain
result = await client.call_tool("lookup_domain", {
"domain": "example.com",
"method": "auto"
})
print(result)
asyncio.run(main())import asyncio
from domain_checker import DomainChecker
async def single_domain_lookup():
"""Example: Lookup a single domain"""
checker = DomainChecker()
result = await checker.lookup_domain("example.com")
if result.success:
print(f"β
Domain: {result.data.domain}")
print(f"π Registrar: {result.data.registrar}")
print(f"π
Creation: {result.data.creation_date}")
print(f"β° Expiration: {result.data.expiration_date}")
print(f"π Last Updated: {result.data.updated_date}")
print(f"π Status: {', '.join(result.data.status)}")
print(f"π Name Servers: {', '.join(result.data.name_servers)}")
print(f"β±οΈ Lookup Time: {result.lookup_time:.2f}s")
print(f"π§ Method: {result.method}")
else:
print(f"β Error: {result.error}")
asyncio.run(single_domain_lookup())import asyncio
from domain_checker import DomainChecker
async def bulk_domain_lookup():
"""Example: Bulk domain lookup"""
checker = DomainChecker(max_concurrent=5, rate_limit=2.0)
domains = [
"example.com",
"google.com",
"github.com",
"stackoverflow.com",
"reddit.com"
]
results = await checker.lookup_domains_bulk(domains)
print(f"π Summary:")
print(f" Total: {results.total_domains}")
print(f" Successful: {results.successful_lookups}")
print(f" Failed: {results.failed_lookups}")
print(f" Total Time: {results.total_time:.2f}s")
print(f" Average Time: {results.average_time_per_domain:.2f}s")
print(f"\nπ Results:")
for result in results.results:
if result.success:
print(f" β
{result.domain}: {result.data.registrar} ({result.method})")
else:
print(f" β {result.domain}: {result.error}")
asyncio.run(bulk_domain_lookup())import asyncio
from domain_checker import DomainChecker
async def method_comparison():
"""Example: Compare WHOIS vs RDAP"""
checker = DomainChecker()
comparison = await checker.compare_methods("example.com")
print(f"π Comparing methods for: {comparison['domain']}")
# WHOIS results
whois_result = comparison['whois']
print(f"\nπ WHOIS:")
print(f" Success: {'β
' if whois_result.success else 'β'}")
print(f" Time: {whois_result.lookup_time:.2f}s")
if whois_result.success and whois_result.data:
print(f" Registrar: {whois_result.data.registrar}")
# RDAP results
rdap_result = comparison['rdap']
print(f"\nπ RDAP:")
print(f" Success: {'β
' if rdap_result.success else 'β'}")
print(f" Time: {rdap_result.lookup_time:.2f}s")
if rdap_result.success and rdap_result.data:
print(f" Registrar: {rdap_result.data.registrar}")
asyncio.run(method_comparison())import asyncio
import json
from domain_checker import DomainChecker
async def file_processing():
"""Example: Process domains from file"""
# Create a sample domains file
sample_domains = [
"example.com",
"google.com",
"github.com",
"stackoverflow.com",
"reddit.com",
"youtube.com",
"facebook.com",
"twitter.com"
]
with open("sample_domains.txt", "w") as f:
for domain in sample_domains:
f.write(f"{domain}\n")
print("π Created sample_domains.txt")
# Process the file
checker = DomainChecker(max_concurrent=3, rate_limit=1.5)
results = await checker.lookup_domains_from_file("sample_domains.txt")
print(f"π File Processing Results:")
print(f" Total: {results.total_domains}")
print(f" Successful: {results.successful_lookups}")
print(f" Failed: {results.failed_lookups}")
print(f" Total Time: {results.total_time:.2f}s")
# Save results to JSON
results_data = {
"summary": {
"total_domains": results.total_domains,
"successful_lookups": results.successful_lookups,
"failed_lookups": results.failed_lookups,
"total_time": results.total_time,
"average_time_per_domain": results.average_time_per_domain
},
"results": [
{
"domain": r.domain,
"success": r.success,
"method": r.method,
"lookup_time": r.lookup_time,
"error": r.error,
"data": {
"registrar": r.data.registrar if r.data else None,
"creation_date": r.data.creation_date.isoformat() if r.data and r.data.creation_date else None,
"expiration_date": r.data.expiration_date.isoformat() if r.data and r.data.expiration_date else None,
"status": r.data.status if r.data else [],
"name_servers": r.data.name_servers if r.data else [],
"source": r.data.source if r.data else None
} if r.data else None
}
for r in results.results
]
}
with open("results.json", "w") as f:
json.dump(results_data, f, indent=2, default=str)
print("πΎ Saved results to results.json")
asyncio.run(file_processing())import asyncio
from domain_checker import DomainChecker
async def dig_examples():
"""Examples of DIG functionality"""
checker = DomainChecker()
# Example 1: Basic DIG lookup
print("=== Example 1: Basic DIG Lookup ===")
result = await checker.dig_lookup("example.com", "A")
print(f"β
A Records for example.com:")
print(f" Raw Data: {result.data.raw_data}")
print(f" Lookup Time: {result.lookup_time:.2f}s")
# Example 2: Different record types
print("\n=== Example 2: Different Record Types ===")
record_types = ["A", "AAAA", "MX", "NS", "TXT", "SOA"]
for record_type in record_types:
result = await checker.dig_lookup("example.com", record_type)
if result.success and result.data.raw_data:
print(f"β
{record_type} Records:")
print(f" {result.data.raw_data.strip()}")
else:
print(f"β {record_type} Records: No data or error")
# Example 3: Reverse DNS lookup
print("\n=== Example 3: Reverse DNS Lookup ===")
ips = ["8.8.8.8", "1.1.1.1", "208.67.222.222"]
for ip in ips:
result = await checker.reverse_lookup(ip)
if result.success and result.data:
print(f"β
{ip} -> {result.data.domain}")
else:
print(f"β {ip} -> Error: {result.error}")
asyncio.run(dig_examples())import asyncio
import json
import time
from typing import List
from domain_checker import DomainChecker
from domain_checker.exceptions import ValidationError, TimeoutError
from domain_checker.utils import validate_domains, create_summary_stats
async def custom_configuration():
"""Example: Custom configuration"""
# Create checker with custom settings
checker = DomainChecker(
timeout=60, # Longer timeout
max_concurrent=20, # More concurrent requests
rate_limit=0.5 # Slower rate limit
)
print(f"π§ Configuration:")
print(f" Timeout: 60s")
print(f" Max Concurrent: 20")
print(f" Rate Limit: 0.5 req/s")
# Test with a domain
result = await checker.lookup_domain("example.com")
print(f"β
Lookup completed in {result.lookup_time:.2f}s")
async def performance_benchmark():
"""Example: Performance benchmarking"""
domains = [
"example.com", "google.com", "github.com", "stackoverflow.com",
"reddit.com", "youtube.com", "facebook.com", "twitter.com",
"linkedin.com", "instagram.com", "amazon.com", "microsoft.com"
]
# Test different configurations
configurations = [
{"max_concurrent": 1, "rate_limit": 0.5, "name": "Conservative"},
{"max_concurrent": 5, "rate_limit": 1.0, "name": "Balanced"},
{"max_concurrent": 10, "rate_limit": 2.0, "name": "Aggressive"},
]
results = {}
for config in configurations:
print(f"\nπ§ Testing {config['name']} configuration...")
checker = DomainChecker(
max_concurrent=config['max_concurrent'],
rate_limit=config['rate_limit']
)
start_time = time.time()
bulk_results = await checker.lookup_domains_bulk(domains)
end_time = time.time()
results[config['name']] = {
'total_time': end_time - start_time,
'successful': bulk_results.successful_lookups,
'failed': bulk_results.failed_lookups,
'average_time': bulk_results.average_time_per_domain
}
print(f" β±οΈ Total Time: {results[config['name']]['total_time']:.2f}s")
print(f" β
Successful: {results[config['name']]['successful']}")
print(f" β Failed: {results[config['name']]['failed']}")
print(f" π Average per Domain: {results[config['name']]['average_time']:.2f}s")
# Find best configuration
best_config = min(results.items(), key=lambda x: x[1]['total_time'])
print(f"\nπ Best Configuration: {best_config[0]}")
print(f" Time: {best_config[1]['total_time']:.2f}s")
# Run examples
asyncio.run(custom_configuration())
asyncio.run(performance_benchmark())import asyncio
import json
from mcp.client import Client
async def mcp_client_example():
"""Example: Using domain checker via MCP"""
# Connect to MCP server
client = Client("domain-checker")
try:
await client.connect()
print("β
Connected to MCP server")
# List available tools
tools = await client.list_tools()
print(f"π Available tools: {[tool.name for tool in tools]}")
# Lookup a single domain
print("\nπ Looking up example.com...")
result = await client.call_tool("lookup_domain", {
"domain": "example.com",
"method": "auto",
"timeout": 30
})
if result and result.content:
data = json.loads(result.content[0].text)
print(f"β
Domain: {data['domain']}")
print(f"π Success: {data['success']}")
print(f"β±οΈ Time: {data['lookup_time']:.2f}s")
print(f"π§ Method: {data['method']}")
if data['data']:
print(f"π’ Registrar: {data['data']['registrar']}")
print(f"π
Creation: {data['data']['creation_date']}")
print(f"β° Expiration: {data['data']['expiration_date']}")
# Bulk lookup
print("\nπ Bulk lookup...")
bulk_result = await client.call_tool("lookup_domains_bulk", {
"domains": ["google.com", "github.com", "stackoverflow.com"],
"method": "auto",
"timeout": 30,
"max_concurrent": 5,
"rate_limit": 1.0
})
if bulk_result and bulk_result.content:
data = json.loads(bulk_result.content[0].text)
print(f"π Bulk Results:")
print(f" Total: {data['total_domains']}")
print(f" Successful: {data['successful_lookups']}")
print(f" Failed: {data['failed_lookups']}")
print(f" Total Time: {data['total_time']:.2f}s")
for result in data['results']:
if result['success']:
print(f" β
{result['domain']}: {result['data']['registrar'] if result['data'] else 'N/A'}")
else:
print(f" β {result['domain']}: {result['error']}")
except Exception as e:
print(f"β Error: {e}")
finally:
await client.disconnect()
print("π Disconnected from MCP server")
asyncio.run(mcp_client_example())The library provides comprehensive error handling:
from domain_checker.exceptions import (
DomainLookupError,
WhoisError,
RdapError,
ValidationError,
TimeoutError
)
try:
result = await checker.lookup_domain("invalid-domain")
except ValidationError as e:
print(f"Invalid domain: {e}")
except TimeoutError as e:
print(f"Lookup timed out: {e}")
except DomainLookupError as e:
print(f"Lookup failed: {e}")- Use appropriate concurrency: Set
max_concurrentbased on your system and network capacity - Rate limiting: Use
rate_limitto avoid overwhelming servers - Method selection: RDAP is generally faster than WHOIS
- Batch processing: Use bulk operations for multiple domains
- pipx installation: Use pipx for better isolation and performance
β Python 3.8+ is required. Found: 3.7
Solution: Upgrade Python to 3.8 or higher
β Permission denied: /usr/local/bin/domch
Solution: Use pipx for isolated installation: pipx install -e .
β externally-managed-environment
Solution: Use pipx for installation: pipx install -e .
β ModuleNotFoundError: No module named 'domain_checker'
Solution: Reinstall the package with pipx reinstall domain-checker
β domch: command not found
Solution:
- First, ensure pipx is in your PATH:
pipx ensurepath - Restart your terminal or run:
source ~/.bashrc(or~/.zshrc) - If still not found, reinstall:
pipx reinstall domain-checker
- Cause: Network latency or rate limiting
- Solution: Adjust
--rate-limitand--concurrentparameters
- Cause: Large bulk operations
- Solution: Reduce
--concurrentparameter or process in smaller batches
- Cause: WHOIS servers are slow or unresponsive
- Solution: Use
--method rdapor increase--timeout
- Cause: RDAP servers are down or domain not supported
- Solution: Use
--method whoisor--method auto
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
MIT License - see LICENSE file for details.
For issues and questions:
- Create an issue on GitHub
- Check the documentation
- Review the examples
The domch update command keeps your installation current.
domch update- Checks latest release and recent commits on
main - If already up to date, suggests using
--forceif desired
domch update --force- Skips version checks and pulls the absolute latest commit from
main - Performs a fetch+hard reset to ensure newest code
- Shows pulled commit hash/message/date for verification
This behavior ensures you can always update even when no new release was cut yet.
- Initial release
- WHOIS and RDAP support
- CLI interface
- MCP server
- Bulk processing
- Configuration system
- Error handling