ddns 4.1.0b2__py2.py3-none-any.whl → 4.1.0b4__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 +1 -0
- ddns/__init__.py +2 -2
- ddns/__main__.py +45 -12
- ddns/ip.py +53 -9
- ddns/provider/_base.py +13 -26
- ddns/provider/alidns.py +5 -4
- ddns/provider/aliesa.py +5 -4
- ddns/provider/callback.py +1 -0
- ddns/provider/cloudflare.py +2 -2
- ddns/provider/debug.py +0 -1
- ddns/provider/dnscom.py +3 -2
- ddns/provider/dnspod.py +1 -3
- ddns/provider/edgeone.py +1 -0
- ddns/provider/huaweidns.py +5 -4
- ddns/provider/noip.py +14 -16
- ddns/provider/tencentcloud.py +9 -10
- ddns/util/fileio.py +113 -0
- ddns/util/http.py +271 -177
- ddns/util/try_run.py +37 -0
- {ddns-4.1.0b2.dist-info → ddns-4.1.0b4.dist-info}/METADATA +85 -118
- ddns-4.1.0b4.dist-info/RECORD +33 -0
- ddns-4.1.0b2.dist-info/RECORD +0 -31
- {ddns-4.1.0b2.dist-info → ddns-4.1.0b4.dist-info}/WHEEL +0 -0
- {ddns-4.1.0b2.dist-info → ddns-4.1.0b4.dist-info}/entry_points.txt +0 -0
- {ddns-4.1.0b2.dist-info → ddns-4.1.0b4.dist-info}/licenses/LICENSE +0 -0
- {ddns-4.1.0b2.dist-info → ddns-4.1.0b4.dist-info}/top_level.txt +0 -0
ddns/__builtins__.pyi
CHANGED
ddns/__init__.py
CHANGED
ddns/__main__.py
CHANGED
|
@@ -4,20 +4,18 @@ DDNS
|
|
|
4
4
|
@author: NewFuture, rufengsuixing
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import sys
|
|
7
8
|
from io import TextIOWrapper
|
|
8
|
-
from subprocess import check_output
|
|
9
9
|
from logging import getLogger
|
|
10
|
-
import
|
|
10
|
+
from subprocess import check_output
|
|
11
11
|
|
|
12
|
-
from .__init__ import __version__, __description__, build_date
|
|
13
|
-
from .config import load_config, Config # noqa: F401
|
|
14
|
-
from .provider import get_provider_class, SimpleProvider
|
|
15
12
|
from . import ip
|
|
13
|
+
from .__init__ import __description__, __version__, build_date
|
|
16
14
|
from .cache import Cache
|
|
15
|
+
from .config import Config, load_configs # noqa: F401
|
|
16
|
+
from .provider import SimpleProvider, get_provider_class # noqa: F401
|
|
17
17
|
|
|
18
18
|
logger = getLogger()
|
|
19
|
-
# Set user agent for All Providers
|
|
20
|
-
SimpleProvider.user_agent = SimpleProvider.user_agent.format(version=__version__)
|
|
21
19
|
|
|
22
20
|
|
|
23
21
|
def get_ip(ip_type, rules):
|
|
@@ -94,7 +92,7 @@ def run(config):
|
|
|
94
92
|
# dns provider class
|
|
95
93
|
provider_class = get_provider_class(config.dns)
|
|
96
94
|
dns = provider_class(
|
|
97
|
-
config.id, config.token, endpoint=config.endpoint, logger=logger, proxy=config.proxy,
|
|
95
|
+
config.id, config.token, endpoint=config.endpoint, logger=logger, proxy=config.proxy, ssl=config.ssl
|
|
98
96
|
)
|
|
99
97
|
cache = Cache.new(config.cache, config.md5(), logger)
|
|
100
98
|
return (
|
|
@@ -104,14 +102,49 @@ def run(config):
|
|
|
104
102
|
|
|
105
103
|
|
|
106
104
|
def main():
|
|
107
|
-
|
|
108
|
-
if
|
|
105
|
+
stdout = sys.stdout # pythonw 模式无 stdout
|
|
106
|
+
if stdout and stdout.encoding and stdout.encoding.lower() != "utf-8" and hasattr(stdout, "buffer"):
|
|
109
107
|
# 兼容windows 和部分ASCII编码的老旧系统
|
|
110
108
|
sys.stdout = TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
|
111
109
|
sys.stderr = TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
|
110
|
+
|
|
111
|
+
# Windows 下输出一个空行
|
|
112
|
+
if stdout and sys.platform.startswith("win"):
|
|
113
|
+
stdout.write("\r\n")
|
|
114
|
+
|
|
112
115
|
logger.name = "ddns"
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
|
|
117
|
+
# 使用多配置加载器,它会自动处理单个和多个配置
|
|
118
|
+
configs = load_configs(__description__, __version__, build_date)
|
|
119
|
+
|
|
120
|
+
if len(configs) == 1:
|
|
121
|
+
# 单个配置,使用原有逻辑(向后兼容)
|
|
122
|
+
config = configs[0]
|
|
123
|
+
success = run(config)
|
|
124
|
+
if not success:
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
else:
|
|
127
|
+
# 多个配置,使用新的批处理逻辑
|
|
128
|
+
overall_success = True
|
|
129
|
+
for i, config in enumerate(configs):
|
|
130
|
+
# 如果log_level有值则设置setLevel
|
|
131
|
+
if hasattr(config, "log_level") and config.log_level:
|
|
132
|
+
logger.setLevel(config.log_level)
|
|
133
|
+
logger.info("Running configuration %d/%d", i + 1, len(configs))
|
|
134
|
+
# 记录当前provider
|
|
135
|
+
logger.info("Using DNS provider: %s", config.dns)
|
|
136
|
+
success = run(config)
|
|
137
|
+
if not success:
|
|
138
|
+
overall_success = False
|
|
139
|
+
logger.error("Configuration %d failed", i + 1)
|
|
140
|
+
else:
|
|
141
|
+
logger.info("Configuration %d completed successfully", i + 1)
|
|
142
|
+
|
|
143
|
+
if not overall_success:
|
|
144
|
+
logger.error("Some configurations failed")
|
|
145
|
+
sys.exit(1)
|
|
146
|
+
else:
|
|
147
|
+
logger.info("All configurations completed successfully")
|
|
115
148
|
|
|
116
149
|
|
|
117
150
|
if __name__ == "__main__":
|
ddns/ip.py
CHANGED
|
@@ -5,7 +5,7 @@ from os import name as os_name, popen
|
|
|
5
5
|
from socket import socket, getaddrinfo, gethostname, AF_INET, AF_INET6, SOCK_DGRAM
|
|
6
6
|
from logging import debug, error
|
|
7
7
|
|
|
8
|
-
from .util.http import
|
|
8
|
+
from .util.http import request
|
|
9
9
|
|
|
10
10
|
# 模块级别的SSL验证配置,默认使用auto模式
|
|
11
11
|
ssl_verify = "auto"
|
|
@@ -16,6 +16,24 @@ IPV4_REG = r"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1
|
|
|
16
16
|
# https://community.helpsystems.com/forums/intermapper/miscellaneous-topics/5acc4fcf-fa83-e511-80cf-0050568460e4
|
|
17
17
|
IPV6_REG = r"((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))" # noqa: E501
|
|
18
18
|
|
|
19
|
+
# 公网IPv4 API列表,按优先级排序
|
|
20
|
+
PUBLIC_IPV4_APIS = [
|
|
21
|
+
"https://api.ipify.cn",
|
|
22
|
+
"https://api.ipify.org",
|
|
23
|
+
"https://4.ipw.cn/",
|
|
24
|
+
"https://ipinfo.io/ip",
|
|
25
|
+
"https://api-ipv4.ip.sb/ip",
|
|
26
|
+
"http://checkip.amazonaws.com",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
# 公网IPv6 API列表,按优先级排序
|
|
30
|
+
PUBLIC_IPV6_APIS = [
|
|
31
|
+
"https://api6.ipify.org/",
|
|
32
|
+
"https://6.ipw.cn/",
|
|
33
|
+
"https://api-ipv6.ip.sb/ip",
|
|
34
|
+
"http://ipv6.icanhazip.com",
|
|
35
|
+
]
|
|
36
|
+
|
|
19
37
|
|
|
20
38
|
def default_v4(): # 默认连接外网的ipv4
|
|
21
39
|
s = socket(AF_INET, SOCK_DGRAM)
|
|
@@ -48,9 +66,8 @@ def local_v4(i=0): # 本地ipv4地址
|
|
|
48
66
|
def _open(url, reg):
|
|
49
67
|
try:
|
|
50
68
|
debug("open: %s", url)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
69
|
+
# IP 模块重试3次
|
|
70
|
+
response = request("GET", url, verify=ssl_verify, retries=2)
|
|
54
71
|
res = response.body
|
|
55
72
|
debug("response: %s", res)
|
|
56
73
|
match = compile(reg).search(res)
|
|
@@ -61,16 +78,43 @@ def _open(url, reg):
|
|
|
61
78
|
error(e)
|
|
62
79
|
|
|
63
80
|
|
|
64
|
-
def
|
|
65
|
-
|
|
81
|
+
def _try_multiple_apis(api_list, reg, ip_type):
|
|
82
|
+
"""
|
|
83
|
+
Try multiple API endpoints until one succeeds
|
|
84
|
+
"""
|
|
85
|
+
for url in api_list:
|
|
86
|
+
try:
|
|
87
|
+
debug("Trying %s API: %s", ip_type, url)
|
|
88
|
+
result = _open(url, reg)
|
|
89
|
+
if result:
|
|
90
|
+
debug("Successfully got %s from %s: %s", ip_type, url, result)
|
|
91
|
+
return result
|
|
92
|
+
else:
|
|
93
|
+
debug("No valid IP found from %s", url)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
debug("Failed to get %s from %s: %s", ip_type, url, e)
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def public_v4(url=None, reg=IPV4_REG): # 公网IPV4地址
|
|
100
|
+
if url:
|
|
101
|
+
# 使用指定URL
|
|
102
|
+
return _open(url, reg)
|
|
103
|
+
else:
|
|
104
|
+
# 使用多个API自动重试
|
|
105
|
+
return _try_multiple_apis(PUBLIC_IPV4_APIS, reg, "IPv4")
|
|
66
106
|
|
|
67
107
|
|
|
68
|
-
def public_v6(url=
|
|
69
|
-
|
|
108
|
+
def public_v6(url=None, reg=IPV6_REG): # 公网IPV6地址
|
|
109
|
+
if url:
|
|
110
|
+
# 使用指定URL
|
|
111
|
+
return _open(url, reg)
|
|
112
|
+
else:
|
|
113
|
+
# 使用多个API自动重试
|
|
114
|
+
return _try_multiple_apis(PUBLIC_IPV6_APIS, reg, "IPv6")
|
|
70
115
|
|
|
71
116
|
|
|
72
117
|
def _ip_regex_match(parrent_regex, match_regex):
|
|
73
|
-
|
|
74
118
|
ip_pattern = compile(parrent_regex)
|
|
75
119
|
matcher = compile(match_regex)
|
|
76
120
|
|
ddns/provider/_base.py
CHANGED
|
@@ -58,7 +58,7 @@ Defines a unified interface to support extension and adaptation across providers
|
|
|
58
58
|
from abc import ABCMeta, abstractmethod
|
|
59
59
|
from json import loads as jsondecode, dumps as jsonencode
|
|
60
60
|
from logging import Logger, getLogger # noqa:F401 # type: ignore[no-redef]
|
|
61
|
-
from ..util.http import
|
|
61
|
+
from ..util.http import request, quote, urlencode
|
|
62
62
|
|
|
63
63
|
TYPE_FORM = "application/x-www-form-urlencoded"
|
|
64
64
|
TYPE_JSON = "application/json"
|
|
@@ -102,13 +102,11 @@ class SimpleProvider(object):
|
|
|
102
102
|
accept = TYPE_JSON # type: str | None
|
|
103
103
|
# Decode Response as JSON by default
|
|
104
104
|
decode_response = True # type: bool
|
|
105
|
-
# UA 可自定义, 可在子类中覆盖,空则不设置
|
|
106
|
-
user_agent = "DDNS/{version} (ddns@newfuture.cc)"
|
|
107
105
|
# Description
|
|
108
106
|
remark = "Managed by [DDNS](https://ddns.newfuture.cc)"
|
|
109
107
|
|
|
110
|
-
def __init__(self, id, token, logger=None,
|
|
111
|
-
# type: (str, str, Logger | None, bool|str,
|
|
108
|
+
def __init__(self, id, token, logger=None, ssl="auto", proxy=None, endpoint=None, **options):
|
|
109
|
+
# type: (str, str, Logger | None, bool|str, list[str]|None, str|None, **object) -> None
|
|
112
110
|
"""
|
|
113
111
|
初始化服务商对象
|
|
114
112
|
|
|
@@ -117,14 +115,18 @@ class SimpleProvider(object):
|
|
|
117
115
|
Args:
|
|
118
116
|
id (str): 身份认证 ID / Authentication ID
|
|
119
117
|
token (str): 密钥 / Authentication Token
|
|
118
|
+
proxy (list[str | None] | None): 代理配置,支持代理列表
|
|
120
119
|
options (dict): 其它参数 / Additional options
|
|
121
120
|
"""
|
|
122
121
|
self.id = id
|
|
123
122
|
self.token = token
|
|
124
123
|
if endpoint:
|
|
125
124
|
self.endpoint = endpoint
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
|
|
126
|
+
# 处理代理配置
|
|
127
|
+
self._proxy = proxy # 代理列表或None
|
|
128
|
+
|
|
129
|
+
self._ssl = ssl
|
|
128
130
|
|
|
129
131
|
self.options = options
|
|
130
132
|
name = self.__class__.__name__
|
|
@@ -227,26 +229,11 @@ class SimpleProvider(object):
|
|
|
227
229
|
# 处理headers
|
|
228
230
|
if self.accept and "accept" not in headers and "Accept" not in headers:
|
|
229
231
|
headers["accept"] = self.accept
|
|
230
|
-
if
|
|
231
|
-
headers["user-agent"] = self.user_agent
|
|
232
|
-
if len(headers) > 3:
|
|
232
|
+
if len(headers) > 2:
|
|
233
233
|
self.logger.debug("headers:\n%s", {k: self._mask_sensitive_data(v) for k, v in headers.items()})
|
|
234
234
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if p:
|
|
238
|
-
self.logger.debug("Using proxy: %s", p)
|
|
239
|
-
try:
|
|
240
|
-
response = send_http_request(
|
|
241
|
-
method, url, body=body_data, headers=headers, proxy=p, verify_ssl=self._ssl
|
|
242
|
-
)
|
|
243
|
-
break # 成功发送请求,跳出循环
|
|
244
|
-
except Exception as e:
|
|
245
|
-
self.logger.warning("Failed to send request: %s", e)
|
|
246
|
-
if not response:
|
|
247
|
-
if len(self._proxy) > 1:
|
|
248
|
-
self.logger.error("Failed to send request via all proxies: %s", self._proxy)
|
|
249
|
-
raise RuntimeError("Failed to send request to {}".format(url))
|
|
235
|
+
# 直接传递代理列表给request函数
|
|
236
|
+
response = request(method, url, body_data, headers=headers, proxies=self._proxy, verify=self._ssl, retries=2)
|
|
250
237
|
# 处理响应
|
|
251
238
|
status_code = response.status
|
|
252
239
|
if not (200 <= status_code < 300):
|
|
@@ -257,7 +244,7 @@ class SimpleProvider(object):
|
|
|
257
244
|
if status_code >= 500 or status_code in (400, 401, 403):
|
|
258
245
|
self.logger.error("HTTP error:\n%s", res)
|
|
259
246
|
if status_code == 400:
|
|
260
|
-
raise RuntimeError("
|
|
247
|
+
raise RuntimeError("参数错误 [400]: " + response.reason)
|
|
261
248
|
elif status_code == 401:
|
|
262
249
|
raise RuntimeError("认证失败 [401]: " + response.reason)
|
|
263
250
|
elif status_code == 403:
|
ddns/provider/alidns.py
CHANGED
|
@@ -5,9 +5,10 @@ AliDNS API
|
|
|
5
5
|
@author: NewFuture
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from
|
|
8
|
+
from time import gmtime, strftime, time
|
|
9
|
+
|
|
10
|
+
from ._base import TYPE_FORM, BaseProvider, encode_params, join_domain
|
|
9
11
|
from ._signature import hmac_sha256_authorization, sha256_hash
|
|
10
|
-
from time import strftime, gmtime, time
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class AliBaseProvider(BaseProvider):
|
|
@@ -116,7 +117,7 @@ class AlidnsProvider(AliBaseProvider):
|
|
|
116
117
|
TTL=ttl,
|
|
117
118
|
Line=line,
|
|
118
119
|
**extra
|
|
119
|
-
)
|
|
120
|
+
) # fmt: skip
|
|
120
121
|
if data and data.get("RecordId"):
|
|
121
122
|
self.logger.info("Record created: %s", data)
|
|
122
123
|
return True
|
|
@@ -143,7 +144,7 @@ class AlidnsProvider(AliBaseProvider):
|
|
|
143
144
|
TTL=ttl,
|
|
144
145
|
Line=line or old_record.get("Line"),
|
|
145
146
|
**extra
|
|
146
|
-
)
|
|
147
|
+
) # fmt: skip
|
|
147
148
|
if data and data.get("RecordId"):
|
|
148
149
|
self.logger.info("Record updated: %s", data)
|
|
149
150
|
return True
|
ddns/provider/aliesa.py
CHANGED
|
@@ -5,10 +5,11 @@ AliESA API
|
|
|
5
5
|
@author: NewFuture, GitHub Copilot
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from .alidns import AliBaseProvider
|
|
9
|
-
from ._base import join_domain, TYPE_JSON
|
|
10
8
|
from time import strftime
|
|
11
9
|
|
|
10
|
+
from ._base import TYPE_JSON, join_domain
|
|
11
|
+
from .alidns import AliBaseProvider
|
|
12
|
+
|
|
12
13
|
|
|
13
14
|
class AliesaProvider(AliBaseProvider):
|
|
14
15
|
"""阿里云边缘安全加速(ESA) DNS Provider"""
|
|
@@ -82,7 +83,7 @@ class AliesaProvider(AliBaseProvider):
|
|
|
82
83
|
Data={"Value": value},
|
|
83
84
|
Ttl=ttl or 1,
|
|
84
85
|
**extra
|
|
85
|
-
)
|
|
86
|
+
) # fmt: skip
|
|
86
87
|
|
|
87
88
|
if data and data.get("RecordId"):
|
|
88
89
|
self.logger.info("Record created: %s", data)
|
|
@@ -115,7 +116,7 @@ class AliesaProvider(AliBaseProvider):
|
|
|
115
116
|
Data={"Value": value},
|
|
116
117
|
Ttl=ttl,
|
|
117
118
|
**extra
|
|
118
|
-
)
|
|
119
|
+
) # fmt: skip
|
|
119
120
|
|
|
120
121
|
if data and data.get("RecordId"):
|
|
121
122
|
self.logger.info("Record updated: %s", data)
|
ddns/provider/callback.py
CHANGED
ddns/provider/cloudflare.py
CHANGED
|
@@ -4,7 +4,7 @@ CloudFlare API
|
|
|
4
4
|
@author: TongYifan, NewFuture
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from ._base import
|
|
7
|
+
from ._base import TYPE_JSON, BaseProvider, join_domain
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class CloudflareProvider(BaseProvider):
|
|
@@ -92,7 +92,7 @@ class CloudflareProvider(BaseProvider):
|
|
|
92
92
|
content=value,
|
|
93
93
|
ttl=ttl,
|
|
94
94
|
**extra
|
|
95
|
-
)
|
|
95
|
+
) # fmt: skip
|
|
96
96
|
self.logger.debug("Record updated: %s", data)
|
|
97
97
|
if data:
|
|
98
98
|
return True
|
ddns/provider/debug.py
CHANGED
ddns/provider/dnscom.py
CHANGED
|
@@ -5,10 +5,11 @@ www.51dns.com (原dns.com)
|
|
|
5
5
|
@author: Bigjin<i@bigjin.com>, NewFuture
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from ._base import BaseProvider, TYPE_FORM, encode_params
|
|
9
8
|
from hashlib import md5
|
|
10
9
|
from time import time
|
|
11
10
|
|
|
11
|
+
from ._base import TYPE_FORM, BaseProvider, encode_params
|
|
12
|
+
|
|
12
13
|
|
|
13
14
|
class DnscomProvider(BaseProvider):
|
|
14
15
|
"""
|
|
@@ -82,7 +83,7 @@ class DnscomProvider(BaseProvider):
|
|
|
82
83
|
TTL=ttl,
|
|
83
84
|
viewID=line,
|
|
84
85
|
**extra
|
|
85
|
-
)
|
|
86
|
+
) # fmt: skip
|
|
86
87
|
if res and res.get("recordID"):
|
|
87
88
|
self.logger.info("Record created: %s", res)
|
|
88
89
|
return True
|
ddns/provider/dnspod.py
CHANGED
|
@@ -101,9 +101,7 @@ class DnspodProvider(BaseProvider):
|
|
|
101
101
|
def _query_record(self, zone_id, subdomain, main_domain, record_type, line, extra):
|
|
102
102
|
# type: (str, str, str, str, str | None, dict) -> dict | None
|
|
103
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
|
-
)
|
|
104
|
+
res = self._request("Record.List", domain_id=zone_id, sub_domain=subdomain, record_type=record_type, line=line)
|
|
107
105
|
# length="3000"
|
|
108
106
|
records = res.get("records", [])
|
|
109
107
|
n = len(records)
|
ddns/provider/edgeone.py
CHANGED
ddns/provider/huaweidns.py
CHANGED
|
@@ -5,9 +5,10 @@ HuaweiDNS API
|
|
|
5
5
|
@author: NewFuture
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from
|
|
8
|
+
from time import gmtime, strftime
|
|
9
|
+
|
|
10
|
+
from ._base import TYPE_JSON, BaseProvider, encode_params, join_domain
|
|
9
11
|
from ._signature import hmac_sha256_authorization, sha256_hash
|
|
10
|
-
from time import strftime, gmtime
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class HuaweiDNSProvider(BaseProvider):
|
|
@@ -114,7 +115,7 @@ class HuaweiDNSProvider(BaseProvider):
|
|
|
114
115
|
ttl=ttl,
|
|
115
116
|
line=line,
|
|
116
117
|
**extra
|
|
117
|
-
)
|
|
118
|
+
) # fmt: skip
|
|
118
119
|
if res and res.get("id"):
|
|
119
120
|
self.logger.info("Record created: %s", res)
|
|
120
121
|
return True
|
|
@@ -134,7 +135,7 @@ class HuaweiDNSProvider(BaseProvider):
|
|
|
134
135
|
records=[value],
|
|
135
136
|
ttl=ttl if ttl is not None else old_record.get("ttl"),
|
|
136
137
|
**extra
|
|
137
|
-
)
|
|
138
|
+
) # fmt: skip
|
|
138
139
|
if res and res.get("id"):
|
|
139
140
|
self.logger.info("Record updated: %s", res)
|
|
140
141
|
return True
|
ddns/provider/noip.py
CHANGED
|
@@ -4,8 +4,7 @@ No-IP (noip.com) Dynamic DNS API
|
|
|
4
4
|
@author: GitHub Copilot
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
from ._base import SimpleProvider, TYPE_FORM
|
|
7
|
+
from ._base import SimpleProvider, TYPE_FORM, quote
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
class NoipProvider(SimpleProvider):
|
|
@@ -24,13 +23,23 @@ class NoipProvider(SimpleProvider):
|
|
|
24
23
|
|
|
25
24
|
def _validate(self):
|
|
26
25
|
"""
|
|
27
|
-
Validate authentication credentials for No-IP
|
|
26
|
+
Validate authentication credentials for No-IP and update endpoint with auth
|
|
28
27
|
"""
|
|
28
|
+
# Check endpoint first
|
|
29
|
+
if not self.endpoint or "://" not in self.endpoint:
|
|
30
|
+
raise ValueError("API endpoint must be defined and contain protocol")
|
|
31
|
+
|
|
29
32
|
if not self.id:
|
|
30
33
|
raise ValueError("No-IP requires username as 'id'")
|
|
31
34
|
if not self.token:
|
|
32
35
|
raise ValueError("No-IP requires password as 'token'")
|
|
33
36
|
|
|
37
|
+
# Update endpoint with URL-encoded auth credentials
|
|
38
|
+
protocol, domain = self.endpoint.split("://", 1)
|
|
39
|
+
self.endpoint = "{0}://{1}:{2}@{3}".format(
|
|
40
|
+
protocol, quote(self.id, safe=""), quote(self.token, safe=""), domain
|
|
41
|
+
)
|
|
42
|
+
|
|
34
43
|
def set_record(self, domain, value, record_type="A", ttl=None, line=None, **extra):
|
|
35
44
|
"""
|
|
36
45
|
Update DNS record using No-IP Dynamic Update API
|
|
@@ -59,21 +68,10 @@ class NoipProvider(SimpleProvider):
|
|
|
59
68
|
# Prepare request parameters
|
|
60
69
|
params = {"hostname": domain, "myip": value}
|
|
61
70
|
|
|
62
|
-
# Prepare HTTP Basic Authentication headers
|
|
63
|
-
auth_string = "{0}:{1}".format(self.id, self.token)
|
|
64
|
-
if not isinstance(auth_string, bytes): # Python 3
|
|
65
|
-
auth_bytes = auth_string.encode("utf-8")
|
|
66
|
-
else: # Python 2
|
|
67
|
-
auth_bytes = auth_string
|
|
68
|
-
|
|
69
|
-
auth_b64 = base64.b64encode(auth_bytes).decode("ascii")
|
|
70
|
-
headers = {
|
|
71
|
-
"Authorization": "Basic {0}".format(auth_b64),
|
|
72
|
-
}
|
|
73
|
-
|
|
74
71
|
try:
|
|
75
72
|
# Use GET request as it's the most common method for DDNS
|
|
76
|
-
|
|
73
|
+
# Endpoint already includes auth credentials from _validate()
|
|
74
|
+
response = self._http("GET", "/nic/update", queries=params)
|
|
77
75
|
|
|
78
76
|
if response is not None:
|
|
79
77
|
response_str = str(response).strip()
|
ddns/provider/tencentcloud.py
CHANGED
|
@@ -5,9 +5,11 @@ Tencent Cloud DNSPod API
|
|
|
5
5
|
|
|
6
6
|
@author: NewFuture
|
|
7
7
|
"""
|
|
8
|
-
|
|
9
|
-
from
|
|
10
|
-
|
|
8
|
+
|
|
9
|
+
from time import gmtime, strftime, time
|
|
10
|
+
|
|
11
|
+
from ._base import TYPE_JSON, BaseProvider
|
|
12
|
+
from ._signature import hmac_sha256, hmac_sha256_authorization, sha256_hash
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class TencentCloudProvider(BaseProvider):
|
|
@@ -45,10 +47,7 @@ class TencentCloudProvider(BaseProvider):
|
|
|
45
47
|
body = self._encode_body(params)
|
|
46
48
|
|
|
47
49
|
# 构建请求头,小写 腾讯云只签名特定头部
|
|
48
|
-
headers = {
|
|
49
|
-
"content-type": self.content_type,
|
|
50
|
-
"host": self.endpoint.split("://", 1)[1].strip("/"),
|
|
51
|
-
}
|
|
50
|
+
headers = {"content-type": self.content_type, "host": self.endpoint.split("://", 1)[1].strip("/")}
|
|
52
51
|
|
|
53
52
|
# 腾讯云特殊的密钥派生过程
|
|
54
53
|
date = strftime("%Y-%m-%d", gmtime())
|
|
@@ -132,7 +131,7 @@ class TencentCloudProvider(BaseProvider):
|
|
|
132
131
|
RecordType=record_type,
|
|
133
132
|
RecordLine=line,
|
|
134
133
|
**extra
|
|
135
|
-
)
|
|
134
|
+
) # fmt: skip
|
|
136
135
|
if not response or "RecordList" not in response:
|
|
137
136
|
self.logger.debug("No records found or query failed")
|
|
138
137
|
return None
|
|
@@ -165,7 +164,7 @@ class TencentCloudProvider(BaseProvider):
|
|
|
165
164
|
RecordLine=line or "默认",
|
|
166
165
|
TTL=int(ttl) if ttl else None,
|
|
167
166
|
**extra
|
|
168
|
-
)
|
|
167
|
+
) # fmt: skip
|
|
169
168
|
if response and "RecordId" in response:
|
|
170
169
|
self.logger.info("Record created successfully with ID: %s", response["RecordId"])
|
|
171
170
|
return True
|
|
@@ -186,7 +185,7 @@ class TencentCloudProvider(BaseProvider):
|
|
|
186
185
|
Value=value,
|
|
187
186
|
TTL=int(ttl) if ttl else None,
|
|
188
187
|
**extra
|
|
189
|
-
)
|
|
188
|
+
) # fmt: skip
|
|
190
189
|
if response and "RecordId" in response:
|
|
191
190
|
self.logger.info("Record updated successfully")
|
|
192
191
|
return True
|