|
| 1 | +"""Domain privacy (WhoisGuard) API.""" |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +from decimal import Decimal |
| 6 | +from typing import Any, Literal |
| 7 | + |
| 8 | +from namecheap.models import WhoisguardEntry |
| 9 | + |
| 10 | +from .base import BaseAPI |
| 11 | + |
| 12 | + |
| 13 | +class WhoisguardAPI(BaseAPI): |
| 14 | + """Domain privacy (WhoisGuard) management. |
| 15 | +
|
| 16 | + The Namecheap API uses WhoisGuard IDs internally, but this class |
| 17 | + provides domain-name-based convenience methods that resolve the ID |
| 18 | + automatically via get_list(). |
| 19 | + """ |
| 20 | + |
| 21 | + def get_list( |
| 22 | + self, |
| 23 | + *, |
| 24 | + list_type: Literal["ALL", "ALLOTED", "FREE", "DISCARD"] = "ALL", |
| 25 | + page: int = 1, |
| 26 | + page_size: int = 100, |
| 27 | + ) -> list[WhoisguardEntry]: |
| 28 | + """ |
| 29 | + Get all WhoisGuard subscriptions. |
| 30 | +
|
| 31 | + Args: |
| 32 | + list_type: Filter type (ALL, ALLOTED, FREE, DISCARD) |
| 33 | + page: Page number |
| 34 | + page_size: Items per page (2-100) |
| 35 | +
|
| 36 | + Returns: |
| 37 | + List of WhoisguardEntry subscriptions |
| 38 | +
|
| 39 | + Examples: |
| 40 | + >>> entries = nc.whoisguard.get_list() |
| 41 | + >>> for e in entries: |
| 42 | + ... print(f"{e.domain} (ID={e.id}) status={e.status}") |
| 43 | + """ |
| 44 | + result: Any = self._request( |
| 45 | + "namecheap.whoisguard.getList", |
| 46 | + { |
| 47 | + "ListType": list_type, |
| 48 | + "Page": page, |
| 49 | + "PageSize": min(page_size, 100), |
| 50 | + }, |
| 51 | + path="WhoisguardGetListResult", |
| 52 | + ) |
| 53 | + |
| 54 | + if not result: |
| 55 | + return [] |
| 56 | + |
| 57 | + entries = result.get("Whoisguard", []) |
| 58 | + if isinstance(entries, dict): |
| 59 | + entries = [entries] |
| 60 | + assert isinstance(entries, list), f"Unexpected Whoisguard type: {type(entries)}" |
| 61 | + |
| 62 | + return [WhoisguardEntry.model_validate(e) for e in entries] |
| 63 | + |
| 64 | + def _resolve_id(self, domain: str) -> int: |
| 65 | + """Resolve a domain name to its WhoisGuard ID.""" |
| 66 | + entries = self.get_list(list_type="ALLOTED") |
| 67 | + for entry in entries: |
| 68 | + if entry.domain.lower() == domain.lower(): |
| 69 | + return entry.id |
| 70 | + raise ValueError( |
| 71 | + f"No WhoisGuard subscription found for {domain}. " |
| 72 | + f"Domain must have WhoisGuard allotted to enable/disable it." |
| 73 | + ) |
| 74 | + |
| 75 | + def enable(self, domain: str, forwarded_to_email: str) -> bool: |
| 76 | + """ |
| 77 | + Enable domain privacy for a domain. |
| 78 | +
|
| 79 | + Args: |
| 80 | + domain: Domain name (resolved to WhoisGuard ID automatically) |
| 81 | + forwarded_to_email: Email where privacy-masked emails get forwarded |
| 82 | +
|
| 83 | + Returns: |
| 84 | + True if successful |
| 85 | +
|
| 86 | + Examples: |
| 87 | + >>> nc.whoisguard.enable("example.com", "me@gmail.com") |
| 88 | + """ |
| 89 | + wg_id = self._resolve_id(domain) |
| 90 | + |
| 91 | + result: Any = self._request( |
| 92 | + "namecheap.whoisguard.enable", |
| 93 | + { |
| 94 | + "WhoisguardID": wg_id, |
| 95 | + "ForwardedToEmail": forwarded_to_email, |
| 96 | + }, |
| 97 | + path="WhoisguardEnableResult", |
| 98 | + ) |
| 99 | + |
| 100 | + assert result, f"API returned empty result for whoisguard.enable on {domain}" |
| 101 | + return result.get("@IsSuccess", "false").lower() == "true" |
| 102 | + |
| 103 | + def disable(self, domain: str) -> bool: |
| 104 | + """ |
| 105 | + Disable domain privacy for a domain. |
| 106 | +
|
| 107 | + Args: |
| 108 | + domain: Domain name (resolved to WhoisGuard ID automatically) |
| 109 | +
|
| 110 | + Returns: |
| 111 | + True if successful |
| 112 | +
|
| 113 | + Examples: |
| 114 | + >>> nc.whoisguard.disable("example.com") |
| 115 | + """ |
| 116 | + wg_id = self._resolve_id(domain) |
| 117 | + |
| 118 | + result: Any = self._request( |
| 119 | + "namecheap.whoisguard.disable", |
| 120 | + {"WhoisguardID": wg_id}, |
| 121 | + path="WhoisguardDisableResult", |
| 122 | + ) |
| 123 | + |
| 124 | + assert result, f"API returned empty result for whoisguard.disable on {domain}" |
| 125 | + return result.get("@IsSuccess", "false").lower() == "true" |
| 126 | + |
| 127 | + def renew(self, domain: str, *, years: int = 1) -> dict[str, Any]: |
| 128 | + """ |
| 129 | + Renew domain privacy for a domain. |
| 130 | +
|
| 131 | + Args: |
| 132 | + domain: Domain name (resolved to WhoisGuard ID automatically) |
| 133 | + years: Number of years to renew (1-9) |
| 134 | +
|
| 135 | + Returns: |
| 136 | + Dict with OrderId, TransactionId, ChargedAmount |
| 137 | +
|
| 138 | + Examples: |
| 139 | + >>> result = nc.whoisguard.renew("example.com", years=1) |
| 140 | + >>> print(f"Charged: {result['charged_amount']}") |
| 141 | + """ |
| 142 | + assert 1 <= years <= 9, "Years must be between 1 and 9" |
| 143 | + wg_id = self._resolve_id(domain) |
| 144 | + |
| 145 | + result: Any = self._request( |
| 146 | + "namecheap.whoisguard.renew", |
| 147 | + { |
| 148 | + "WhoisguardID": wg_id, |
| 149 | + "Years": years, |
| 150 | + }, |
| 151 | + path="WhoisguardRenewResult", |
| 152 | + ) |
| 153 | + |
| 154 | + assert result, f"API returned empty result for whoisguard.renew on {domain}" |
| 155 | + return { |
| 156 | + "whoisguard_id": int(result.get("@WhoisguardId", wg_id)), |
| 157 | + "years": int(result.get("@Years", years)), |
| 158 | + "is_renewed": result.get("@Renew", "false").lower() == "true", |
| 159 | + "order_id": int(result.get("@OrderId", 0)), |
| 160 | + "transaction_id": int(result.get("@TransactionId", 0)), |
| 161 | + "charged_amount": Decimal(result.get("@ChargedAmount", "0")), |
| 162 | + } |
| 163 | + |
| 164 | + def change_email(self, domain: str) -> dict[str, str]: |
| 165 | + """ |
| 166 | + Rotate the privacy forwarding email address for a domain. |
| 167 | +
|
| 168 | + Namecheap generates a new masked email and retires the old one. |
| 169 | + No input email needed — the API handles the rotation. |
| 170 | +
|
| 171 | + Args: |
| 172 | + domain: Domain name (resolved to WhoisGuard ID automatically) |
| 173 | +
|
| 174 | + Returns: |
| 175 | + Dict with new_email and old_email |
| 176 | +
|
| 177 | + Examples: |
| 178 | + >>> result = nc.whoisguard.change_email("example.com") |
| 179 | + >>> print(f"New: {result['new_email']}") |
| 180 | + """ |
| 181 | + wg_id = self._resolve_id(domain) |
| 182 | + |
| 183 | + result: Any = self._request( |
| 184 | + "namecheap.whoisguard.changeEmailAddress", |
| 185 | + {"WhoisguardID": wg_id}, |
| 186 | + path="WhoisguardChangeEmailAddressResult", |
| 187 | + ) |
| 188 | + |
| 189 | + assert result, ( |
| 190 | + f"API returned empty result for whoisguard.changeEmailAddress on {domain}" |
| 191 | + ) |
| 192 | + return { |
| 193 | + "new_email": result.get("@WGEmail", ""), |
| 194 | + "old_email": result.get("@WGOldEmail", ""), |
| 195 | + } |
0 commit comments