ddns 4.0.1__py2.py3-none-any.whl → 4.1.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ddns might be problematic. Click here for more details.
- ddns/__builtins__.pyi +6 -0
- ddns/__init__.py +2 -9
- ddns/__main__.py +108 -144
- ddns/cache.py +183 -0
- ddns/ip.py +145 -0
- ddns/provider/__init__.py +79 -0
- ddns/provider/_base.py +526 -0
- ddns/provider/_signature.py +113 -0
- ddns/provider/alidns.py +145 -176
- ddns/provider/aliesa.py +130 -0
- ddns/provider/callback.py +66 -104
- ddns/provider/cloudflare.py +87 -151
- ddns/provider/debug.py +19 -0
- ddns/provider/dnscom.py +91 -168
- ddns/provider/dnspod.py +102 -174
- ddns/provider/dnspod_com.py +11 -8
- ddns/provider/edgeone.py +83 -0
- ddns/provider/he.py +41 -78
- ddns/provider/huaweidns.py +134 -256
- ddns/provider/namesilo.py +159 -0
- ddns/provider/noip.py +101 -0
- ddns/provider/tencentcloud.py +194 -0
- ddns/util/comment.py +88 -0
- ddns/util/fileio.py +113 -0
- ddns/util/http.py +322 -0
- ddns/util/try_run.py +37 -0
- ddns-4.1.0.dist-info/METADATA +327 -0
- ddns-4.1.0.dist-info/RECORD +33 -0
- ddns/util/cache.py +0 -139
- ddns/util/config.py +0 -317
- ddns/util/ip.py +0 -96
- ddns-4.0.1.dist-info/METADATA +0 -326
- ddns-4.0.1.dist-info/RECORD +0 -21
- {ddns-4.0.1.dist-info → ddns-4.1.0.dist-info}/WHEEL +0 -0
- {ddns-4.0.1.dist-info → ddns-4.1.0.dist-info}/entry_points.txt +0 -0
- {ddns-4.0.1.dist-info → ddns-4.1.0.dist-info}/licenses/LICENSE +0 -0
- {ddns-4.0.1.dist-info → ddns-4.1.0.dist-info}/top_level.txt +0 -0
ddns/provider/callback.py
CHANGED
|
@@ -3,115 +3,77 @@
|
|
|
3
3
|
Custom Callback API
|
|
4
4
|
自定义回调接口解析操作库
|
|
5
5
|
|
|
6
|
-
@author:
|
|
6
|
+
@author: 老周部落, NewFuture
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
from logging import debug, info, warning
|
|
9
|
+
from ._base import TYPE_JSON, SimpleProvider
|
|
11
10
|
from time import time
|
|
12
|
-
|
|
13
|
-
try: # python 3
|
|
14
|
-
from http.client import HTTPSConnection, HTTPConnection
|
|
15
|
-
from urllib.parse import urlencode, urlparse, parse_qsl
|
|
16
|
-
except ImportError: # python 2
|
|
17
|
-
from httplib import HTTPSConnection, HTTPConnection
|
|
18
|
-
from urlparse import urlparse, parse_qsl
|
|
19
|
-
from urllib import urlencode
|
|
20
|
-
|
|
21
|
-
__author__ = '老周部落'
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class Config:
|
|
25
|
-
ID = None # 自定义回调 URL
|
|
26
|
-
TOKEN = None # 使用 JSON 编码的 POST 参数
|
|
27
|
-
PROXY = None # 代理设置
|
|
28
|
-
TTL = None
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def request(method, action, param=None, **params):
|
|
32
|
-
"""
|
|
33
|
-
发送请求数据
|
|
34
|
-
"""
|
|
35
|
-
if param:
|
|
36
|
-
params.update(param)
|
|
37
|
-
|
|
38
|
-
URLObj = urlparse(Config.ID)
|
|
39
|
-
params = dict((k, params[k]) for k in params if params[k] is not None)
|
|
40
|
-
info("%s/%s : %s", URLObj.netloc, action, params)
|
|
41
|
-
|
|
42
|
-
if Config.PROXY:
|
|
43
|
-
if URLObj.netloc == "http":
|
|
44
|
-
conn = HTTPConnection(Config.PROXY)
|
|
45
|
-
else:
|
|
46
|
-
conn = HTTPSConnection(Config.PROXY)
|
|
47
|
-
conn.set_tunnel(URLObj.netloc, URLObj.port)
|
|
48
|
-
else:
|
|
49
|
-
if URLObj.netloc == "http":
|
|
50
|
-
conn = HTTPConnection(URLObj.netloc, URLObj.port)
|
|
51
|
-
else:
|
|
52
|
-
conn = HTTPSConnection(URLObj.netloc, URLObj.port)
|
|
53
|
-
|
|
54
|
-
headers = {}
|
|
55
|
-
|
|
56
|
-
if method == "GET":
|
|
57
|
-
if params:
|
|
58
|
-
action += '?' + urlencode(params)
|
|
59
|
-
params = ""
|
|
60
|
-
else:
|
|
61
|
-
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
62
|
-
|
|
63
|
-
params = urlencode(params)
|
|
64
|
-
|
|
65
|
-
conn.request(method, action, params, headers)
|
|
66
|
-
response = conn.getresponse()
|
|
67
|
-
res = response.read().decode('utf8')
|
|
68
|
-
conn.close()
|
|
69
|
-
if response.status < 200 or response.status >= 300:
|
|
70
|
-
warning('%s : error[%d]:%s', action, response.status, res)
|
|
71
|
-
raise Exception(res)
|
|
72
|
-
else:
|
|
73
|
-
debug('%s : result:%s', action, res)
|
|
74
|
-
return res
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def replace_params(domain, record_type, ip, params):
|
|
78
|
-
"""
|
|
79
|
-
替换定义常量为实际值
|
|
80
|
-
"""
|
|
81
|
-
dict = {"__DOMAIN__": domain, "__RECORDTYPE__": record_type,
|
|
82
|
-
"__TTL__": Config.TTL, "__TIMESTAMP__": time(), "__IP__": ip}
|
|
83
|
-
for key, value in params.items():
|
|
84
|
-
if dict.get(value):
|
|
85
|
-
params[key] = dict.get(value)
|
|
86
|
-
return params
|
|
11
|
+
from json import loads as jsondecode
|
|
87
12
|
|
|
88
13
|
|
|
89
|
-
|
|
14
|
+
class CallbackProvider(SimpleProvider):
|
|
90
15
|
"""
|
|
91
|
-
|
|
16
|
+
通用自定义回调 Provider,支持 GET/POST 任意接口。
|
|
17
|
+
Generic custom callback provider, supports GET/POST arbitrary API.
|
|
92
18
|
"""
|
|
93
|
-
info(">>>>>%s(%s)", domain, record_type)
|
|
94
|
-
|
|
95
|
-
result = {}
|
|
96
|
-
|
|
97
|
-
if not Config.TOKEN: # 此处使用 TOKEN 参数透传 POST 参数所用的 JSON
|
|
98
|
-
method = "GET"
|
|
99
|
-
URLObj = urlparse(Config.ID)
|
|
100
|
-
path = URLObj.path
|
|
101
|
-
query = dict(parse_qsl(URLObj.query))
|
|
102
|
-
params = replace_params(domain, record_type, value, query)
|
|
103
|
-
else:
|
|
104
|
-
method = "POST"
|
|
105
|
-
URLObj = urlparse(Config.ID)
|
|
106
|
-
path = URLObj.path
|
|
107
|
-
params = replace_params(domain, record_type,
|
|
108
|
-
value, jsondecode(Config.TOKEN))
|
|
109
|
-
|
|
110
|
-
res = request(method, path, params)
|
|
111
|
-
|
|
112
|
-
if res:
|
|
113
|
-
result = "Callback Request Success!\n" + res
|
|
114
|
-
else:
|
|
115
|
-
result = "Callback Request Fail!\n"
|
|
116
19
|
|
|
117
|
-
|
|
20
|
+
endpoint = "" # CallbackProvider uses id as URL, no fixed API endpoint
|
|
21
|
+
content_type = TYPE_JSON
|
|
22
|
+
decode_response = False # Callback response is not JSON, it's a custom response
|
|
23
|
+
|
|
24
|
+
def set_record(self, domain, value, record_type="A", ttl=None, line=None, **extra):
|
|
25
|
+
"""
|
|
26
|
+
发送自定义回调请求,支持 GET/POST
|
|
27
|
+
Send custom callback request, support GET/POST
|
|
28
|
+
"""
|
|
29
|
+
self.logger.info("%s => %s(%s)", domain, value, record_type)
|
|
30
|
+
url = self.id # 直接用 id 作为 url
|
|
31
|
+
token = self.token # token 作为 POST 参数
|
|
32
|
+
extra.update(
|
|
33
|
+
{
|
|
34
|
+
"__DOMAIN__": domain,
|
|
35
|
+
"__RECORDTYPE__": record_type,
|
|
36
|
+
"__TTL__": ttl,
|
|
37
|
+
"__IP__": value,
|
|
38
|
+
"__TIMESTAMP__": time(),
|
|
39
|
+
"__LINE__": line,
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
url = self._replace_vars(url, extra)
|
|
43
|
+
method, params = "GET", None
|
|
44
|
+
if token:
|
|
45
|
+
# 如果有 token,使用 POST 方法
|
|
46
|
+
method = "POST"
|
|
47
|
+
# POST 方式,token 作为 POST 参数
|
|
48
|
+
params = token if isinstance(token, dict) else jsondecode(token)
|
|
49
|
+
for k, v in params.items():
|
|
50
|
+
if hasattr(v, "replace"): # 判断是否支持字符串替换, 兼容py2,py3
|
|
51
|
+
params[k] = self._replace_vars(v, extra)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
res = self._http(method, url, body=params)
|
|
55
|
+
if res is not None:
|
|
56
|
+
self.logger.info("Callback result: %s", res)
|
|
57
|
+
return True
|
|
58
|
+
else:
|
|
59
|
+
self.logger.warning("Callback received empty response.")
|
|
60
|
+
except Exception as e:
|
|
61
|
+
self.logger.error("Callback failed: %s", e)
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
def _replace_vars(self, string, mapping):
|
|
65
|
+
# type: (str, dict) -> str
|
|
66
|
+
"""
|
|
67
|
+
替换字符串中的变量为实际值
|
|
68
|
+
Replace variables in string with actual values
|
|
69
|
+
"""
|
|
70
|
+
for k, v in mapping.items():
|
|
71
|
+
string = string.replace(k, str(v))
|
|
72
|
+
return string
|
|
73
|
+
|
|
74
|
+
def _validate(self):
|
|
75
|
+
# CallbackProvider uses id as URL, not as regular ID
|
|
76
|
+
if self.endpoint or (not self.id or "://" not in self.id):
|
|
77
|
+
# 如果 endpoint 已经设置,或者 id 不是有效的 URL,则抛出异常
|
|
78
|
+
self.logger.critical("endpoint [%s] or id [%s] 必须是有效的URL", self.endpoint, self.id)
|
|
79
|
+
raise ValueError("endpoint or id must be configured with URL")
|
ddns/provider/cloudflare.py
CHANGED
|
@@ -1,163 +1,99 @@
|
|
|
1
1
|
# coding=utf-8
|
|
2
2
|
"""
|
|
3
3
|
CloudFlare API
|
|
4
|
-
|
|
5
|
-
https://api.cloudflare.com/#dns-records-for-a-zone-properties
|
|
6
|
-
@author: TongYifan
|
|
4
|
+
@author: TongYifan, NewFuture
|
|
7
5
|
"""
|
|
8
6
|
|
|
9
|
-
from
|
|
10
|
-
from logging import debug, info, warning
|
|
7
|
+
from ._base import TYPE_JSON, BaseProvider, join_domain
|
|
11
8
|
|
|
12
|
-
try: # python 3
|
|
13
|
-
from http.client import HTTPSConnection
|
|
14
|
-
from urllib.parse import urlencode
|
|
15
|
-
except ImportError: # python 2
|
|
16
|
-
from httplib import HTTPSConnection
|
|
17
|
-
from urllib import urlencode
|
|
18
9
|
|
|
19
|
-
|
|
10
|
+
class CloudflareProvider(BaseProvider):
|
|
11
|
+
endpoint = "https://api.cloudflare.com"
|
|
12
|
+
content_type = TYPE_JSON
|
|
20
13
|
|
|
14
|
+
def _validate(self):
|
|
15
|
+
if not self.token:
|
|
16
|
+
raise ValueError("token must be configured")
|
|
17
|
+
if self.id:
|
|
18
|
+
# must be email for Cloudflare API v4
|
|
19
|
+
if "@" not in self.id:
|
|
20
|
+
self.logger.critical("ID 必须为空或有效的邮箱地址")
|
|
21
|
+
raise ValueError("ID must be a valid email or Empty for Cloudflare API v4")
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class API:
|
|
30
|
-
# API 配置
|
|
31
|
-
SITE = "api.cloudflare.com" # API endpoint
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def request(method, action, param=None, **params):
|
|
35
|
-
"""
|
|
36
|
-
发送请求数据
|
|
37
|
-
"""
|
|
38
|
-
if param:
|
|
39
|
-
params.update(param)
|
|
40
|
-
|
|
41
|
-
params = dict((k, params[k]) for k in params if params[k] is not None)
|
|
42
|
-
info("%s/%s : %s", API.SITE, action, params)
|
|
43
|
-
if Config.PROXY:
|
|
44
|
-
conn = HTTPSConnection(Config.PROXY)
|
|
45
|
-
conn.set_tunnel(API.SITE, 443)
|
|
46
|
-
else:
|
|
47
|
-
conn = HTTPSConnection(API.SITE)
|
|
48
|
-
|
|
49
|
-
if method in ['PUT', 'POST', 'PATCH']:
|
|
50
|
-
# 从public_v(4,6)获取的IP是bytes类型,在json.dumps时会报TypeError
|
|
51
|
-
params['content'] = str(params.get('content'))
|
|
52
|
-
params = jsonencode(params)
|
|
53
|
-
else: # (GET, DELETE) where DELETE doesn't require params in Cloudflare
|
|
54
|
-
if params:
|
|
55
|
-
action += '?' + urlencode(params)
|
|
56
|
-
params = None
|
|
57
|
-
if not Config.ID:
|
|
58
|
-
headers = {"Content-type": "application/json",
|
|
59
|
-
"Authorization": "Bearer " + Config.TOKEN}
|
|
60
|
-
else:
|
|
61
|
-
headers = {"Content-type": "application/json",
|
|
62
|
-
"X-Auth-Email": Config.ID, "X-Auth-Key": Config.TOKEN}
|
|
63
|
-
conn.request(method, '/client/v4/zones' + action, params, headers)
|
|
64
|
-
response = conn.getresponse()
|
|
65
|
-
res = response.read().decode('utf8')
|
|
66
|
-
conn.close()
|
|
67
|
-
if response.status < 200 or response.status >= 300:
|
|
68
|
-
warning('%s : error[%d]:%s', action, response.status, res)
|
|
69
|
-
raise Exception(res)
|
|
70
|
-
else:
|
|
71
|
-
data = jsondecode(res)
|
|
72
|
-
debug('%s : result:%s', action, data)
|
|
73
|
-
if not data:
|
|
74
|
-
raise Exception("Empty Response")
|
|
75
|
-
elif data.get('success'):
|
|
76
|
-
return data.get('result', [{}])
|
|
23
|
+
def _request(self, method, action, **params):
|
|
24
|
+
"""发送请求数据"""
|
|
25
|
+
headers = {}
|
|
26
|
+
if self.id:
|
|
27
|
+
headers["X-Auth-Email"] = self.id
|
|
28
|
+
headers["X-Auth-Key"] = self.token
|
|
77
29
|
else:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def get_zone_id(domain):
|
|
82
|
-
"""
|
|
83
|
-
切割域名获取主域名ID(Zone_ID)
|
|
84
|
-
https://api.cloudflare.com/#zone-list-zones
|
|
85
|
-
"""
|
|
86
|
-
zoneid = None
|
|
87
|
-
domain_slice = domain.split('.')
|
|
88
|
-
index = 2
|
|
89
|
-
# ddns.example.com => example.com; ddns.example.eu.org => example.eu.org
|
|
90
|
-
while (not zoneid) and (index <= len(domain_slice)):
|
|
91
|
-
zones = request('GET', '', name='.'.join(domain_slice[-index:]))
|
|
92
|
-
zone = next((z for z in zones if domain.endswith(z.get('name'))), None)
|
|
93
|
-
zoneid = zone and zone['id']
|
|
94
|
-
index += 1
|
|
95
|
-
return zoneid
|
|
96
|
-
|
|
30
|
+
headers["Authorization"] = "Bearer " + self.token
|
|
97
31
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
TODO 大于100翻页
|
|
103
|
-
"""
|
|
104
|
-
cache_key = zoneid + "_" + \
|
|
105
|
-
conditions.get('name', "") + "_" + conditions.get('type', "")
|
|
106
|
-
if not hasattr(get_records, 'records'):
|
|
107
|
-
get_records.records = {} # "静态变量"存储已查询过的id
|
|
108
|
-
get_records.keys = ('id', 'type', 'name', 'content', 'proxied', 'ttl')
|
|
109
|
-
|
|
110
|
-
if zoneid not in get_records.records:
|
|
111
|
-
get_records.records[cache_key] = {}
|
|
112
|
-
data = request('GET', '/' + zoneid + '/dns_records',
|
|
113
|
-
per_page=100, **conditions)
|
|
114
|
-
if data:
|
|
115
|
-
for record in data:
|
|
116
|
-
get_records.records[cache_key][record['id']] = {
|
|
117
|
-
k: v for (k, v) in record.items() if k in get_records.keys}
|
|
118
|
-
|
|
119
|
-
records = {}
|
|
120
|
-
for (zid, record) in get_records.records[cache_key].items():
|
|
121
|
-
for (k, value) in conditions.items():
|
|
122
|
-
if record.get(k) != value:
|
|
123
|
-
break
|
|
124
|
-
else: # for else push
|
|
125
|
-
records[zid] = record
|
|
126
|
-
return records
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def update_record(domain, value, record_type="A"):
|
|
130
|
-
"""
|
|
131
|
-
更新记录
|
|
132
|
-
"""
|
|
133
|
-
info(">>>>>%s(%s)", domain, record_type)
|
|
134
|
-
zoneid = get_zone_id(domain)
|
|
135
|
-
if not zoneid:
|
|
136
|
-
raise Exception("invalid domain: [ %s ] " % domain)
|
|
137
|
-
|
|
138
|
-
records = get_records(zoneid, name=domain, type=record_type)
|
|
139
|
-
cache_key = zoneid + "_" + domain + "_" + record_type
|
|
140
|
-
result = {}
|
|
141
|
-
if records: # update
|
|
142
|
-
# https://api.cloudflare.com/#dns-records-for-a-zone-update-dns-record
|
|
143
|
-
for (rid, record) in records.items():
|
|
144
|
-
if record['content'] != value:
|
|
145
|
-
res = request('PUT', '/' + zoneid + '/dns_records/' + record['id'],
|
|
146
|
-
type=record_type, content=value, name=domain, proxied=record['proxied'], ttl=Config.TTL)
|
|
147
|
-
if res:
|
|
148
|
-
get_records.records[cache_key][rid]['content'] = value
|
|
149
|
-
result[rid] = res.get("name")
|
|
150
|
-
else:
|
|
151
|
-
result[rid] = "Update fail!\n" + str(res)
|
|
152
|
-
else:
|
|
153
|
-
result[rid] = domain
|
|
154
|
-
else: # create
|
|
155
|
-
# https://api.cloudflare.com/#dns-records-for-a-zone-create-dns-record
|
|
156
|
-
res = request('POST', '/' + zoneid + '/dns_records',
|
|
157
|
-
type=record_type, name=domain, content=value, proxied=False, ttl=Config.TTL)
|
|
158
|
-
if res:
|
|
159
|
-
get_records.records[cache_key][res['id']] = res
|
|
160
|
-
result = res
|
|
32
|
+
params = {k: v for k, v in params.items() if v is not None} # 过滤掉None参数
|
|
33
|
+
data = self._http(method, "/client/v4/zones" + action, headers=headers, params=params)
|
|
34
|
+
if data and data.get("success"):
|
|
35
|
+
return data.get("result") # 返回结果或原始数据
|
|
161
36
|
else:
|
|
162
|
-
|
|
163
|
-
|
|
37
|
+
self.logger.warning("Cloudflare API error: %s", data.get("errors", "Unknown error"))
|
|
38
|
+
return data
|
|
39
|
+
|
|
40
|
+
def _query_zone_id(self, domain):
|
|
41
|
+
"""https://developers.cloudflare.com/api/resources/zones/methods/list/"""
|
|
42
|
+
params = {"name.exact": domain, "per_page": 50}
|
|
43
|
+
zones = self._request("GET", "", **params)
|
|
44
|
+
zone = next((z for z in zones if domain == z.get("name", "")), None)
|
|
45
|
+
self.logger.debug("Queried zone: %s", zone)
|
|
46
|
+
if zone:
|
|
47
|
+
return zone["id"]
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def _query_record(self, zone_id, subdomain, main_domain, record_type, line, extra):
|
|
51
|
+
# type: (str, str, str, str, str | None, dict) -> dict | None
|
|
52
|
+
"""https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/list/"""
|
|
53
|
+
# cloudflare的域名查询需要完整域名
|
|
54
|
+
name = join_domain(subdomain, main_domain)
|
|
55
|
+
query = {"name.exact": name} # type: dict[str, str|None]
|
|
56
|
+
if extra:
|
|
57
|
+
query["proxied"] = extra.get("proxied", None) # 代理状态
|
|
58
|
+
data = self._request("GET", "/{}/dns_records".format(zone_id), type=record_type, per_page=10000, **query)
|
|
59
|
+
record = next((r for r in data if r.get("name") == name and r.get("type") == record_type), None)
|
|
60
|
+
self.logger.debug("Record queried: %s", record)
|
|
61
|
+
if record:
|
|
62
|
+
return record
|
|
63
|
+
self.logger.warning("Failed to query record: %s", data)
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def _create_record(self, zone_id, subdomain, main_domain, value, record_type, ttl, line, extra):
|
|
67
|
+
# type: (str, str, str, str, str, int | str | None, str | None, dict ) -> bool
|
|
68
|
+
"""https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/create/"""
|
|
69
|
+
name = join_domain(subdomain, main_domain)
|
|
70
|
+
extra["comment"] = extra.get("comment", self.remark) # 添加注释
|
|
71
|
+
data = self._request(
|
|
72
|
+
"POST", "/{}/dns_records".format(zone_id), name=name, type=record_type, content=value, ttl=ttl, **extra
|
|
73
|
+
)
|
|
74
|
+
if data:
|
|
75
|
+
self.logger.info("Record created: %s", data)
|
|
76
|
+
return True
|
|
77
|
+
self.logger.error("Failed to create record: %s", data)
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
def _update_record(self, zone_id, old_record, value, record_type, ttl, line, extra):
|
|
81
|
+
# type: (str, dict, str, str, int | str | None, str | None, dict) -> bool
|
|
82
|
+
"""https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/edit/"""
|
|
83
|
+
extra["comment"] = extra.get("comment", self.remark) # 注释
|
|
84
|
+
extra["proxied"] = old_record.get("proxied", extra.get("proxied")) # 保持原有的代理状态
|
|
85
|
+
extra["tags"] = old_record.get("tags", extra.get("tags")) # 保持原有的标签
|
|
86
|
+
extra["settings"] = old_record.get("settings", extra.get("settings")) # 保持原有的设置
|
|
87
|
+
data = self._request(
|
|
88
|
+
"PUT",
|
|
89
|
+
"/{}/dns_records/{}".format(zone_id, old_record["id"]),
|
|
90
|
+
type=record_type,
|
|
91
|
+
name=old_record.get("name"),
|
|
92
|
+
content=value,
|
|
93
|
+
ttl=ttl,
|
|
94
|
+
**extra
|
|
95
|
+
) # fmt: skip
|
|
96
|
+
self.logger.debug("Record updated: %s", data)
|
|
97
|
+
if data:
|
|
98
|
+
return True
|
|
99
|
+
return False
|
ddns/provider/debug.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
DebugProvider
|
|
4
|
+
仅打印出 IP 地址,不进行任何实际 DNS 更新。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ._base import SimpleProvider
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DebugProvider(SimpleProvider):
|
|
11
|
+
def _validate(self):
|
|
12
|
+
"""无需任何验证"""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
def set_record(self, domain, value, record_type="A", ttl=None, line=None, **extra):
|
|
16
|
+
self.logger.debug("DebugProvider: %s(%s) => %s", domain, record_type, value)
|
|
17
|
+
ip_type = "IPv4" if record_type == "A" else "IPv6" if record_type == "AAAA" else record_type
|
|
18
|
+
print("[{}] {}".format(ip_type, value))
|
|
19
|
+
return True
|