ddns 4.0.1__py2.py3-none-any.whl → 4.1.0b1__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 +5 -0
- ddns/__init__.py +6 -4
- ddns/__main__.py +75 -69
- ddns/provider/__init__.py +59 -0
- ddns/provider/_base.py +659 -0
- ddns/provider/alidns.py +127 -176
- ddns/provider/callback.py +67 -105
- ddns/provider/cloudflare.py +87 -151
- ddns/provider/debug.py +20 -0
- ddns/provider/dnscom.py +91 -169
- ddns/provider/dnspod.py +105 -174
- ddns/provider/dnspod_com.py +11 -8
- ddns/provider/he.py +41 -78
- ddns/provider/huaweidns.py +133 -256
- ddns/provider/tencentcloud.py +195 -0
- ddns/util/cache.py +67 -54
- ddns/util/config.py +29 -32
- ddns/util/http.py +277 -0
- ddns/util/ip.py +20 -17
- {ddns-4.0.1.dist-info → ddns-4.1.0b1.dist-info}/METADATA +96 -71
- ddns-4.1.0b1.dist-info/RECORD +26 -0
- ddns-4.0.1.dist-info/RECORD +0 -21
- {ddns-4.0.1.dist-info → ddns-4.1.0b1.dist-info}/WHEEL +0 -0
- {ddns-4.0.1.dist-info → ddns-4.1.0b1.dist-info}/entry_points.txt +0 -0
- {ddns-4.0.1.dist-info → ddns-4.1.0b1.dist-info}/licenses/LICENSE +0 -0
- {ddns-4.0.1.dist-info → ddns-4.1.0b1.dist-info}/top_level.txt +0 -0
ddns/provider/dnspod.py
CHANGED
|
@@ -1,186 +1,117 @@
|
|
|
1
1
|
# coding=utf-8
|
|
2
2
|
"""
|
|
3
3
|
DNSPOD API
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@author: New Future
|
|
4
|
+
@doc: https://docs.dnspod.cn/api/
|
|
5
|
+
@author: NewFuture
|
|
7
6
|
"""
|
|
8
7
|
|
|
9
|
-
from
|
|
10
|
-
from logging import debug, info, warning
|
|
11
|
-
from os import environ
|
|
8
|
+
from ._base import BaseProvider, TYPE_FORM
|
|
12
9
|
|
|
13
|
-
try: # python 3
|
|
14
|
-
from http.client import HTTPSConnection
|
|
15
|
-
from urllib.parse import urlencode
|
|
16
|
-
except ImportError: # python 2
|
|
17
|
-
from httplib import HTTPSConnection
|
|
18
|
-
from urllib import urlencode
|
|
19
10
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class Config:
|
|
24
|
-
ID = "token id"
|
|
25
|
-
TOKEN = "token key"
|
|
26
|
-
PROXY = None # 代理设置
|
|
27
|
-
TTL = None
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class API:
|
|
31
|
-
# API 配置
|
|
32
|
-
SITE = "dnsapi.cn" # API endpoint
|
|
33
|
-
METHOD = "POST" # 请求方法
|
|
34
|
-
TOKEN_PARAM = "login_token" # token参数
|
|
35
|
-
DEFAULT = "默认" # 默认线路名
|
|
36
|
-
LENGTH = "length" # 添加参数
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def request(action, param=None, **params):
|
|
11
|
+
class DnspodProvider(BaseProvider):
|
|
40
12
|
"""
|
|
41
|
-
|
|
13
|
+
DNSPOD API
|
|
14
|
+
DNSPOD 接口解析操作库
|
|
42
15
|
"""
|
|
43
|
-
if param:
|
|
44
|
-
params.update(param)
|
|
45
|
-
params = dict((k, params[k]) for k in params if params[k] is not None)
|
|
46
|
-
params.update({API.TOKEN_PARAM: '***', 'format': 'json'})
|
|
47
|
-
info("%s/%s : %s", API.SITE, action, params)
|
|
48
|
-
params[API.TOKEN_PARAM] = "%s,%s" % (Config.ID, Config.TOKEN)
|
|
49
|
-
params[API.LENGTH] = "3000" # 添加参数
|
|
50
|
-
if Config.PROXY:
|
|
51
|
-
conn = HTTPSConnection(Config.PROXY)
|
|
52
|
-
conn.set_tunnel(API.SITE, 443)
|
|
53
|
-
else:
|
|
54
|
-
conn = HTTPSConnection(API.SITE)
|
|
55
|
-
|
|
56
|
-
conn.request(API.METHOD, '/' + action, urlencode(params), {
|
|
57
|
-
"Content-type": "application/x-www-form-urlencoded",
|
|
58
|
-
"User-Agent": "DDNS/%s (ddns@newfuture.cc)" % environ.get("DDNS_VERSION", "1.0.0")
|
|
59
|
-
})
|
|
60
|
-
response = conn.getresponse()
|
|
61
|
-
res = response.read().decode('utf8')
|
|
62
|
-
conn.close()
|
|
63
16
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
17
|
+
API = "https://dnsapi.cn"
|
|
18
|
+
content_type = TYPE_FORM
|
|
19
|
+
|
|
20
|
+
DefaultLine = "默认"
|
|
21
|
+
|
|
22
|
+
def _request(self, action, extra=None, **params):
|
|
23
|
+
# type: (str, dict | None, **(str | int | bytes | bool | None)) -> dict
|
|
24
|
+
"""
|
|
25
|
+
发送请求数据
|
|
26
|
+
|
|
27
|
+
Send request to DNSPod API.
|
|
28
|
+
Args:
|
|
29
|
+
action (str): API 动作/Action
|
|
30
|
+
extra (dict|None): 额外参数/Extra params
|
|
31
|
+
params (dict): 其它参数/Other params
|
|
32
|
+
Returns:
|
|
33
|
+
dict: 响应数据/Response data
|
|
34
|
+
"""
|
|
35
|
+
# 过滤掉None参数
|
|
36
|
+
if extra:
|
|
37
|
+
params.update(extra)
|
|
38
|
+
params = {k: v for k, v in params.items() if v is not None}
|
|
39
|
+
params.update({"login_token": "{0},{1}".format(self.auth_id, self.auth_token), "format": "json"})
|
|
40
|
+
headers = {"User-Agent": "DDNS/{0} (ddns@newfuture.cc)".format(self.version)}
|
|
41
|
+
data = self._http("POST", "/" + action, headers=headers, body=params)
|
|
42
|
+
if data and data.get("status", {}).get("code") == "1": # 请求成功
|
|
43
|
+
return data
|
|
44
|
+
else: # 请求失败
|
|
45
|
+
error_msg = "Unknown error"
|
|
46
|
+
if data and isinstance(data, dict):
|
|
47
|
+
error_msg = data.get("status", {}).get("message", "Unknown error")
|
|
48
|
+
self.logger.warning("DNSPod API error: %s", error_msg)
|
|
73
49
|
return data
|
|
74
|
-
else:
|
|
75
|
-
raise Exception(data.get('status', {}))
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def get_domain_info(domain):
|
|
79
|
-
"""
|
|
80
|
-
切割域名获取主域名和对应ID
|
|
81
|
-
"""
|
|
82
|
-
domain_split = domain.split('.')
|
|
83
|
-
sub, did = None, None
|
|
84
|
-
main = domain_split.pop()
|
|
85
|
-
while domain_split: # 通过API判断,最后两个,三个递增
|
|
86
|
-
main = domain_split.pop() + '.' + main
|
|
87
|
-
did = get_domain_id(main)
|
|
88
|
-
if did:
|
|
89
|
-
sub = ".".join(domain_split) or '@'
|
|
90
|
-
# root domain根域名https://github.com/NewFuture/DDNS/issues/9
|
|
91
|
-
break
|
|
92
|
-
info('domain_id: %s, sub: %s', did, sub)
|
|
93
|
-
return did, sub
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def get_domain_id(domain):
|
|
97
|
-
"""
|
|
98
|
-
获取域名ID
|
|
99
|
-
http://www.dnspod.cn/docs/domains.html#domain-info
|
|
100
|
-
"""
|
|
101
|
-
if not hasattr(get_domain_id, "domain_list"):
|
|
102
|
-
get_domain_id.domain_list = {} # "静态变量"存储已查询过的id
|
|
103
|
-
|
|
104
|
-
if domain in get_domain_id.domain_list:
|
|
105
|
-
# 如果已经存在直接返回防止再次请求
|
|
106
|
-
return get_domain_id.domain_list[domain]
|
|
107
|
-
else:
|
|
108
|
-
try:
|
|
109
|
-
d_info = request('Domain.Info', domain=domain)
|
|
110
|
-
except Exception as e:
|
|
111
|
-
info("get_domain_id(%s) error: %s", domain, e)
|
|
112
|
-
return
|
|
113
|
-
did = d_info.get("domain", {}).get("id")
|
|
114
|
-
if did:
|
|
115
|
-
get_domain_id.domain_list[domain] = did
|
|
116
|
-
return did
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def get_records(did, **conditions):
|
|
120
|
-
"""
|
|
121
|
-
获取记录ID
|
|
122
|
-
返回满足条件的所有记录[]
|
|
123
|
-
TODO 大于3000翻页
|
|
124
|
-
http://www.dnspod.cn/docs/records.html#record-list
|
|
125
|
-
"""
|
|
126
|
-
if not hasattr(get_records, "records"):
|
|
127
|
-
get_records.records = {} # "静态变量"存储已查询过的id
|
|
128
|
-
get_records.keys = ("id", "name", "type", "line",
|
|
129
|
-
"line_id", "enabled", "mx", "value")
|
|
130
|
-
|
|
131
|
-
if did not in get_records.records:
|
|
132
|
-
get_records.records[did] = {}
|
|
133
|
-
data = request('Record.List', domain_id=did)
|
|
134
|
-
if data:
|
|
135
|
-
for record in data.get('records'):
|
|
136
|
-
get_records.records[did][record["id"]] = {
|
|
137
|
-
k: v for (k, v) in record.items() if k in get_records.keys}
|
|
138
|
-
|
|
139
|
-
records = {}
|
|
140
|
-
for (did, record) in get_records.records[did].items():
|
|
141
|
-
for (k, value) in conditions.items():
|
|
142
|
-
if record.get(k) != value:
|
|
143
|
-
break
|
|
144
|
-
else: # for else push
|
|
145
|
-
records[did] = record
|
|
146
|
-
return records
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def update_record(domain, value, record_type="A"):
|
|
150
|
-
"""
|
|
151
|
-
更新记录
|
|
152
|
-
"""
|
|
153
|
-
info(">>>>>%s(%s)", domain, record_type)
|
|
154
|
-
domainid, sub = get_domain_info(domain)
|
|
155
|
-
if not domainid:
|
|
156
|
-
raise Exception("invalid domain: [ %s ] " % domain)
|
|
157
50
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
#
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
51
|
+
def _create_record(self, zone_id, subdomain, main_domain, value, record_type, ttl, line, extra):
|
|
52
|
+
# type: (str, str, str, str, str, int | str | None, str | None, dict) -> bool
|
|
53
|
+
"""https://docs.dnspod.cn/api/add-record/"""
|
|
54
|
+
res = self._request(
|
|
55
|
+
"Record.Create",
|
|
56
|
+
extra=extra,
|
|
57
|
+
domain_id=zone_id,
|
|
58
|
+
sub_domain=subdomain,
|
|
59
|
+
value=value,
|
|
60
|
+
record_type=record_type,
|
|
61
|
+
record_line=line or self.DefaultLine,
|
|
62
|
+
ttl=ttl,
|
|
63
|
+
)
|
|
64
|
+
record = res and res.get("record")
|
|
65
|
+
if record: # 记录创建成功
|
|
66
|
+
self.logger.info("Record created: %s", record)
|
|
67
|
+
return True
|
|
68
|
+
else: # 记录创建失败
|
|
69
|
+
self.logger.error("Failed to create record: %s", res)
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
def _update_record(self, zone_id, old_record, value, record_type, ttl, line, extra):
|
|
73
|
+
# type: (str, dict, str, str, int | str | None, str | None, dict) -> bool
|
|
74
|
+
"""https://docs.dnspod.cn/api/modify-records/"""
|
|
75
|
+
record_line = (line or old_record.get("line") or self.DefaultLine).replace("Default", "default")
|
|
76
|
+
res = self._request(
|
|
77
|
+
"Record.Modify",
|
|
78
|
+
domain_id=zone_id,
|
|
79
|
+
record_id=old_record.get("id"),
|
|
80
|
+
sub_domain=old_record.get("name"),
|
|
81
|
+
record_type=record_type,
|
|
82
|
+
value=value,
|
|
83
|
+
record_line=record_line,
|
|
84
|
+
extra=extra,
|
|
85
|
+
)
|
|
86
|
+
record = res and res.get("record")
|
|
87
|
+
if record: # 记录更新成功
|
|
88
|
+
self.logger.debug("Record updated: %s", record)
|
|
89
|
+
return True
|
|
90
|
+
else: # 记录更新失败
|
|
91
|
+
self.logger.error("Failed to update record: %s", res)
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
def _query_zone_id(self, domain):
|
|
95
|
+
# type: (str) -> str | None
|
|
96
|
+
"""查询域名信息 https://docs.dnspod.cn/api/domain-info/"""
|
|
97
|
+
res = self._request("Domain.Info", domain=domain)
|
|
98
|
+
if res and isinstance(res, dict):
|
|
99
|
+
return res.get("domain", {}).get("id")
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
def _query_record(self, zone_id, subdomain, main_domain, record_type, line, extra):
|
|
103
|
+
# type: (str, str, str, str, str | None, dict) -> dict | None
|
|
104
|
+
"""查询记录 list 然后逐个查找 https://docs.dnspod.cn/api/record-list/"""
|
|
105
|
+
res = self._request(
|
|
106
|
+
"Record.List", domain_id=zone_id, sub_domain=subdomain, record_type=record_type, line=line
|
|
107
|
+
)
|
|
108
|
+
# length="3000"
|
|
109
|
+
records = res.get("records", [])
|
|
110
|
+
n = len(records)
|
|
111
|
+
if not n:
|
|
112
|
+
self.logger.warning("No record found for [%s] %s<%s>(line: %s)", zone_id, subdomain, record_type, line)
|
|
113
|
+
return None
|
|
114
|
+
if n > 1:
|
|
115
|
+
self.logger.warning("%d records found for %s<%s>(%s):\n %s", n, subdomain, record_type, line, records)
|
|
116
|
+
return next((r for r in records if r.get("name") == subdomain), None)
|
|
117
|
+
return records[0]
|
ddns/provider/dnspod_com.py
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# coding=utf-8
|
|
2
2
|
"""
|
|
3
|
-
DNSPOD API
|
|
4
|
-
DNSPOD 接口解析操作库
|
|
3
|
+
DNSPOD Global (国际版) API
|
|
5
4
|
http://www.dnspod.com/docs/domains.html
|
|
6
|
-
@author:
|
|
5
|
+
@author: NewFuture
|
|
7
6
|
"""
|
|
8
7
|
|
|
9
|
-
from .dnspod import
|
|
8
|
+
from .dnspod import DnspodProvider # noqa: F401
|
|
10
9
|
|
|
11
|
-
API.SITE = "api.dnspod.com" # noqa: F405
|
|
12
|
-
API.DEFAULT = "default" # noqa: F405
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
class DnspodComProvider(DnspodProvider):
|
|
12
|
+
"""
|
|
13
|
+
DNSPOD.com Provider (国际版)
|
|
14
|
+
This class extends the DnspodProvider to use the global DNSPOD API.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
API = "https://api.dnspod.com"
|
|
18
|
+
DefaultLine = "default"
|
ddns/provider/he.py
CHANGED
|
@@ -1,83 +1,46 @@
|
|
|
1
1
|
# coding=utf-8
|
|
2
2
|
"""
|
|
3
3
|
Hurricane Electric (he.net) API
|
|
4
|
-
|
|
5
|
-
https://dns.he.net/docs.html
|
|
6
|
-
@author: NN708
|
|
4
|
+
@author: NN708, NewFuture
|
|
7
5
|
"""
|
|
8
6
|
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
conn = HTTPSConnection(API.SITE)
|
|
50
|
-
|
|
51
|
-
conn.request(API.METHOD, '/' + API.ACTION, urlencode(params), {
|
|
52
|
-
"Content-type": "application/x-www-form-urlencoded"
|
|
53
|
-
})
|
|
54
|
-
response = conn.getresponse()
|
|
55
|
-
res = response.read().decode('utf8')
|
|
56
|
-
conn.close()
|
|
57
|
-
|
|
58
|
-
if response.status < 200 or response.status >= 300:
|
|
59
|
-
warning('%s : error[%d]:%s', API.ACTION, response.status, res)
|
|
60
|
-
raise Exception(res)
|
|
61
|
-
else:
|
|
62
|
-
debug('%s : result:%s', API.ACTION, res)
|
|
63
|
-
if not res:
|
|
64
|
-
raise Exception("empty response")
|
|
65
|
-
elif res[:5] == "nochg" or res[:4] == "good": # No change or success
|
|
66
|
-
return res
|
|
67
|
-
else:
|
|
68
|
-
raise Exception(res)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def update_record(domain, value, record_type="A"):
|
|
72
|
-
"""
|
|
73
|
-
更新记录
|
|
74
|
-
"""
|
|
75
|
-
info(">>>>>%s(%s)", domain, record_type)
|
|
76
|
-
res = request(hostname=domain, myip=value)
|
|
77
|
-
if res[:4] == "good":
|
|
78
|
-
result = "Record updated. New IP is: " + res[5:-1]
|
|
79
|
-
elif res[:5] == "nochg":
|
|
80
|
-
result = "IP not changed. IP is: " + res[6:-1]
|
|
81
|
-
else:
|
|
82
|
-
result = "Record update failed."
|
|
83
|
-
return result
|
|
7
|
+
from ._base import SimpleProvider, TYPE_FORM
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HeProvider(SimpleProvider):
|
|
11
|
+
API = "https://dyn.dns.he.net"
|
|
12
|
+
content_type = TYPE_FORM
|
|
13
|
+
accept = None # he.net does not require a specific Accept header
|
|
14
|
+
decode_response = False # he.net response is plain text, not JSON
|
|
15
|
+
|
|
16
|
+
def _validate(self):
|
|
17
|
+
self.logger.warning(
|
|
18
|
+
"HE.net 缺少充分的真实环境测试,请及时在 GitHub Issues 中反馈: %s",
|
|
19
|
+
"https://github.com/NewFuture/DDNS/issues",
|
|
20
|
+
)
|
|
21
|
+
if self.auth_id:
|
|
22
|
+
raise ValueError("Hurricane Electric (he.net) does not use `id`, use `token(password)` only.")
|
|
23
|
+
if not self.auth_token:
|
|
24
|
+
raise ValueError("Hurricane Electric (he.net) requires `token(password)`.")
|
|
25
|
+
|
|
26
|
+
def set_record(self, domain, value, record_type="A", ttl=None, line=None, **extra):
|
|
27
|
+
"""
|
|
28
|
+
使用 POST API 更新或创建 DNS 记录。Update or create DNS record with POST API.
|
|
29
|
+
https://dns.he.net/docs.html
|
|
30
|
+
"""
|
|
31
|
+
self.logger.info("%s => %s(%s)", domain, value, record_type)
|
|
32
|
+
params = {
|
|
33
|
+
"hostname": domain, # he.net requires full domain name
|
|
34
|
+
"myip": value, # IP address to update
|
|
35
|
+
"password": self.auth_token, # Use auth_token as password
|
|
36
|
+
}
|
|
37
|
+
try:
|
|
38
|
+
res = self._http("POST", "/nic/update", body=params)
|
|
39
|
+
if res and res[:5] == "nochg" or res[:4] == "good": # No change or success
|
|
40
|
+
self.logger.info("HE API response: %s", res)
|
|
41
|
+
return True
|
|
42
|
+
else:
|
|
43
|
+
self.logger.error("HE API error: %s", res)
|
|
44
|
+
except Exception as e:
|
|
45
|
+
self.logger.error("Error updating record for %s: %s", domain, e)
|
|
46
|
+
return False
|