feat(hub): improve WHOIS lookup reliability and enhance site UI
Build Docker images / Hub (push) Failing after 52s

Implement enhanced WHOIS lookup strategies, specifically targeting .eu
domains through EURid web scraping and alternative services to
improve data accuracy for expiry dates.

- Add EURid web scraping and alternative WHOIS service support for .eu domains
- Increase timeouts for .eu domain lookups in TCP and native WHOIS
- Improve domain scheduler to prevent overwriting valid data with zero-value dates
- Enhance site UI with subdomain indicators in domain tables
- Add filtering capabilities to the calendar view
- Implement drag-and-drop reordering for systems table
- Add new debug and test utilities for WHOIS and date parsing logic
This commit is contained in:
Tomas Dvorak
2026-05-08 11:07:34 +02:00
parent 1af18872d5
commit b6f40af67f
15 changed files with 1934 additions and 195 deletions
+160
View File
@@ -0,0 +1,160 @@
package main
import (
"context"
"fmt"
"io"
"net/http"
"regexp"
"strings"
"time"
)
func tryAlternativeWHOISService(ctx context.Context, serviceName, url, domainName string) (string, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return "", fmt.Errorf("failed to create request for %s: %w", serviceName, err)
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
client := &http.Client{Timeout: 15 * time.Second}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("failed to fetch %s: %w", serviceName, err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", fmt.Errorf("%s returned status %d", serviceName, resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read %s response: %w", serviceName, err)
}
return string(body), nil
}
func parseAlternativeWHOISHTML(html, domainName, serviceName string) {
fmt.Printf("HTML length from %s: %d characters\n", serviceName, len(html))
// Look for expiry date patterns (common across WHOIS services)
expiryPatterns := []string{
`Expiry Date:\s*</[^>]*>\s*([^<\n]+)`,
`Expiry Date:</[^>]*>\s*([^<\n]+)`,
`Expires on:\s*</[^>]*>\s*([^<\n]+)`,
`Expires:\s*</[^>]*>\s*([^<\n]+)`,
`"expiry":"([^"]+)"`,
`"expires":"([^"]+)"`,
`data-expiry="([^"]+)"`,
`expiry_date["\s]*:\s*"([^"]+)"`,
`expires["\s]*:\s*"([^"]+)"`,
`\d{4}-\d{2}-\d{2}`, // ISO date pattern
`\d{2}/\d{2}/\d{4}`, // DD/MM/YYYY pattern
`\d{2}-\d{2}-\d{4}`, // MM-DD-YYYY pattern
`202\d-\d{2}-\d{2}`, // 202x-xx-xx pattern
}
fmt.Printf("\n=== Searching for expiry dates on %s ===\n", serviceName)
for _, pattern := range expiryPatterns {
re := regexp.MustCompile(pattern)
matches := re.FindAllStringSubmatch(html, -1)
if len(matches) > 0 {
fmt.Printf("Pattern '%s' found %d matches:\n", pattern, len(matches))
for i, match := range matches {
if len(match) > 1 {
fmt.Printf(" Match %d: %s\n", i+1, strings.TrimSpace(match[1]))
}
}
}
}
// Look for registrar name
registrarPatterns := []string{
`Registrar:\s*</[^>]*>\s*([^<\n]+)`,
`Registrar:</[^>]*>\s*([^<\n]+)`,
`Registered through:\s*</[^>]*>\s*([^<\n]+)`,
`"registrar":"([^"]+)"`,
`data-registrar="([^"]+)"`,
}
fmt.Printf("\n=== Searching for registrar on %s ===\n", serviceName)
for _, pattern := range registrarPatterns {
re := regexp.MustCompile(pattern)
matches := re.FindAllStringSubmatch(html, -1)
if len(matches) > 0 {
fmt.Printf("Pattern '%s' found %d matches:\n", pattern, len(matches))
for i, match := range matches {
if len(match) > 1 {
fmt.Printf(" Match %d: %s\n", i+1, strings.TrimSpace(match[1]))
}
}
}
}
// Look for status
statusPatterns := []string{
`Status:\s*</[^>]*>\s*([^<\n]+)`,
`Status:</[^>]*>\s*([^<\n]+)`,
`"status":"([^"]+)"`,
`data-status="([^"]+)"`,
}
fmt.Printf("\n=== Searching for status on %s ===\n", serviceName)
for _, pattern := range statusPatterns {
re := regexp.MustCompile(pattern)
matches := re.FindAllStringSubmatch(html, -1)
if len(matches) > 0 {
fmt.Printf("Pattern '%s' found %d matches:\n", pattern, len(matches))
for i, match := range matches {
if len(match) > 1 {
fmt.Printf(" Match %d: %s\n", i+1, strings.TrimSpace(match[1]))
}
}
}
}
// Show sample HTML
fmt.Printf("\n=== Sample HTML from %s (first 1500 chars) ===\n", serviceName)
if len(html) > 1500 {
fmt.Printf("%s...\n", html[:1500])
} else {
fmt.Printf("%s\n", html)
}
}
func main() {
testDomains := []string{"bookra.eu", "sportcreative.eu"}
for _, domain := range testDomains {
fmt.Printf("Testing alternative WHOIS services for: %s\n", domain)
fmt.Println("========================================")
// Try multiple alternative WHOIS services
services := []struct {
name string
url string
}{
{"whois.com", fmt.Sprintf("https://www.whois.com/whois/%s", domain)},
{"who.is", fmt.Sprintf("https://who.is/whois/%s", domain)},
{"ip2location.com", fmt.Sprintf("https://www.ip2location.com/whois/%s", domain)},
}
for _, service := range services {
fmt.Printf("\n--- Testing %s ---\n", service.name)
html, err := tryAlternativeWHOISService(context.Background(), service.name, service.url, domain)
if err != nil {
fmt.Printf("Error: %v\n", err)
continue
}
parseAlternativeWHOISHTML(html, domain, service.name)
}
fmt.Println("\n========================================\n")
}
}
+94
View File
@@ -0,0 +1,94 @@
package main
import (
"fmt"
"regexp"
"strconv"
)
// parseFlexibleDate parses dates in various formats
func parseFlexibleDate(dateString string) string {
if dateString == "" {
return ""
}
// Remove common separators and normalize
normalized := regexp.MustCompile(`[./-]`).ReplaceAllString(dateString, "-")
normalized = regexp.MustCompile(`\s+`).ReplaceAllString(normalized, "")
// Try different date formats
formats := []string{
// DD.MM.YYYY, DD/MM/YYYY, DD-MM-YYYY
`^(\d{2})[-/.](\d{2})[-/.](\d{4})$`,
// YYYY-MM-DD, YYYY/MM/DD, YYYY.MM.DD
`^(\d{4})[-/.](\d{2})[-/.](\d{2})$`,
// MM-DD-YYYY, MM/DD/YYYY, MM.DD.YYYY
`^(\d{2})[-/.](\d{2})[-/.](\d{4})$`,
}
for _, format := range formats {
re := regexp.MustCompile(format)
match := re.FindStringSubmatch(normalized)
if match != nil {
part1, part2, part3 := match[1], match[2], match[3]
// Determine if it's DD.MM.YYYY or YYYY.MM.DD format
var year, month, day string
if len(part1) == 4 {
// YYYY.MM.DD format
year = part1
month = part2
day = part3
} else {
// DD.MM.YYYY format (most common)
day = part1
month = part2
year = part3
}
// Validate and format
yearNum, _ := strconv.Atoi(year)
monthNum, _ := strconv.Atoi(month)
dayNum, _ := strconv.Atoi(day)
if yearNum >= 2000 && yearNum <= 2100 && monthNum >= 1 && monthNum <= 12 && dayNum >= 1 && dayNum <= 31 {
return fmt.Sprintf("%s-%s-%s", year, fmt.Sprintf("%02s", month), fmt.Sprintf("%02s", day))
}
}
}
return ""
}
func main() {
testDates := []string{
"15.06.2026",
"13.11.2029",
"2026-06-15",
"15/06/2026",
"13-11-2029",
"2026.06.15",
"15 06 2026",
"invalid-date",
"32.13.2026", // Invalid day/month
"1999.12.31", // Too old
}
fmt.Println("Testing Flexible Date Parsing")
fmt.Println("=============================")
for _, date := range testDates {
result := parseFlexibleDate(date)
status := "✅"
if result == "" {
status = "❌"
}
fmt.Printf("%s '%s' -> '%s'\n", status, date, result)
}
fmt.Println("\nExample Usage:")
fmt.Println("=============")
fmt.Println("User can paste: '15.06.2026, 13.11.2029'")
fmt.Println("System will parse: '2026-06-15', '2029-11-13'")
}
+112
View File
@@ -0,0 +1,112 @@
package main
import (
"context"
"fmt"
"io"
"net/http"
"regexp"
"strings"
"time"
)
func main() {
testDomains := []string{"bookra.eu", "sportcreative.eu"}
for _, domain := range testDomains {
fmt.Printf("Testing EURid web scraping for: %s\n", domain)
fmt.Println("----------------------------------------")
// EURid web WHOIS URL
url := fmt.Sprintf("https://www.eurid.eu/en/registrations/search/?domain=%s", domain)
req, err := http.NewRequestWithContext(context.Background(), "GET", url, nil)
if err != nil {
fmt.Printf("Failed to create request: %v\n", err)
continue
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
client := &http.Client{Timeout: 15 * time.Second}
resp, err := client.Do(req)
if err != nil {
fmt.Printf("Failed to fetch EURid web page: %v\n", err)
continue
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
fmt.Printf("EURid web page returned status %d\n", resp.StatusCode)
continue
}
// Read the HTML response
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Failed to read EURid response: %v\n", err)
continue
}
html := string(body)
fmt.Printf("HTML length: %d characters\n", len(html))
// Look for expiry date patterns
expiryPatterns := []string{
`Expiry date:\s*</strong>\s*(\d{2}/\d{2}/\d{4})`,
`Expiry date:</strong>\s*(\d{2}/\d{2}/\d{4})`,
`Expiry date</strong>\s*(\d{2}/\d{2}/\d{4})`,
`"expiryDate":"([^"]+)"`,
`data-expiry="([^"]+)"`,
`\d{2}/\d{2}/\d{4}`, // Generic date pattern
}
fmt.Println("\n=== Searching for expiry dates ===")
for _, pattern := range expiryPatterns {
re := regexp.MustCompile(pattern)
matches := re.FindAllStringSubmatch(html, -1)
if len(matches) > 0 {
fmt.Printf("Pattern '%s' found %d matches:\n", pattern, len(matches))
for i, match := range matches {
if len(match) > 1 {
fmt.Printf(" Match %d: %s\n", i+1, match[1])
}
}
}
}
// Look for registrar patterns
registrarPatterns := []string{
`Registrar:\s*</strong>\s*([^<\n]+)`,
`Registrar:</strong>\s*([^<\n]+)`,
`Registrar</strong>\s*([^<\n]+)`,
`"registrar":"([^"]+)"`,
`data-registrar="([^"]+)"`,
}
fmt.Println("\n=== Searching for registrar ===")
for _, pattern := range registrarPatterns {
re := regexp.MustCompile(pattern)
matches := re.FindAllStringSubmatch(html, -1)
if len(matches) > 0 {
fmt.Printf("Pattern '%s' found %d matches:\n", pattern, len(matches))
for i, match := range matches {
if len(match) > 1 {
fmt.Printf(" Match %d: %s\n", i+1, strings.TrimSpace(match[1]))
}
}
}
}
// Show some sample HTML to understand the structure
fmt.Println("\n=== Sample HTML (first 1000 chars) ===")
if len(html) > 1000 {
fmt.Printf("%s...\n", html[:1000])
} else {
fmt.Printf("%s\n", html)
}
fmt.Println("\n========================================\n")
}
}
+163
View File
@@ -0,0 +1,163 @@
package main
import (
"context"
"fmt"
"io"
"net/http"
"regexp"
"strings"
"time"
)
func tryEURidEndpoint(ctx context.Context, url, domainName string) (string, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
// Use more realistic browser headers
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
req.Header.Set("Accept-Language", "en-US,en;q=0.9")
req.Header.Set("Accept-Encoding", "gzip, deflate, br")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Pragma", "no-cache")
req.Header.Set("Sec-Ch-Ua", "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"")
req.Header.Set("Sec-Ch-Ua-Mobile", "?0")
req.Header.Set("Sec-Ch-Ua-Platform", "\"Windows\"")
req.Header.Set("Sec-Fetch-Dest", "document")
req.Header.Set("Sec-Fetch-Mode", "navigate")
req.Header.Set("Sec-Fetch-Site", "none")
req.Header.Set("Sec-Fetch-User", "?1")
req.Header.Set("Upgrade-Insecure-Requests", "1")
client := &http.Client{Timeout: 20 * time.Second}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("failed to fetch EURid web page: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", fmt.Errorf("EURid web page returned status %d", resp.StatusCode)
}
// Read the HTML response
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read EURid response: %w", err)
}
return string(body), nil
}
func parseEURidWebHTML(html, domainName string) {
fmt.Printf("HTML length: %d characters\n", len(html))
// Look for expiry date patterns
expiryPatterns := []string{
`Expiry date:\s*</strong>\s*(\d{2}/\d{2}/\d{4})`,
`Expiry date:</strong>\s*(\d{2}/\d{2}/\d{4})`,
`Expiry date</strong>\s*(\d{2}/\d{2}/\d{4})`,
`"expiryDate":"([^"]+)"`,
`data-expiry="([^"]+)"`,
`\d{2}/\d{2}/\d{4}`, // Generic date pattern
`\d{4}-\d{2}-\d{2}`, // ISO date pattern
}
fmt.Println("\n=== Searching for expiry dates ===")
for _, pattern := range expiryPatterns {
re := regexp.MustCompile(pattern)
matches := re.FindAllStringSubmatch(html, -1)
if len(matches) > 0 {
fmt.Printf("Pattern '%s' found %d matches:\n", pattern, len(matches))
for i, match := range matches {
if len(match) > 1 {
fmt.Printf(" Match %d: %s\n", i+1, match[1])
}
}
}
}
// Look for registrar patterns
registrarPatterns := []string{
`Registrar:\s*</strong>\s*([^<\n]+)`,
`Registrar:</strong>\s*([^<\n]+)`,
`Registrar</strong>\s*([^<\n]+)`,
`"registrar":"([^"]+)"`,
`data-registrar="([^"]+)"`,
}
fmt.Println("\n=== Searching for registrar ===")
for _, pattern := range registrarPatterns {
re := regexp.MustCompile(pattern)
matches := re.FindAllStringSubmatch(html, -1)
if len(matches) > 0 {
fmt.Printf("Pattern '%s' found %d matches:\n", pattern, len(matches))
for i, match := range matches {
if len(match) > 1 {
fmt.Printf(" Match %d: %s\n", i+1, strings.TrimSpace(match[1]))
}
}
}
}
// Look for any JSON data
jsonPatterns := []string{
`\{[^}]*"expiry[^}]*\}`,
`\{[^}]*"registrar[^}]*\}`,
`\{[^}]*"domain"[^}]*\}`,
}
fmt.Println("\n=== Searching for JSON data ===")
for _, pattern := range jsonPatterns {
re := regexp.MustCompile(pattern)
matches := re.FindAllString(html, -1)
if len(matches) > 0 {
fmt.Printf("Pattern '%s' found %d matches:\n", pattern, len(matches))
for i, match := range matches {
fmt.Printf(" Match %d: %s\n", i+1, match)
}
}
}
// Show sample HTML
fmt.Println("\n=== Sample HTML (first 2000 chars) ===")
if len(html) > 2000 {
fmt.Printf("%s...\n", html[:2000])
} else {
fmt.Printf("%s\n", html)
}
}
func main() {
testDomains := []string{"bookra.eu", "sportcreative.eu"}
for _, domain := range testDomains {
fmt.Printf("Testing EURid web scraping for: %s\n", domain)
fmt.Println("========================================")
// Try multiple EURid endpoints
endpoints := []string{
fmt.Sprintf("https://whois.eurid.eu/en/?q=%s", domain),
fmt.Sprintf("https://whois.eurid.eu/en/search?q=%s", domain),
fmt.Sprintf("https://www.eurid.eu/en/whois/?domain=%s", domain),
}
for i, url := range endpoints {
fmt.Printf("\n--- Attempt %d: %s ---\n", i+1, url)
html, err := tryEURidEndpoint(context.Background(), url, domain)
if err != nil {
fmt.Printf("Error: %v\n", err)
continue
}
parseEURidWebHTML(html, domain)
break // If we got HTML, don't try other endpoints
}
fmt.Println("\n========================================\n")
}
}
+112
View File
@@ -0,0 +1,112 @@
package main
import (
"fmt"
"net"
"strings"
"time"
)
func main() {
// Test TCP WHOIS for .eu domains
testDomains := []string{"bookra.eu", "sportcreative.eu"}
for _, domainName := range testDomains {
fmt.Printf("Testing TCP WHOIS for: %s\n", domainName)
fmt.Println("----------------------------------------")
// Extract TLD
parts := strings.Split(domainName, ".")
if len(parts) < 2 {
fmt.Printf("Invalid domain format: %s\n", domainName)
continue
}
tld := strings.ToLower(parts[len(parts)-1])
// WHOIS server for .eu
server := "whois.eu"
if tld != "eu" {
fmt.Printf("Skipping non-eu domain: %s\n", domainName)
continue
}
addr := net.JoinHostPort(server, "43")
fmt.Printf("Connecting to: %s\n", addr)
// Use longer timeout for .eu domains
timeout := 20 * time.Second
dialer := &net.Dialer{Timeout: timeout}
start := time.Now()
conn, err := dialer.Dial("tcp", addr)
if err != nil {
fmt.Printf("TCP WHOIS dial failed: %v\n", err)
continue
}
defer conn.Close()
fmt.Printf("Connected in: %v\n", time.Since(start))
// Send query
query := domainName + "\r\n"
fmt.Printf("Sending query: %q\n", query)
writeStart := time.Now()
if _, err := conn.Write([]byte(query)); err != nil {
fmt.Printf("TCP WHOIS write failed: %v\n", err)
continue
}
fmt.Printf("Write completed in: %v\n", time.Since(writeStart))
// Set read deadline
if err := conn.SetReadDeadline(time.Now().Add(timeout)); err != nil {
fmt.Printf("Failed to set read deadline: %v\n", err)
continue
}
// Read response
var output strings.Builder
buf := make([]byte, 4096)
totalRead := 0
readStart := time.Now()
for {
n, err := conn.Read(buf)
if n > 0 {
output.Write(buf[:n])
totalRead += n
fmt.Printf("Read %d bytes (total: %d)\n", n, totalRead)
}
if err != nil {
if err.Error() != "EOF" {
fmt.Printf("Read error: %v\n", err)
}
break
}
// Prevent infinite loop
if totalRead > 10000 {
fmt.Printf("Stopping read after 10KB\n")
break
}
}
fmt.Printf("Read completed in: %v\n", time.Since(readStart))
fmt.Printf("Total bytes read: %d\n", totalRead)
response := output.String()
if len(response) > 0 {
fmt.Printf("✅ WHOIS Response (first 500 chars):\n")
if len(response) > 500 {
fmt.Printf("%s...\n", response[:500])
} else {
fmt.Printf("%s\n", response)
}
} else {
fmt.Printf("❌ No response received\n")
}
fmt.Println()
fmt.Println("========================================")
fmt.Println()
}
}
+119
View File
@@ -0,0 +1,119 @@
package main
import (
"context"
"fmt"
"time"
"github.com/henrygd/beszel/internal/hub/domains/whois"
)
func main() {
// Create a new lookup service with a dummy API key
lookupService := whois.NewLookupService("dummy-api-key")
// Test domains including .eu domain
testDomains := []string{
"google.com",
"bookra.eu", // Your .eu domain
"sportcreative.eu", // Another .eu domain
"github.com",
}
ctx := context.Background()
fmt.Println("=== WHOIS Lookup Test ===")
fmt.Println()
for _, domainName := range testDomains {
fmt.Printf("Testing domain: %s\n", domainName)
fmt.Println("----------------------------------------")
start := time.Now()
// Test WHOIS lookup
whoisData, err := lookupService.LookupWHOIS(ctx, domainName)
duration := time.Since(start)
fmt.Printf("Lookup duration: %v\n", duration)
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else if whoisData != nil {
fmt.Printf("✅ WHOIS Data Found:\n")
fmt.Printf(" Domain: %s\n", whoisData.DomainName)
fmt.Printf(" Status: %v\n", whoisData.Status)
fmt.Printf(" DNSSEC: %s\n", whoisData.DNSSEC)
if whoisData.Dates.ExpiryDate != nil && !whoisData.Dates.ExpiryDate.IsZero() {
fmt.Printf(" Expiry Date: %s\n", whoisData.Dates.ExpiryDate.Format("2006-01-02"))
}
if whoisData.Dates.CreationDate != nil && !whoisData.Dates.CreationDate.IsZero() {
fmt.Printf(" Creation Date: %s\n", whoisData.Dates.CreationDate.Format("2006-01-02"))
}
if whoisData.Dates.UpdatedDate != nil && !whoisData.Dates.UpdatedDate.IsZero() {
fmt.Printf(" Updated Date: %s\n", whoisData.Dates.UpdatedDate.Format("2006-01-02"))
}
if whoisData.Registrar.Name != "" {
fmt.Printf(" Registrar: %s\n", whoisData.Registrar.Name)
}
if whoisData.Registrar.ID != "" {
fmt.Printf(" Registrar ID: %s\n", whoisData.Registrar.ID)
}
if whoisData.Registrant.Name != "" || whoisData.Registrant.Organization != "" {
fmt.Printf(" Registrant: %s (%s)\n", whoisData.Registrant.Name, whoisData.Registrant.Organization)
}
if whoisData.Registrant.Country != "" {
fmt.Printf(" Registrant Country: %s\n", whoisData.Registrant.Country)
}
} else {
fmt.Printf("❌ No WHOIS data returned\n")
}
fmt.Println()
fmt.Println("=== Full Domain Lookup Test ===")
// Test full domain lookup
fullDomain, err := lookupService.LookupDomain(ctx, domainName)
if err != nil {
fmt.Printf("Full lookup ERROR: %v\n", err)
} else if fullDomain != nil {
fmt.Printf("✅ Full Domain Data:\n")
fmt.Printf(" Domain: %s\n", fullDomain.DomainName)
fmt.Printf(" Status: %s\n", fullDomain.Status)
fmt.Printf(" Active: %t\n", fullDomain.Active)
if fullDomain.ExpiryDate != nil {
fmt.Printf(" Expiry: %s\n", fullDomain.ExpiryDate.Format("2006-01-02"))
daysUntil := int(fullDomain.ExpiryDate.Sub(time.Now()).Hours() / 24)
fmt.Printf(" Days Until Expiry: %d\n", daysUntil)
}
fmt.Printf(" Registrar: %s\n", fullDomain.RegistrarName)
fmt.Printf(" DNSSEC: %s\n", fullDomain.DNSSEC)
if len(fullDomain.IPv4Addresses) > 0 {
fmt.Printf(" IPv4 Addresses: %v\n", fullDomain.IPv4Addresses)
}
if len(fullDomain.IPv6Addresses) > 0 {
fmt.Printf(" IPv6 Addresses: %v\n", fullDomain.IPv6Addresses)
}
if len(fullDomain.NameServers) > 0 {
fmt.Printf(" Name Servers: %v\n", fullDomain.NameServers)
}
if len(fullDomain.MXRecords) > 0 {
fmt.Printf(" MX Records: %v\n", fullDomain.MXRecords)
}
if fullDomain.SSLValidTo != nil {
fmt.Printf(" SSL Valid Until: %s\n", fullDomain.SSLValidTo.Format("2006-01-02"))
sslDaysUntil := int(fullDomain.SSLValidTo.Sub(time.Now()).Hours() / 24)
fmt.Printf(" SSL Days Until: %d\n", sslDaysUntil)
}
}
fmt.Println()
fmt.Println("========================================")
fmt.Println()
}
}
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env node
// Test WhoisXML API for .eu domains
const testWhoisXML = async (domain) => {
try {
// Note: This would require a real API key to test
console.log(`Testing WhoisXML API for: ${domain}`);
console.log('Note: WhoisXML API requires an API key to test properly');
// URL structure they use
const url = `https://www.whoisxmlapi.com/whoisserver/WhoisService?apiKey=YOUR_API_KEY&outputFormat=json&domainName=${domain}`;
console.log(`WhoisXML URL: ${url}`);
// Based on their code, WhoisXML should return expiry dates for .eu domains
console.log('WhoisXML API typically provides expiry dates for .eu domains that TCP WHOIS does not');
} catch (error) {
console.error(`Error: ${error.message}`);
}
};
testWhoisXML('bookra.eu');
testWhoisXML('sportcreative.eu');