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/provider/dnspod.py CHANGED
@@ -1,186 +1,116 @@
1
1
  # coding=utf-8
2
2
  """
3
3
  DNSPOD API
4
- DNSPOD 接口解析操作库
5
- http://www.dnspod.cn/docs/domains.html
6
- @author: New Future
4
+ @doc: https://docs.dnspod.cn/api/
5
+ @author: NewFuture
7
6
  """
8
7
 
9
- from json import loads as jsondecode
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
- __author__ = 'New Future'
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
- if response.status < 200 or response.status >= 300:
65
- warning('%s : error[%d]:%s', action, response.status, res)
66
- raise Exception(res)
67
- else:
68
- data = jsondecode(res)
69
- debug('%s : result:%s', action, data)
70
- if not data:
71
- raise Exception("empty response")
72
- elif data.get("status", {}).get("code") == "1":
17
+ endpoint = "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.id, self.token), "format": "json"})
40
+ data = self._http("POST", "/" + action, body=params)
41
+ if data and data.get("status", {}).get("code") == "1": # 请求成功
42
+ return data
43
+ else: # 请求失败
44
+ error_msg = "Unknown error"
45
+ if data and isinstance(data, dict):
46
+ error_msg = data.get("status", {}).get("message", "Unknown error")
47
+ self.logger.warning("DNSPod API error: %s", error_msg)
73
48
  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
49
 
158
- records = get_records(domainid, name=sub, type=record_type)
159
- result = {}
160
- if records: # update
161
- # http://www.dnspod.cn/docs/records.html#record-modify
162
- for (did, record) in records.items():
163
- if record["value"] != value:
164
- debug(sub, record)
165
- res = request('Record.Modify', record_id=did, record_line=record["line"].replace("Default", "default").encode(
166
- "utf-8"), value=value, sub_domain=sub, domain_id=domainid, record_type=record_type, ttl=Config.TTL)
167
- if res:
168
- get_records.records[domainid][did]["value"] = value
169
- result[did] = res.get("record")
170
- else:
171
- result[did] = "update fail!\n" + str(res)
172
- else:
173
- result[did] = domain
174
- else: # create
175
- # http://www.dnspod.cn/docs/records.html#record-create
176
- res = request("Record.Create", domain_id=domainid, value=value,
177
- sub_domain=sub, record_type=record_type, record_line=API.DEFAULT, ttl=Config.TTL)
178
- if res:
179
- did = res.get("record")["id"]
180
- get_records.records[domainid][did] = res.get("record")
181
- get_records.records[domainid][did].update(
182
- value=value, sub_domain=sub, record_type=record_type)
183
- result = res.get("record")
184
- else:
185
- result = domain + " created fail!"
186
- return result
50
+ def _create_record(self, zone_id, subdomain, main_domain, value, record_type, ttl, line, extra):
51
+ # type: (str, str, str, str, str, int | str | None, str | None, dict) -> bool
52
+ """https://docs.dnspod.cn/api/add-record/"""
53
+ res = self._request(
54
+ "Record.Create",
55
+ extra=extra,
56
+ domain_id=zone_id,
57
+ sub_domain=subdomain,
58
+ value=value,
59
+ record_type=record_type,
60
+ record_line=line or self.DefaultLine,
61
+ ttl=ttl,
62
+ )
63
+ record = res and res.get("record")
64
+ if record: # 记录创建成功
65
+ self.logger.info("Record created: %s", record)
66
+ return True
67
+ else: # 记录创建失败
68
+ self.logger.error("Failed to create record: %s", res)
69
+ return False
70
+
71
+ def _update_record(self, zone_id, old_record, value, record_type, ttl, line, extra):
72
+ # type: (str, dict, str, str, int | str | None, str | None, dict) -> bool
73
+ """https://docs.dnspod.cn/api/modify-records/"""
74
+ record_line = (line or old_record.get("line") or self.DefaultLine).replace("Default", "default")
75
+ res = self._request(
76
+ "Record.Modify",
77
+ domain_id=zone_id,
78
+ record_id=old_record.get("id"),
79
+ sub_domain=old_record.get("name"),
80
+ record_type=record_type,
81
+ value=value,
82
+ record_line=record_line,
83
+ extra=extra,
84
+ )
85
+ record = res and res.get("record")
86
+ if record: # 记录更新成功
87
+ self.logger.debug("Record updated: %s", record)
88
+ return True
89
+ else: # 记录更新失败
90
+ self.logger.error("Failed to update record: %s", res)
91
+ return False
92
+
93
+ def _query_zone_id(self, domain):
94
+ # type: (str) -> str | None
95
+ """查询域名信息 https://docs.dnspod.cn/api/domain-info/"""
96
+ res = self._request("Domain.Info", domain=domain)
97
+ if res and isinstance(res, dict):
98
+ return res.get("domain", {}).get("id")
99
+ return None
100
+
101
+ def _query_record(self, zone_id, subdomain, main_domain, record_type, line, extra):
102
+ # type: (str, str, str, str, str | None, dict) -> dict | None
103
+ """查询记录 list 然后逐个查找 https://docs.dnspod.cn/api/record-list/"""
104
+ res = self._request(
105
+ "Record.List", domain_id=zone_id, sub_domain=subdomain, record_type=record_type, line=line
106
+ )
107
+ # length="3000"
108
+ records = res.get("records", [])
109
+ n = len(records)
110
+ if not n:
111
+ self.logger.warning("No record found for [%s] %s<%s>(line: %s)", zone_id, subdomain, record_type, line)
112
+ return None
113
+ if n > 1:
114
+ self.logger.warning("%d records found for %s<%s>(%s):\n %s", n, subdomain, record_type, line, records)
115
+ return next((r for r in records if r.get("name") == subdomain), None)
116
+ return records[0]
@@ -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: New Future
5
+ @author: NewFuture
7
6
  """
8
7
 
9
- from .dnspod import * # noqa: F403
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
- # https://github.com/NewFuture/DDNS/issues/286
15
- # API.TOKEN_PARAM = "user_token" # noqa: F405
11
+ class DnspodComProvider(DnspodProvider):
12
+ """
13
+ DNSPOD.com Provider (国际版)
14
+ This class extends the DnspodProvider to use the global DNSPOD API.
15
+ """
16
+
17
+ endpoint = "https://api.dnspod.com"
18
+ DefaultLine = "default"
@@ -0,0 +1,82 @@
1
+ # coding=utf-8
2
+ """
3
+ Tencent Cloud EdgeOne API
4
+ 腾讯云 EdgeOne (边缘安全速平台) API
5
+ API Documentation: https://cloud.tencent.com/document/api/1552/80731
6
+ @author: NewFuture
7
+ """
8
+ from ddns.provider._base import join_domain
9
+ from .tencentcloud import TencentCloudProvider
10
+
11
+
12
+ class EdgeOneProvider(TencentCloudProvider):
13
+ """
14
+ 腾讯云 EdgeOne API 提供商
15
+ Tencent Cloud EdgeOne API Provider
16
+ """
17
+
18
+ endpoint = "https://teo.tencentcloudapi.com"
19
+ # 腾讯云 EdgeOne API 配置
20
+ service = "teo"
21
+ version_date = "2022-09-01"
22
+
23
+ def _query_zone_id(self, domain):
24
+ # type: (str) -> str | None
25
+ """查询域名的加速域名信息获取 ZoneId https://cloud.tencent.com/document/api/1552/80713"""
26
+ # 首先尝试直接查找域名
27
+ filters = [{"Name": "zone-name", "Values": [domain], "Fuzzy": False}] # type: Any
28
+ response = self._request("DescribeZones", Filters=filters)
29
+
30
+ if response and "Zones" in response:
31
+ for zone in response.get("Zones", []):
32
+ if zone.get("ZoneName") == domain:
33
+ zone_id = zone.get("ZoneId")
34
+ if zone_id:
35
+ self.logger.debug("Found acceleration domain %s with Zone ID: %s", domain, zone_id)
36
+ return zone_id
37
+
38
+ self.logger.debug("Acceleration domain not found for: %s", domain)
39
+ return None
40
+
41
+ def _query_record(self, zone_id, subdomain, main_domain, record_type, line, extra):
42
+ # type: (str, str, str, str, str | None, dict) -> dict | None
43
+ """查询加速域名信息 https://cloud.tencent.com/document/api/1552/86336"""
44
+ domain = join_domain(subdomain, main_domain)
45
+ filters = [{"Name": "domain-name", "Values": [domain], "Fuzzy": False}] # type: Any
46
+ response = self._request("DescribeAccelerationDomains", ZoneId=zone_id, Filters=filters)
47
+
48
+ if response and "AccelerationDomains" in response:
49
+ for domain_info in response.get("AccelerationDomains", []):
50
+ if domain_info.get("DomainName") == domain:
51
+ self.logger.debug("Found acceleration domain: %s", domain_info)
52
+ return domain_info
53
+
54
+ self.logger.warning("No acceleration domain found for: %s, response: %s", domain, response)
55
+ return None
56
+
57
+ def _create_record(self, zone_id, subdomain, main_domain, value, record_type, ttl, line, extra):
58
+ # type: (str, str, str, str, str, int, str | None, dict) -> bool
59
+ """创建新的加速域名记录 https://cloud.tencent.com/document/api/1552/86338"""
60
+ domain = join_domain(subdomain, main_domain)
61
+ origin = {"OriginType": "IP_DOMAIN", "Origin": value} # type: Any
62
+ res = self._request("CreateAccelerationDomain", ZoneId=zone_id, DomainName=domain, OriginInfo=origin, **extra)
63
+ if res:
64
+ self.logger.info("Acceleration domain created (%s)", res.get("RequestId"))
65
+ return True
66
+
67
+ self.logger.error("Failed to create acceleration domain, response: %s", res)
68
+ return False
69
+
70
+ def _update_record(self, zone_id, old_record, value, record_type, ttl, line, extra):
71
+ """更新加速域名的源站 IP 地址 https://cloud.tencent.com/document/api/1552/86335"""
72
+ domain = old_record.get("DomainName")
73
+ # 构建源站信息
74
+ backup = old_record.get("OriginDetail", {}).get("BackupOrigin", "")
75
+ origin = {"OriginType": "IP_DOMAIN", "Origin": value, "BackupOrigin": backup} # type: Any
76
+ response = self._request("ModifyAccelerationDomain", ZoneId=zone_id, DomainName=domain, OriginInfo=origin)
77
+
78
+ if response:
79
+ self.logger.info("Acceleration domain updated (%s)", response.get("RequestId"))
80
+ return True
81
+ self.logger.error("Failed to update acceleration domain origin, response: %s", response)
82
+ return False
ddns/provider/he.py CHANGED
@@ -1,83 +1,46 @@
1
1
  # coding=utf-8
2
2
  """
3
3
  Hurricane Electric (he.net) API
4
- Hurricane Electric (he.net) 接口解析操作库
5
- https://dns.he.net/docs.html
6
- @author: NN708
4
+ @author: NN708, NewFuture
7
5
  """
8
6
 
9
- from logging import debug, info, warning
10
-
11
- try: # python 3
12
- from http.client import HTTPSConnection
13
- from urllib.parse import urlencode
14
- except ImportError: # python 2
15
- from httplib import HTTPSConnection
16
- from urllib import urlencode
17
-
18
- __author__ = 'NN708'
19
-
20
-
21
- class Config:
22
- TOKEN = "password"
23
- PROXY = None # 代理设置
24
-
25
-
26
- class API:
27
- # API 配置
28
- SITE = "dyn.dns.he.net"
29
- METHOD = "POST"
30
- ACTION = "nic/update"
31
- TOKEN_PARAM = "password" # key name of token param
32
-
33
-
34
- def request(param=None, **params):
35
- """
36
- 发送请求数据
37
- """
38
- if param:
39
- params.update(param)
40
-
41
- params.update({API.TOKEN_PARAM: '***'})
42
- info("%s/%s : %s", API.SITE, API.ACTION, params)
43
- params[API.TOKEN_PARAM] = Config.TOKEN
44
-
45
- if Config.PROXY:
46
- conn = HTTPSConnection(Config.PROXY)
47
- conn.set_tunnel(API.SITE, 443)
48
- else:
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
+ endpoint = "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.id:
22
+ raise ValueError("Hurricane Electric (he.net) does not use `id`, use `token(password)` only.")
23
+ if not self.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.token, # Use 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