aboutsummaryrefslogtreecommitdiffstats
path: root/src/porkbun/scraper.py
blob: aef2b4945435dcac0e5cf2984010f5f002c62c8c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import httpx
import asyncio
import sys
import json
from typing import Dict, Any, Optional
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type


class PorkbunAPI:
    BASE_URL = "https://api.porkbun.com/api/json/v3"

    def __init__(self, api_key: str, secret_key: str, debug: bool = False):
        self.api_key = api_key
        self.secret_key = secret_key
        self.debug = debug
        self.client = httpx.AsyncClient(timeout=30.0)

    async def _make_request(self, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        payload = {
            "apikey": self.api_key,
            "secretapikey": self.secret_key
        }
        if data:
            payload.update(data)

        url = f"{self.BASE_URL}/{endpoint}"

        if self.debug:
            print("=" * 80, file=sys.stderr)
            print(f"DEBUG: REQUEST", file=sys.stderr)
            print(f"URL: POST {url}", file=sys.stderr)
            print(f"Headers: {dict(self.client.headers)}", file=sys.stderr)
            print(f"Payload: {json.dumps(payload, indent=2)}", file=sys.stderr)
            print("=" * 80, file=sys.stderr)

        while True:
            response = await self.client.post(url, json=payload)

            if self.debug:
                print("=" * 80, file=sys.stderr)
                print(f"DEBUG: RESPONSE", file=sys.stderr)
                print(f"Status: {response.status_code} {response.reason_phrase}", file=sys.stderr)
                print(f"Headers: {dict(response.headers)}", file=sys.stderr)
                print(f"Body: {response.text[:1000]}", file=sys.stderr)
                if len(response.text) > 1000:
                    print(f"... (truncated, total length: {len(response.text)})", file=sys.stderr)
                print("=" * 80, file=sys.stderr)

            if response.status_code == 429:
                retry_after = int(response.headers.get("Retry-After", 1))
                if self.debug:
                    print(f"DEBUG: Rate limited, waiting {retry_after}s", file=sys.stderr)
                await asyncio.sleep(retry_after)
                continue

            response.raise_for_status()
            result = response.json()

            if result.get("status") == "ERROR":
                raise Exception(f"API Error: {result.get('message', 'Unknown error')}")

            return result

    async def ping(self) -> Dict[str, Any]:
        return await self._make_request("ping")

    async def get_pricing(self) -> Dict[str, Any]:
        return await self._make_request("pricing/get")

    async def check_domain_availability(self, domain: str) -> Dict[str, Any]:
        return await self._make_request(f"domain/checkDomain/{domain}")

    async def get_domain_pricing(self, domain: str) -> Dict[str, Any]:
        pricing_data = await self.get_pricing()

        if "pricing" not in pricing_data:
            raise Exception("Invalid pricing response from API")

        parts = domain.split('.')
        if len(parts) < 2:
            raise ValueError(f"Invalid domain format: {domain}")

        tld = '.'.join(parts[1:])

        result = {
            "domain": domain,
            "tld": tld
        }

        # Get pricing info
        if tld in pricing_data["pricing"]:
            result["pricing"] = pricing_data["pricing"][tld]
        else:
            result["error"] = "TLD not found in pricing data"
            return result

        # Check domain availability
        try:
            availability = await self.check_domain_availability(domain)
            if availability.get("status") == "SUCCESS":
                response_data = availability.get("response", {})
                result["available"] = response_data.get("avail") == "yes"
                if response_data.get("premium") == "yes":
                    result["premium"] = True
            else:
                result["available"] = None
        except Exception as e:
            result["available"] = None

        return result

    async def close(self):
        await self.client.aclose()