GoDaddy.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. <?php
  2. namespace App\Utils\DDNS;
  3. use App\Utils\Library\Templates\DNS;
  4. use Arr;
  5. use Cache;
  6. use Http;
  7. use Log;
  8. use RuntimeException;
  9. class GoDaddy implements DNS
  10. {
  11. // 开发依据: https://developer.godaddy.com/doc/endpoint/domains
  12. private const API_ENDPOINT = 'https://api.godaddy.com/v1/domains/';
  13. public const KEY = 'godaddy';
  14. public const LABEL = 'GoDaddy';
  15. private string $key;
  16. private string $secret;
  17. private array $domainInfo;
  18. public function __construct(private readonly string $subdomain)
  19. {
  20. $this->key = sysConfig('ddns_key');
  21. $this->secret = sysConfig('ddns_secret');
  22. $this->domainInfo = $this->parseDomainInfo();
  23. }
  24. private function parseDomainInfo(): array
  25. {
  26. $domains = Cache::remember('ddns_get_domains', now()->addHour(), function () {
  27. return array_column($this->sendRequest('DescribeDomains') ?? [], 'domain');
  28. });
  29. if ($domains) {
  30. $matched = Arr::first($domains, fn ($domain) => str_contains($this->subdomain, $domain));
  31. }
  32. if (empty($matched)) {
  33. throw new RuntimeException('['.self::LABEL." — DescribeDomains] The subdomain $this->subdomain does not match any domain in your account.");
  34. }
  35. return [
  36. 'sub' => rtrim(substr($this->subdomain, 0, -strlen($matched)), '.'),
  37. 'domain' => $matched,
  38. ];
  39. }
  40. private function sendRequest(string $action, array $parameters = []): array|bool
  41. {
  42. $client = Http::timeout(15)->retry(3, 1000)->withHeader('Authorization', "sso-key $this->key:$this->secret")->baseUrl(self::API_ENDPOINT)->asJson();
  43. $response = match ($action) {
  44. 'DescribeDomains' => $client->get('', ['statuses' => 'ACTIVE']),
  45. 'DescribeSubDomainRecords' => $client->get("{$parameters['Domain']}/records/{$parameters['Type']}/{$parameters['Sub']}"),
  46. 'AddDomainRecord' => $client->patch("{$parameters['Domain']}/records", $parameters['Data']),
  47. 'UpdateDomainRecord' => $client->put("{$parameters['Domain']}/records/{$parameters['Type']}/{$parameters['Sub']}", $parameters['Data']),
  48. 'DeleteDomainRecord' => $client->delete("{$parameters['Domain']}/records/{$parameters['Type']}/{$parameters['Sub']}"),
  49. };
  50. $data = $response->json();
  51. if ($response->successful()) {
  52. return $data ?: true;
  53. }
  54. if ($data) {
  55. Log::error('['.self::LABEL." — $action] 返回错误信息: ".$data['message'] ?? 'Unknown error');
  56. } else {
  57. Log::error('['.self::LABEL." — $action] 请求失败");
  58. }
  59. return false;
  60. }
  61. public function store(string $ip, string $type): bool
  62. {
  63. $ret = $this->sendRequest('AddDomainRecord', ['Domain' => $this->domainInfo['domain'], 'Data' => [['name' => $this->domainInfo['sub'], 'type' => $type, 'data' => $ip]]]);
  64. return (bool) ($ret ?: false);
  65. }
  66. public function destroy(string $type, string $ip): bool
  67. {
  68. if ($ip) {
  69. $ret = $this->update('', $ip, $type);
  70. } else {
  71. $ret = $this->sendRequest('DeleteDomainRecord', ['Sub' => $this->domainInfo['sub'], 'Domain' => $this->domainInfo['domain'], 'Type' => $type]);
  72. }
  73. return (bool) ($ret ?: false);
  74. }
  75. public function update(string $latest_ip, string $original_ip, string $type): bool
  76. {
  77. $recordIPs = $this->getRecordIPs($type);
  78. if ($recordIPs) {
  79. $recordIPs = array_values(array_filter($recordIPs, static fn ($ip) => $ip !== $original_ip));
  80. if ($latest_ip) {
  81. $recordIPs[] = $latest_ip;
  82. }
  83. $ret = $this->sendRequest('UpdateDomainRecord', [
  84. 'Sub' => $this->domainInfo['sub'],
  85. 'Domain' => $this->domainInfo['domain'],
  86. 'Type' => $type,
  87. 'Data' => array_map(static function ($ip) {
  88. return ['data' => $ip];
  89. }, $recordIPs),
  90. ]);
  91. }
  92. return (bool) ($ret ?? false);
  93. }
  94. private function getRecordIPs(string $type): array
  95. {
  96. $records = $this->sendRequest('DescribeSubDomainRecords', ['Sub' => $this->domainInfo['sub'], 'Domain' => $this->domainInfo['domain'], 'Type' => $type]);
  97. return array_column($records, 'data');
  98. }
  99. }