Skip to content

Commit f26d506

Browse files
CopilotNewFuture
andauthored
refactor(provider): optimize cloudns implementation and add documentation (#664)
* Initial plan * refactor(provider.cloudns): optimize and simplify implementation Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com> * docs(providers): add comprehensive cloudns documentation and update indices Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com> * fix(provider.cloudns): change default TTL from 3600s to 60s Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com> * fix(provider.cloudns): fix Python 2.7 compatibility with dict unpacking Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>
1 parent 8184021 commit f26d506

7 files changed

Lines changed: 397 additions & 76 deletions

File tree

ddns/provider/cloudns.py

Lines changed: 111 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# coding=utf-8
22
"""
33
ClouDNS API
4-
@author: NewFuture (modifications for ClouDNS)
4+
@doc: https://www.cloudns.net/wiki/
5+
@author: NewFuture
56
"""
67

78
from ._base import BaseProvider, TYPE_FORM
@@ -15,12 +16,21 @@ class CloudnsProvider(BaseProvider):
1516

1617
endpoint = "https://api.cloudns.net"
1718
content_type = TYPE_FORM
19+
DEFAULT_TTL = 60 # Minimum TTL (60 seconds) for faster updates
1820

1921
def _request(self, action, **params):
22+
# type: (str, **(str | int | None)) -> dict | None
2023
"""
2124
发送请求数据,自动添加认证参数
2225
23-
Send request to ClouDNS API.
26+
Send request to ClouDNS API with authentication.
27+
28+
Args:
29+
action (str): API endpoint path
30+
params: API parameters
31+
32+
Returns:
33+
dict|None: Response data or None on error
2434
"""
2535
# Add authentication
2636
params["auth-id"] = self.id
@@ -31,126 +41,155 @@ def _request(self, action, **params):
3141

3242
data = self._http("POST", action, body=params)
3343

34-
# CloudNS API returns different formats:
35-
# - Success for modifications: {"status": "Success", ...}
44+
# ClouDNS API returns different formats:
45+
# - Success: {"status": "Success", ...}
3646
# - Failure: {"status": "Failed", "statusDescription": "..."}
37-
# - Query success: raw data (e.g., {"id1": {...}, "id2": {...}}) without status field
47+
# - Query: raw data (e.g., {"id1": {...}, "id2": {...}}) without status field
3848

3949
# Check for explicit error status
40-
if data and isinstance(data, dict):
41-
if data.get("status") == "Failed":
42-
error_msg = data.get("statusDescription", "Unknown error")
43-
self.logger.warning("ClouDNS API error: %s", error_msg)
44-
return None
45-
# If status is "Success" or no status field (raw data), return as-is
46-
return data
50+
if data and isinstance(data, dict) and data.get("status") == "Failed":
51+
error_msg = data.get("statusDescription", "Unknown error")
52+
self.logger.warning("ClouDNS API error: %s", error_msg)
53+
return None
4754

4855
return data
4956

5057
def _query_zone_id(self, domain):
58+
# type: (str) -> str | None
5159
"""
52-
ClouDNS uses domain name directly as zone identifier
60+
查询域名区域ID
61+
62+
Query zone ID for domain.
63+
ClouDNS uses domain name directly as zone identifier.
64+
65+
Args:
66+
domain (str): Domain name
67+
68+
Returns:
69+
str: Domain name (used as zone ID)
5370
"""
5471
# ClouDNS uses domain-name directly, no numeric zone ID
55-
# Return the domain itself as the zone identifier
5672
return domain
5773

5874
def _query_record(self, zone_id, subdomain, main_domain, record_type, line, extra):
75+
# type: (str, str, str, str, str | None, dict | None) -> dict | None
5976
"""
60-
Query DNS records
77+
查询DNS记录
78+
79+
Query DNS record.
6180
https://www.cloudns.net/wiki/article/57/
62-
"""
63-
# For @ (root) records, ClouDNS uses empty string or "@"
64-
host = subdomain if subdomain != "@" else ""
6581
66-
params = {
67-
"domain-name": zone_id,
68-
"host": host,
69-
"type": record_type
70-
}
82+
Args:
83+
zone_id (str): Zone ID (domain name for ClouDNS)
84+
subdomain (str): Subdomain
85+
main_domain (str): Main domain
86+
record_type (str): Record type (A, AAAA, etc.)
87+
line (str|None): Line (not used by ClouDNS)
88+
extra (dict|None): Extra parameters
89+
90+
Returns:
91+
dict|None: Record data or None if not found
92+
"""
93+
# For @ (root) records, ClouDNS uses empty string
94+
host = "" if subdomain == "@" else subdomain
7195

96+
params = {"domain-name": zone_id, "host": host, "type": record_type}
7297
data = self._request("/dns/records.json", **params)
7398

74-
if data and isinstance(data, dict):
75-
# CloudNS returns records as a dict with record IDs as keys
76-
# Format: {"id1": {record_data}, "id2": {record_data}}
77-
# Or error format: {"status": "Failed", ...}
78-
79-
# Check if it's an error response
80-
if "status" in data:
81-
return None
82-
83-
# Iterate through records (values of the dict)
84-
for record in data.values():
85-
# Match host - empty string means root
86-
record_host = record.get("host", "")
87-
if (record_host == host or
88-
(record_host == "@" and subdomain == "@") or
89-
(record_host == "" and subdomain == "@")):
90-
if record.get("type") == record_type:
91-
return record
99+
if not data or not isinstance(data, dict):
100+
return None
101+
102+
# Check if it's an error response (has status field)
103+
if "status" in data:
104+
return None
105+
106+
# ClouDNS returns records as dict: {"id1": {record_data}, "id2": {record_data}}
107+
# Find matching record
108+
for record in data.values():
109+
record_host = record.get("host", "")
110+
# Match host (handle both "" and "@" for root records)
111+
if record_host == host or (subdomain == "@" and record_host in ("", "@")):
112+
if record.get("type") == record_type:
113+
return record
92114

93115
return None
94116

95117
def _create_record(self, zone_id, subdomain, main_domain, value, record_type, ttl, line, extra):
118+
# type: (str, str, str, str, str, int | None, str | None, dict | None) -> bool
96119
"""
97-
Create new DNS record
120+
创建DNS记录
121+
122+
Create new DNS record.
98123
https://www.cloudns.net/wiki/article/58/
124+
125+
Args:
126+
zone_id (str): Zone ID (domain name)
127+
subdomain (str): Subdomain
128+
main_domain (str): Main domain
129+
value (str): Record value (IP address)
130+
record_type (str): Record type (A, AAAA, etc.)
131+
ttl (int|None): TTL in seconds
132+
line (str|None): Line (not used by ClouDNS)
133+
extra (dict|None): Extra parameters
134+
135+
Returns:
136+
bool: True if successful, False otherwise
99137
"""
100-
# Default TTL if not specified
138+
# Use default TTL if not specified
101139
if ttl is None:
102-
ttl = 3600 # 1 hour default
103-
104-
# For @ (root) records
105-
host = subdomain if subdomain != "@" else ""
140+
ttl = self.DEFAULT_TTL
106141

107-
params = {
108-
"domain-name": zone_id,
109-
"record-type": record_type,
110-
"host": host,
111-
"record": value,
112-
"ttl": ttl
113-
}
142+
# For @ (root) records, use empty string
143+
host = "" if subdomain == "@" else subdomain
114144

145+
params = {"domain-name": zone_id, "record-type": record_type, "host": host, "record": value, "ttl": ttl}
115146
data = self._request("/dns/add-record.json", **params)
116147

117148
if data and data.get("status") == "Success":
118149
self.logger.info("Record created successfully")
119150
return True
120-
else:
121-
self.logger.error("Failed to create record: %s", data)
122-
return False
151+
152+
self.logger.error("Failed to create record: %s", data)
153+
return False
123154

124155
def _update_record(self, zone_id, old_record, value, record_type, ttl, line, extra):
156+
# type: (str, dict, str, str, int | None, str | None, dict | None) -> bool
125157
"""
126-
Update existing DNS record
158+
更新DNS记录
159+
160+
Update existing DNS record.
127161
https://www.cloudns.net/wiki/article/60/
162+
163+
Args:
164+
zone_id (str): Zone ID (domain name)
165+
old_record (dict): Existing record data
166+
value (str): New record value (IP address)
167+
record_type (str): Record type (A, AAAA, etc.)
168+
ttl (int|None): TTL in seconds
169+
line (str|None): Line (not used by ClouDNS)
170+
extra (dict|None): Extra parameters
171+
172+
Returns:
173+
bool: True if successful, False otherwise
128174
"""
129-
# Default TTL if not specified
175+
# Use default TTL if not specified
130176
if ttl is None:
131-
ttl = 3600
177+
ttl = self.DEFAULT_TTL
132178

133179
record_id = old_record.get("id")
134180
if not record_id:
135181
self.logger.error("Record ID not found in old_record")
136182
return False
137183

138-
# Get original host
184+
# Get original host from record
139185
host = old_record.get("host", "")
140186

141-
params = {
142-
"domain-name": zone_id,
143-
"record-id": record_id,
144-
"host": host,
145-
"record": value,
146-
"ttl": ttl
147-
}
148-
187+
params = {"domain-name": zone_id, "record-id": record_id, "host": host, "record": value, "ttl": ttl}
149188
data = self._request("/dns/mod-record.json", **params)
150189

151190
if data and data.get("status") == "Success":
152191
self.logger.info("Record updated successfully")
153192
return True
154-
else:
155-
self.logger.error("Failed to update record: %s", data)
156-
return False
193+
194+
self.logger.error("Failed to update record: %s", data)
195+
return False

docs/en/providers/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This directory contains detailed configuration guides for various DNS providers.
1010
| `aliesa` | [Alibaba Cloud ESA](https://esa.console.aliyun.com/) | [aliesa 中文文档](../../providers/aliesa.md) | [aliesa English Doc](aliesa.md) | Alibaba Cloud Edge Security Acceleration |
1111
| `callback` | Custom API (Webhook) | [callback 中文文档](../../providers/callback.md) | [callback English Doc](callback.md) | Custom HTTP API |
1212
| `cloudflare` | [Cloudflare](https://www.cloudflare.com/) | [cloudflare 中文文档](../../providers/cloudflare.md) | [cloudflare English Doc](cloudflare.md) | Global CDN and DNS service |
13+
| `cloudns` | [ClouDNS](https://www.cloudns.net/) | [cloudns 中文文档](../../providers/cloudns.md) | [cloudns English Doc](cloudns.md) | Global DNS hosting service |
1314
| `debug` | Debug Provider | [debug 中文文档](../../providers/debug.md) | [debug English Doc](debug.md) | IP address printing for debugging |
1415
| `dnscom` | [DNS.COM](https://www.dns.com/) | [51dns 中文文档](../../providers/51dns.md) | [51DNS English Doc](51dns.md) | ⚠️ Pending verification |
1516
| `dnspod_com` | [DNSPod Global](https://www.dnspod.com/) | [dnspod_com 中文文档](../../providers/dnspod_com.md) | [dnspod_com English Doc](dnspod_com.md) | ⚠️ Pending verification |

0 commit comments

Comments
 (0)