yves.gugger dc77b2110a feat: Complete Watchlist monitoring, Portfolio tracking & Listings marketplace
## Watchlist & Monitoring
-  Automatic domain monitoring based on subscription tier
-  Email alerts when domains become available
-  Health checks (DNS/HTTP/SSL) with caching
-  Expiry warnings for domains <30 days
-  Weekly digest emails
-  Instant alert toggle (optimistic UI updates)
-  Redesigned health check overlays with full details
- 🔒 'Not public' display for .ch/.de domains without public expiry

## Portfolio Management (NEW)
-  Track owned domains with purchase price & date
-  ROI calculation (unrealized & realized)
-  Domain valuation with auto-refresh
-  Renewal date tracking
-  Sale recording with profit calculation
-  List domains for sale directly from portfolio
-  Full portfolio summary dashboard

## Listings / For Sale
-  Renamed from 'Portfolio' to 'For Sale'
-  Fixed listing limits: Scout=0, Trader=5, Tycoon=50
-  Featured badge for Tycoon listings
-  Inquiries modal for sellers
-  Email notifications when buyer inquires
-  Inquiries column in listings table

## Scrapers & Data
-  Added 4 new registrar scrapers (Namecheap, Cloudflare, GoDaddy, Dynadot)
-  Increased scraping frequency to 2x daily (03:00 & 15:00 UTC)
-  Real historical data from database
-  Fixed RDAP/WHOIS for .ch/.de domains
-  Enhanced SSL certificate parsing

## Scheduler Jobs
-  Tiered domain checks (Scout=daily, Trader=hourly, Tycoon=10min)
-  Daily health checks (06:00 UTC)
-  Weekly expiry warnings (Mon 08:00 UTC)
-  Weekly digest emails (Sun 10:00 UTC)
-  Auction cleanup every 15 minutes

## UI/UX Improvements
-  Removed 'Back' buttons from Intel pages
-  Redesigned Radar page to match Market/Intel design
-  Less prominent check frequency footer
-  Consistent StatCard components across all pages
-  Ambient background glows
-  Better error handling

## Documentation
-  Updated README with monitoring section
-  Added env.example with all required variables
-  Updated Memory Bank (activeContext.md)
-  SMTP configuration requirements documented
2025-12-11 16:57:28 +01:00

134 lines
5.5 KiB
Python

"""GoDaddy TLD price scraper.
GoDaddy is the world's largest domain registrar with significant market share.
Their prices are important for market comparison, especially for promotional pricing.
"""
import logging
from datetime import datetime
from typing import Optional
import httpx
from app.services.tld_scraper.base import BaseTLDScraper, TLDPriceData, ScraperError
logger = logging.getLogger(__name__)
class GoDaddyScraper(BaseTLDScraper):
"""
Scraper for GoDaddy domain prices.
GoDaddy doesn't have a public pricing API, but we maintain
known prices for major TLDs based on their public pricing pages.
Key characteristics of GoDaddy pricing:
- Low promotional first-year prices
- Higher renewal prices (important for "renewal trap" detection)
- Frequent sales and discounts
"""
name = "godaddy"
base_url = "https://www.godaddy.com"
# Known GoDaddy prices (as of Dec 2024)
# Note: GoDaddy has aggressive promo pricing but high renewals
# Source: https://www.godaddy.com/tlds
GODADDY_PRICES = {
# Major TLDs - Note the renewal trap on many!
"com": {"reg": 11.99, "renew": 22.99, "transfer": 11.99, "promo": 0.99},
"net": {"reg": 14.99, "renew": 23.99, "transfer": 14.99},
"org": {"reg": 9.99, "renew": 23.99, "transfer": 9.99},
"info": {"reg": 2.99, "renew": 24.99, "transfer": 2.99, "promo": True},
"biz": {"reg": 16.99, "renew": 24.99, "transfer": 16.99},
# Premium ccTLDs
"io": {"reg": 44.99, "renew": 59.99, "transfer": 44.99},
"co": {"reg": 11.99, "renew": 38.99, "transfer": 11.99},
"ai": {"reg": 79.99, "renew": 99.99, "transfer": 79.99},
"me": {"reg": 2.99, "renew": 19.99, "transfer": 2.99, "promo": True},
# Tech TLDs
"dev": {"reg": 15.99, "renew": 19.99, "transfer": 15.99},
"app": {"reg": 17.99, "renew": 21.99, "transfer": 17.99},
"tech": {"reg": 4.99, "renew": 54.99, "transfer": 4.99, "promo": True}, # Major trap!
"xyz": {"reg": 0.99, "renew": 14.99, "transfer": 0.99, "promo": True},
# Budget/Promo TLDs (watch out for renewals!)
"online": {"reg": 0.99, "renew": 44.99, "transfer": 0.99, "promo": True},
"site": {"reg": 0.99, "renew": 39.99, "transfer": 0.99, "promo": True},
"store": {"reg": 0.99, "renew": 59.99, "transfer": 0.99, "promo": True},
"club": {"reg": 0.99, "renew": 16.99, "transfer": 0.99, "promo": True},
"website": {"reg": 0.99, "renew": 24.99, "transfer": 0.99, "promo": True},
"space": {"reg": 0.99, "renew": 24.99, "transfer": 0.99, "promo": True},
# European ccTLDs
"uk": {"reg": 8.99, "renew": 12.99, "transfer": 8.99},
"de": {"reg": 9.99, "renew": 14.99, "transfer": 9.99},
"eu": {"reg": 6.99, "renew": 12.99, "transfer": 6.99},
"fr": {"reg": 11.99, "renew": 14.99, "transfer": 11.99},
"nl": {"reg": 9.99, "renew": 14.99, "transfer": 9.99},
# Other popular TLDs
"ca": {"reg": 12.99, "renew": 19.99, "transfer": 12.99},
"us": {"reg": 5.99, "renew": 21.99, "transfer": 5.99},
"tv": {"reg": 34.99, "renew": 44.99, "transfer": 34.99},
"cc": {"reg": 9.99, "renew": 14.99, "transfer": 9.99},
# New gTLDs
"shop": {"reg": 2.99, "renew": 39.99, "transfer": 2.99, "promo": True},
"blog": {"reg": 2.99, "renew": 34.99, "transfer": 2.99, "promo": True},
"cloud": {"reg": 4.99, "renew": 24.99, "transfer": 4.99, "promo": True},
"live": {"reg": 2.99, "renew": 29.99, "transfer": 2.99, "promo": True},
"world": {"reg": 2.99, "renew": 34.99, "transfer": 2.99, "promo": True},
}
async def scrape(self) -> list[TLDPriceData]:
"""
Return GoDaddy's known pricing data.
Since GoDaddy doesn't expose a public pricing API,
we use curated data from their public pricing pages.
Returns:
List of TLDPriceData objects with GoDaddy pricing
"""
results = []
now = datetime.utcnow()
for tld, prices in self.GODADDY_PRICES.items():
# Determine if this is promotional pricing
is_promo = prices.get("promo", False)
promo_price = prices["reg"] if is_promo else None
results.append(TLDPriceData(
tld=tld,
registrar="godaddy",
registration_price=prices["reg"],
renewal_price=prices["renew"],
transfer_price=prices.get("transfer"),
promo_price=promo_price,
currency="USD",
source="static",
confidence=0.9, # Static data, updated periodically
scraped_at=now,
notes="High renewal trap alert" if prices["renew"] > prices["reg"] * 3 else None,
))
logger.info(f"Loaded {len(results)} GoDaddy prices")
return results
async def health_check(self) -> bool:
"""Check if GoDaddy is accessible."""
try:
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.get(
self.base_url,
headers=self.get_headers(),
follow_redirects=True,
)
return response.status_code == 200
except Exception as e:
logger.debug(f"GoDaddy health check failed: {e}")
return False