ddns 4.0.2__py2.py3-none-any.whl → 4.1.0b2__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.
- ddns/__builtins__.pyi +5 -0
- ddns/__init__.py +2 -9
- ddns/__main__.py +77 -146
- ddns/cache.py +183 -0
- ddns/{util/ip.py → ip.py} +26 -21
- ddns/provider/__init__.py +79 -0
- ddns/provider/_base.py +539 -0
- ddns/provider/_signature.py +113 -0
- ddns/provider/alidns.py +139 -171
- ddns/provider/aliesa.py +129 -0
- ddns/provider/callback.py +66 -105
- ddns/provider/cloudflare.py +87 -151
- ddns/provider/debug.py +20 -0
- ddns/provider/dnscom.py +91 -169
- ddns/provider/dnspod.py +104 -174
- ddns/provider/dnspod_com.py +11 -8
- ddns/provider/edgeone.py +82 -0
- ddns/provider/he.py +41 -78
- ddns/provider/huaweidns.py +133 -256
- ddns/provider/namesilo.py +159 -0
- ddns/provider/noip.py +103 -0
- ddns/provider/tencentcloud.py +195 -0
- ddns/util/comment.py +88 -0
- ddns/util/http.py +228 -0
- {ddns-4.0.2.dist-info → ddns-4.1.0b2.dist-info}/METADATA +109 -75
- ddns-4.1.0b2.dist-info/RECORD +31 -0
- ddns/util/cache.py +0 -139
- ddns/util/config.py +0 -317
- ddns-4.0.2.dist-info/RECORD +0 -21
- {ddns-4.0.2.dist-info → ddns-4.1.0b2.dist-info}/WHEEL +0 -0
- {ddns-4.0.2.dist-info → ddns-4.1.0b2.dist-info}/entry_points.txt +0 -0
- {ddns-4.0.2.dist-info → ddns-4.1.0b2.dist-info}/licenses/LICENSE +0 -0
- {ddns-4.0.2.dist-info → ddns-4.1.0b2.dist-info}/top_level.txt +0 -0
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 BaseProvider, TYPE_JSON, 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
|
+
)
|
|
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,20 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
DebugProvider
|
|
4
|
+
仅打印出 IP 地址,不进行任何实际 DNS 更新。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ._base import SimpleProvider
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DebugProvider(SimpleProvider):
|
|
11
|
+
|
|
12
|
+
def _validate(self):
|
|
13
|
+
"""无需任何验证"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
def set_record(self, domain, value, record_type="A", ttl=None, line=None, **extra):
|
|
17
|
+
self.logger.debug("DebugProvider: %s(%s) => %s", domain, record_type, value)
|
|
18
|
+
ip_type = "IPv4" if record_type == "A" else "IPv6" if record_type == "AAAA" else record_type
|
|
19
|
+
print("[{}] {}".format(ip_type, value))
|
|
20
|
+
return True
|
ddns/provider/dnscom.py
CHANGED
|
@@ -1,180 +1,102 @@
|
|
|
1
1
|
# coding=utf-8
|
|
2
2
|
"""
|
|
3
|
-
DNSCOM API
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@author: Bigjin
|
|
7
|
-
@mailto: i@bigjin.com
|
|
3
|
+
DNSCOM/51dns API 接口解析操作库
|
|
4
|
+
www.51dns.com (原dns.com)
|
|
5
|
+
@author: Bigjin<i@bigjin.com>, NewFuture
|
|
8
6
|
"""
|
|
9
7
|
|
|
8
|
+
from ._base import BaseProvider, TYPE_FORM, encode_params
|
|
10
9
|
from hashlib import md5
|
|
11
|
-
from
|
|
12
|
-
from logging import debug, info, warning
|
|
13
|
-
from time import mktime
|
|
14
|
-
from datetime import datetime
|
|
10
|
+
from time import time
|
|
15
11
|
|
|
16
|
-
try: # python 3
|
|
17
|
-
from http.client import HTTPSConnection
|
|
18
|
-
from urllib.parse import urlencode
|
|
19
|
-
except ImportError: # python 2
|
|
20
|
-
from httplib import HTTPSConnection
|
|
21
|
-
from urllib import urlencode
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
# __all__ = ["request", "ID", "TOKEN", "PROXY"]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class Config:
|
|
28
|
-
ID = "id"
|
|
29
|
-
TOKEN = "TOKEN"
|
|
30
|
-
PROXY = None # 代理设置
|
|
31
|
-
TTL = None
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class API:
|
|
35
|
-
# API 配置
|
|
36
|
-
SITE = "www.dns.com" # API endpoint
|
|
37
|
-
METHOD = "POST" # 请求方法
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def signature(params):
|
|
13
|
+
class DnscomProvider(BaseProvider):
|
|
41
14
|
"""
|
|
42
|
-
|
|
15
|
+
DNSCOM/51dns API Provider
|
|
16
|
+
https://www.51dns.com/document/api/index.html
|
|
43
17
|
"""
|
|
44
|
-
params.update({
|
|
45
|
-
'apiKey': Config.ID,
|
|
46
|
-
'timestamp': mktime(datetime.now().timetuple()),
|
|
47
|
-
})
|
|
48
|
-
query = urlencode(sorted(params.items()))
|
|
49
|
-
debug(query)
|
|
50
|
-
sign = query
|
|
51
|
-
debug("signString: %s", sign)
|
|
52
|
-
|
|
53
|
-
sign = md5((sign + Config.TOKEN).encode('utf-8')).hexdigest()
|
|
54
|
-
params["hash"] = sign
|
|
55
|
-
|
|
56
|
-
return params
|
|
57
|
-
|
|
58
18
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
{"Content-type": "application/x-www-form-urlencoded"})
|
|
77
|
-
response = conn.getresponse()
|
|
78
|
-
result = response.read().decode('utf8')
|
|
79
|
-
conn.close()
|
|
80
|
-
|
|
81
|
-
if response.status < 200 or response.status >= 300:
|
|
82
|
-
warning('%s : error[%d]:%s', action, response.status, result)
|
|
83
|
-
raise Exception(result)
|
|
84
|
-
else:
|
|
85
|
-
data = jsondecode(result)
|
|
86
|
-
debug('%s : result:%s', action, data)
|
|
87
|
-
if data.get('code') != 0:
|
|
88
|
-
raise Exception("api error:", data.get('message'))
|
|
89
|
-
data = data.get('data')
|
|
90
|
-
if data is None:
|
|
91
|
-
raise Exception('response data is none')
|
|
92
|
-
return data
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def get_domain_info(domain):
|
|
96
|
-
"""
|
|
97
|
-
切割域名获取主域名和对应ID
|
|
98
|
-
"""
|
|
99
|
-
if len(domain.split('.')) > 2:
|
|
100
|
-
domains = domain.split('.', 1)
|
|
101
|
-
sub = domains[0]
|
|
102
|
-
main = domains[1]
|
|
103
|
-
else:
|
|
104
|
-
sub = '' # 接口有bug 不能传 @ * 作为主机头,但是如果为空,默认为 @
|
|
105
|
-
main = domain
|
|
106
|
-
|
|
107
|
-
res = request("domain/getsingle", domainID=main)
|
|
108
|
-
domain_id = res.get('domainID')
|
|
109
|
-
return sub, main, domain_id
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def get_records(domain, domain_id, **conditions):
|
|
113
|
-
"""
|
|
114
|
-
获取记录ID
|
|
115
|
-
返回满足条件的所有记录[]
|
|
116
|
-
TODO 大于500翻页
|
|
117
|
-
"""
|
|
118
|
-
if not hasattr(get_records, "records"):
|
|
119
|
-
get_records.records = {} # "静态变量"存储已查询过的id
|
|
120
|
-
get_records.keys = ("recordID", "record", "type", "viewID",
|
|
121
|
-
"TTL", "state", "value")
|
|
122
|
-
|
|
123
|
-
if domain not in get_records.records:
|
|
124
|
-
get_records.records[domain] = {}
|
|
125
|
-
data = request("record/list",
|
|
126
|
-
domainID=domain_id, pageSize=500)
|
|
127
|
-
if data.get('data'):
|
|
128
|
-
for record in data.get('data'):
|
|
129
|
-
get_records.records[domain][record["recordID"]] = {
|
|
130
|
-
k: v for (k, v) in record.items() if k in get_records.keys}
|
|
131
|
-
records = {}
|
|
132
|
-
for (rid, record) in get_records.records[domain].items():
|
|
133
|
-
for (k, value) in conditions.items():
|
|
134
|
-
if record.get(k) != value:
|
|
135
|
-
break
|
|
136
|
-
else: # for else push
|
|
137
|
-
records[rid] = record
|
|
138
|
-
return records
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def update_record(domain, value, record_type='A'):
|
|
142
|
-
"""
|
|
143
|
-
更新记录
|
|
144
|
-
"""
|
|
145
|
-
info(">>>>>%s(%s)", domain, record_type)
|
|
146
|
-
sub, main, domain_id = get_domain_info(domain)
|
|
147
|
-
|
|
148
|
-
records = get_records(main, domain_id, record=sub, type=record_type)
|
|
149
|
-
result = {}
|
|
150
|
-
|
|
151
|
-
if records:
|
|
152
|
-
for (rid, record) in records.items():
|
|
153
|
-
if record["value"] != value:
|
|
154
|
-
debug(sub, record)
|
|
155
|
-
res = request("record/modify", domainID=domain_id,
|
|
156
|
-
recordID=rid, newvalue=value, newTTL=Config.TTL)
|
|
157
|
-
if res:
|
|
158
|
-
# update records
|
|
159
|
-
get_records.records[main][rid]["value"] = value
|
|
160
|
-
result[rid] = res
|
|
161
|
-
else:
|
|
162
|
-
result[rid] = "update fail!\n" + str(res)
|
|
163
|
-
else:
|
|
164
|
-
result[rid] = domain
|
|
165
|
-
else:
|
|
166
|
-
res = request("record/create", domainID=domain_id,
|
|
167
|
-
value=value, host=sub, type=record_type, TTL=Config.TTL)
|
|
168
|
-
if res:
|
|
169
|
-
# update records INFO
|
|
170
|
-
rid = res.get('recordID')
|
|
171
|
-
get_records.records[main][rid] = {
|
|
172
|
-
'value': value,
|
|
173
|
-
"recordID": rid,
|
|
174
|
-
"record": sub,
|
|
175
|
-
"type": record_type
|
|
19
|
+
endpoint = "https://www.51dns.com"
|
|
20
|
+
content_type = TYPE_FORM
|
|
21
|
+
|
|
22
|
+
def _validate(self):
|
|
23
|
+
self.logger.warning(
|
|
24
|
+
"DNS.COM 缺少充分的真实环境测试,请及时在 GitHub Issues 中反馈: %s",
|
|
25
|
+
"https://github.com/NewFuture/DDNS/issues",
|
|
26
|
+
)
|
|
27
|
+
super(DnscomProvider, self)._validate()
|
|
28
|
+
|
|
29
|
+
def _signature(self, params):
|
|
30
|
+
"""https://www.51dns.com/document/api/70/72.html"""
|
|
31
|
+
params = {k: v for k, v in params.items() if v is not None}
|
|
32
|
+
params.update(
|
|
33
|
+
{
|
|
34
|
+
"apiKey": self.id,
|
|
35
|
+
"timestamp": time(), # 时间戳
|
|
176
36
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
37
|
+
)
|
|
38
|
+
query = encode_params(params)
|
|
39
|
+
sign = md5((query + self.token).encode("utf-8")).hexdigest()
|
|
40
|
+
params["hash"] = sign
|
|
41
|
+
return params
|
|
42
|
+
|
|
43
|
+
def _request(self, action, **params):
|
|
44
|
+
params = self._signature(params)
|
|
45
|
+
data = self._http("POST", "/api/{}/".format(action), body=params)
|
|
46
|
+
if data is None or not isinstance(data, dict):
|
|
47
|
+
raise Exception("response data is none")
|
|
48
|
+
if data.get("code", 0) != 0:
|
|
49
|
+
raise Exception("api error: " + str(data.get("message")))
|
|
50
|
+
return data.get("data")
|
|
51
|
+
|
|
52
|
+
def _query_zone_id(self, domain):
|
|
53
|
+
"""https://www.51dns.com/document/api/74/31.html"""
|
|
54
|
+
res = self._request("domain/getsingle", domainID=domain)
|
|
55
|
+
self.logger.debug("Queried domain: %s", res)
|
|
56
|
+
if res:
|
|
57
|
+
return res.get("domainID")
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def _query_record(self, zone_id, subdomain, main_domain, record_type, line, extra):
|
|
61
|
+
"""https://www.51dns.com/document/api/4/47.html"""
|
|
62
|
+
records = self._request("record/list", domainID=zone_id, host=subdomain, pageSize=500)
|
|
63
|
+
records = records.get("data", []) if records else []
|
|
64
|
+
for record in records:
|
|
65
|
+
if (
|
|
66
|
+
record.get("record") == subdomain
|
|
67
|
+
and record.get("type") == record_type
|
|
68
|
+
and (line is None or record.get("viewID") == line)
|
|
69
|
+
):
|
|
70
|
+
return record
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
def _create_record(self, zone_id, subdomain, main_domain, value, record_type, ttl, line, extra):
|
|
74
|
+
"""https://www.51dns.com/document/api/4/12.html"""
|
|
75
|
+
extra["remark"] = extra.get("remark", self.remark)
|
|
76
|
+
res = self._request(
|
|
77
|
+
"record/create",
|
|
78
|
+
domainID=zone_id,
|
|
79
|
+
value=value,
|
|
80
|
+
host=subdomain,
|
|
81
|
+
type=record_type,
|
|
82
|
+
TTL=ttl,
|
|
83
|
+
viewID=line,
|
|
84
|
+
**extra
|
|
85
|
+
)
|
|
86
|
+
if res and res.get("recordID"):
|
|
87
|
+
self.logger.info("Record created: %s", res)
|
|
88
|
+
return True
|
|
89
|
+
self.logger.error("Failed to create record: %s", res)
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
def _update_record(self, zone_id, old_record, value, record_type, ttl, line, extra):
|
|
93
|
+
"""https://www.51dns.com/document/api/4/45.html"""
|
|
94
|
+
extra["remark"] = extra.get("remark", self.remark)
|
|
95
|
+
res = self._request(
|
|
96
|
+
"record/modify", domainID=zone_id, recordID=old_record.get("recordID"), newvalue=value, newTTL=ttl
|
|
97
|
+
)
|
|
98
|
+
if res:
|
|
99
|
+
self.logger.info("Record updated: %s", res)
|
|
100
|
+
return True
|
|
101
|
+
self.logger.error("Failed to update record: %s", res)
|
|
102
|
+
return False
|