ddns 4.0.2__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.2.dist-info → ddns-4.1.0b1.dist-info}/METADATA +96 -71
- ddns-4.1.0b1.dist-info/RECORD +26 -0
- ddns-4.0.2.dist-info/RECORD +0 -21
- {ddns-4.0.2.dist-info → ddns-4.1.0b1.dist-info}/WHEEL +0 -0
- {ddns-4.0.2.dist-info → ddns-4.1.0b1.dist-info}/entry_points.txt +0 -0
- {ddns-4.0.2.dist-info → ddns-4.1.0b1.dist-info}/licenses/LICENSE +0 -0
- {ddns-4.0.2.dist-info → ddns-4.1.0b1.dist-info}/top_level.txt +0 -0
ddns/__builtins__.pyi
ADDED
ddns/__init__.py
CHANGED
|
@@ -6,14 +6,16 @@ ddns Package
|
|
|
6
6
|
__description__ = "automatically update DNS records to my IP [域名自动指向本机IP]"
|
|
7
7
|
|
|
8
8
|
# 编译时,版本会被替换
|
|
9
|
-
__version__ = "4.
|
|
9
|
+
__version__ = "4.1.0b1"
|
|
10
10
|
|
|
11
11
|
# 时间也会被替换掉
|
|
12
|
-
build_date = "2025-
|
|
12
|
+
build_date = "2025-07-03T13:20:03Z"
|
|
13
13
|
|
|
14
14
|
__doc__ = """
|
|
15
15
|
ddns [v{}@{}]
|
|
16
16
|
(i) homepage or docs [文档主页]: https://ddns.newfuture.cc/
|
|
17
17
|
(?) issues or bugs [问题和反馈]: https://github.com/NewFuture/DDNS/issues
|
|
18
|
-
Copyright (c)
|
|
19
|
-
""".format(
|
|
18
|
+
Copyright (c) NewFuture (MIT License)
|
|
19
|
+
""".format(
|
|
20
|
+
__version__, build_date
|
|
21
|
+
)
|
ddns/__main__.py
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
# -*- coding:utf-8 -*-
|
|
2
2
|
"""
|
|
3
3
|
DDNS
|
|
4
|
-
@author:
|
|
5
|
-
@modified: rufengsuixing
|
|
4
|
+
@author: NewFuture, rufengsuixing
|
|
6
5
|
"""
|
|
7
6
|
|
|
8
7
|
from os import path, environ, name as os_name
|
|
9
8
|
from io import TextIOWrapper
|
|
10
9
|
from subprocess import check_output
|
|
11
10
|
from tempfile import gettempdir
|
|
12
|
-
from logging import basicConfig,
|
|
11
|
+
from logging import basicConfig, getLogger, info, error, debug, warning, INFO
|
|
13
12
|
|
|
14
13
|
import sys
|
|
15
14
|
|
|
@@ -17,6 +16,7 @@ from .__init__ import __version__, __description__, __doc__, build_date
|
|
|
17
16
|
from .util import ip
|
|
18
17
|
from .util.cache import Cache
|
|
19
18
|
from .util.config import init_config, get_config
|
|
19
|
+
from .provider import get_provider_class, SimpleProvider # noqa: F401
|
|
20
20
|
|
|
21
21
|
environ["DDNS_VERSION"] = __version__
|
|
22
22
|
|
|
@@ -27,8 +27,8 @@ def is_false(value):
|
|
|
27
27
|
字符串 'false', 或者 False, 或者 'none';
|
|
28
28
|
0 不是 False
|
|
29
29
|
"""
|
|
30
|
-
if
|
|
31
|
-
return value.strip().lower() in [
|
|
30
|
+
if hasattr(value, "strip"): # 字符串
|
|
31
|
+
return value.strip().lower() in ["false", "none"]
|
|
32
32
|
return value is False
|
|
33
33
|
|
|
34
34
|
|
|
@@ -50,14 +50,13 @@ def get_ip(ip_type, index="default"):
|
|
|
50
50
|
break
|
|
51
51
|
elif str(index).isdigit(): # 数字 local eth
|
|
52
52
|
value = getattr(ip, "local_v" + ip_type)(index)
|
|
53
|
-
elif index.startswith(
|
|
54
|
-
value = str(check_output(index[4:]).strip().decode(
|
|
55
|
-
elif index.startswith(
|
|
56
|
-
value = str(check_output(
|
|
57
|
-
|
|
58
|
-
elif index.startswith('url:'): # 自定义 url
|
|
53
|
+
elif index.startswith("cmd:"): # cmd
|
|
54
|
+
value = str(check_output(index[4:]).strip().decode("utf-8"))
|
|
55
|
+
elif index.startswith("shell:"): # shell
|
|
56
|
+
value = str(check_output(index[6:], shell=True).strip().decode("utf-8"))
|
|
57
|
+
elif index.startswith("url:"): # 自定义 url
|
|
59
58
|
value = getattr(ip, "public_v" + ip_type)(index[4:])
|
|
60
|
-
elif index.startswith(
|
|
59
|
+
elif index.startswith("regex:"): # 正则 regex
|
|
61
60
|
value = getattr(ip, "regex_v" + ip_type)(index[6:])
|
|
62
61
|
else:
|
|
63
62
|
value = getattr(ip, index + "_v" + ip_type)()
|
|
@@ -67,50 +66,56 @@ def get_ip(ip_type, index="default"):
|
|
|
67
66
|
|
|
68
67
|
|
|
69
68
|
def change_dns_record(dns, proxy_list, **kw):
|
|
69
|
+
# type: (SimpleProvider, list, **(str)) -> bool
|
|
70
70
|
for proxy in proxy_list:
|
|
71
|
-
if not proxy or (proxy.upper() in [
|
|
72
|
-
dns.
|
|
71
|
+
if not proxy or (proxy.upper() in ["DIRECT", "NONE"]):
|
|
72
|
+
dns.set_proxy(None)
|
|
73
73
|
else:
|
|
74
|
-
dns.
|
|
75
|
-
record_type, domain = kw[
|
|
76
|
-
info("%s(%s) ==> %s [via %s]", domain, record_type, kw['ip'], proxy)
|
|
74
|
+
dns.set_proxy(proxy)
|
|
75
|
+
record_type, domain = kw["record_type"], kw["domain"]
|
|
77
76
|
try:
|
|
78
|
-
return dns.
|
|
77
|
+
return dns.set_record(domain, kw["ip"], record_type=record_type, ttl=kw["ttl"], line=kw.get("line"))
|
|
79
78
|
except Exception as e:
|
|
80
79
|
error("Failed to update %s record for %s: %s", record_type, domain, e)
|
|
81
80
|
return False
|
|
82
81
|
|
|
83
82
|
|
|
84
|
-
def update_ip(ip_type, cache, dns, proxy_list):
|
|
83
|
+
def update_ip(ip_type, cache, dns, ttl, line, proxy_list):
|
|
84
|
+
# type: (str, Cache | None, SimpleProvider, str, str | None, list[str]) -> bool | None
|
|
85
85
|
"""
|
|
86
86
|
更新IP
|
|
87
87
|
"""
|
|
88
|
-
ipname =
|
|
88
|
+
ipname = "ipv" + ip_type
|
|
89
89
|
domains = get_config(ipname)
|
|
90
90
|
if not domains:
|
|
91
91
|
return None
|
|
92
92
|
if not isinstance(domains, list):
|
|
93
|
-
domains = domains.strip(
|
|
93
|
+
domains = domains.strip("; ").replace(",", ";").replace(" ", ";").split(";")
|
|
94
94
|
|
|
95
|
-
index_rule = get_config(
|
|
95
|
+
index_rule = get_config("index" + ip_type, "default") # type: str # type: ignore
|
|
96
96
|
address = get_ip(ip_type, index_rule)
|
|
97
97
|
if not address:
|
|
98
|
-
error(
|
|
98
|
+
error("Fail to get %s address!", ipname)
|
|
99
99
|
return False
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
info('%s address not changed, using cache.', ipname)
|
|
103
|
-
return True
|
|
104
|
-
|
|
105
|
-
record_type = 'A' if ip_type == '4' else 'AAAA'
|
|
101
|
+
record_type = "A" if ip_type == "4" else "AAAA"
|
|
106
102
|
update_success = False
|
|
103
|
+
|
|
104
|
+
# Check cache and update each domain individually
|
|
107
105
|
for domain in domains:
|
|
108
106
|
domain = domain.lower()
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
cache_key = "{}:{}".format(domain, record_type)
|
|
108
|
+
if cache and cache.get(cache_key) == address:
|
|
109
|
+
info("%s[%s] address not changed, using cache: %s", domain, record_type, address)
|
|
110
|
+
update_success = True # At least one domain is successfully cached
|
|
111
|
+
else:
|
|
112
|
+
# Update domain that is not cached or has different IP
|
|
113
|
+
if change_dns_record(dns, proxy_list, domain=domain, ip=address, record_type=record_type, ttl=ttl, line=line):
|
|
114
|
+
warning("set %s[IPv%s]: %s successfully.", domain, ip_type, address)
|
|
115
|
+
update_success = True
|
|
116
|
+
# Cache successful update immediately
|
|
117
|
+
if isinstance(cache, dict):
|
|
118
|
+
cache[cache_key] = address
|
|
114
119
|
|
|
115
120
|
return update_success
|
|
116
121
|
|
|
@@ -120,68 +125,69 @@ def main():
|
|
|
120
125
|
更新
|
|
121
126
|
"""
|
|
122
127
|
encode = sys.stdout.encoding
|
|
123
|
-
if encode is not None and encode.lower() !=
|
|
128
|
+
if encode is not None and encode.lower() != "utf-8" and hasattr(sys.stdout, "buffer"):
|
|
124
129
|
# 兼容windows 和部分ASCII编码的老旧系统
|
|
125
|
-
sys.stdout = TextIOWrapper(sys.stdout.buffer, encoding=
|
|
126
|
-
sys.stderr = TextIOWrapper(sys.stderr.buffer, encoding=
|
|
130
|
+
sys.stdout = TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
|
131
|
+
sys.stderr = TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
|
127
132
|
init_config(__description__, __doc__, __version__, build_date)
|
|
128
133
|
|
|
129
|
-
log_level = get_config(
|
|
130
|
-
log_format = get_config(
|
|
134
|
+
log_level = get_config("log.level", INFO) # type: int # type: ignore
|
|
135
|
+
log_format = get_config("log.format") # type: str | None # type: ignore
|
|
131
136
|
if log_format:
|
|
132
137
|
# A custom log format is already set; no further action is required.
|
|
133
138
|
pass
|
|
134
139
|
elif log_level < INFO:
|
|
135
140
|
# Override log format in debug mode to include filename and line number for detailed debugging
|
|
136
|
-
log_format =
|
|
141
|
+
log_format = "%(asctime)s %(levelname)s [%(name)s.%(funcName)s](%(filename)s:%(lineno)d): %(message)s"
|
|
137
142
|
elif log_level > INFO:
|
|
138
|
-
log_format =
|
|
143
|
+
log_format = "%(asctime)s %(levelname)s: %(message)s"
|
|
139
144
|
else:
|
|
140
|
-
log_format =
|
|
145
|
+
log_format = "%(asctime)s %(levelname)s [%(name)s]: %(message)s"
|
|
141
146
|
basicConfig(
|
|
142
147
|
level=log_level,
|
|
143
148
|
format=log_format,
|
|
144
|
-
datefmt=get_config(
|
|
145
|
-
filename=get_config(
|
|
149
|
+
datefmt=get_config("log.datefmt", "%Y-%m-%dT%H:%M:%S"), # type: ignore
|
|
150
|
+
filename=get_config("log.file"), # type: ignore
|
|
146
151
|
)
|
|
152
|
+
logger = getLogger()
|
|
153
|
+
logger.name = "ddns"
|
|
147
154
|
|
|
148
|
-
|
|
155
|
+
debug("DDNS[ %s ] run: %s %s", __version__, os_name, sys.platform)
|
|
149
156
|
|
|
150
|
-
#
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
dns =
|
|
155
|
-
# dns = getattr(dns_module, dns_provider)
|
|
156
|
-
dns.Config.ID = get_config('id')
|
|
157
|
-
dns.Config.TOKEN = get_config('token')
|
|
158
|
-
dns.Config.TTL = get_config('ttl')
|
|
157
|
+
# dns provider class
|
|
158
|
+
dns_name = get_config("dns", "debug") # type: str # type: ignore
|
|
159
|
+
provider_class = get_provider_class(dns_name)
|
|
160
|
+
ssl_config = get_config("ssl", "auto") # type: str | bool # type: ignore
|
|
161
|
+
dns = provider_class(get_config("id"), get_config("token"), logger=logger, verify_ssl=ssl_config) # type: ignore
|
|
159
162
|
|
|
160
163
|
if get_config("config"):
|
|
161
|
-
info(
|
|
164
|
+
info("loaded Config from: %s", path.abspath(get_config("config"))) # type: ignore
|
|
162
165
|
|
|
163
|
-
proxy = get_config(
|
|
164
|
-
proxy_list = proxy if isinstance(
|
|
165
|
-
proxy, list) else proxy.strip(';').replace(',', ';').split(';')
|
|
166
|
+
proxy = get_config("proxy") or "DIRECT"
|
|
167
|
+
proxy_list = proxy if isinstance(proxy, list) else proxy.strip(";").replace(",", ";").split(";")
|
|
166
168
|
|
|
167
|
-
cache_config = get_config(
|
|
169
|
+
cache_config = get_config("cache", True) # type: bool | str # type: ignore
|
|
168
170
|
if cache_config is False:
|
|
169
|
-
cache =
|
|
171
|
+
cache = None
|
|
170
172
|
elif cache_config is True:
|
|
171
|
-
cache = Cache(path.join(gettempdir(),
|
|
173
|
+
cache = Cache(path.join(gettempdir(), "ddns.cache"), logger)
|
|
172
174
|
else:
|
|
173
|
-
cache = Cache(cache_config)
|
|
175
|
+
cache = Cache(cache_config, logger)
|
|
174
176
|
|
|
175
|
-
if cache is
|
|
176
|
-
info(
|
|
177
|
-
elif
|
|
178
|
-
|
|
177
|
+
if cache is None:
|
|
178
|
+
info("Cache is disabled!")
|
|
179
|
+
elif get_config("config_modified_time", float("inf")) >= cache.time: # type: ignore
|
|
180
|
+
info("Cache file is outdated.")
|
|
179
181
|
cache.clear()
|
|
182
|
+
elif len(cache) == 0:
|
|
183
|
+
debug("Cache is empty.")
|
|
180
184
|
else:
|
|
181
|
-
debug(
|
|
182
|
-
|
|
183
|
-
|
|
185
|
+
debug("Cache loaded with %d entries.", len(cache))
|
|
186
|
+
ttl = get_config("ttl") # type: str # type: ignore
|
|
187
|
+
line = get_config("line") # type: str | None # type: ignore
|
|
188
|
+
update_ip("4", cache, dns, ttl, line, proxy_list)
|
|
189
|
+
update_ip("6", cache, dns, ttl, line, proxy_list)
|
|
184
190
|
|
|
185
191
|
|
|
186
|
-
if __name__ ==
|
|
192
|
+
if __name__ == "__main__":
|
|
187
193
|
main()
|
ddns/provider/__init__.py
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
from ._base import SimpleProvider # noqa: F401
|
|
3
|
+
from .alidns import AlidnsProvider
|
|
4
|
+
from .callback import CallbackProvider
|
|
5
|
+
from .cloudflare import CloudflareProvider
|
|
6
|
+
from .dnscom import DnscomProvider
|
|
7
|
+
from .dnspod import DnspodProvider
|
|
8
|
+
from .dnspod_com import DnspodComProvider
|
|
9
|
+
from .he import HeProvider
|
|
10
|
+
from .huaweidns import HuaweiDNSProvider
|
|
11
|
+
from .tencentcloud import TencentCloudProvider
|
|
12
|
+
from .debug import DebugProvider
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_provider_class(provider_name):
|
|
16
|
+
# type: (str) -> type[SimpleProvider]
|
|
17
|
+
"""
|
|
18
|
+
获取指定的DNS提供商类
|
|
19
|
+
|
|
20
|
+
:param provider_name: 提供商名称
|
|
21
|
+
:return: 对应的DNS提供商类
|
|
22
|
+
"""
|
|
23
|
+
provider_name = str(provider_name).lower()
|
|
24
|
+
mapping = {
|
|
25
|
+
# dnspod.cn
|
|
26
|
+
"dnspod": DnspodProvider,
|
|
27
|
+
"dnspod_cn": DnspodProvider, # 兼容旧的dnspod_cn
|
|
28
|
+
# dnspod.com
|
|
29
|
+
"dnspod_com": DnspodComProvider,
|
|
30
|
+
"dnspod_global": DnspodComProvider, # 兼容旧的dnspod_global
|
|
31
|
+
# tencent cloud dnspod
|
|
32
|
+
"tencentcloud": TencentCloudProvider,
|
|
33
|
+
"tencent": TencentCloudProvider, # 兼容tencent
|
|
34
|
+
"qcloud": TencentCloudProvider, # 兼容qcloud
|
|
35
|
+
# cloudflare
|
|
36
|
+
"cloudflare": CloudflareProvider,
|
|
37
|
+
# aliyun alidns
|
|
38
|
+
"alidns": AlidnsProvider,
|
|
39
|
+
"aliyun": AlidnsProvider, # 兼容aliyun
|
|
40
|
+
# dns.com
|
|
41
|
+
"dnscom": DnscomProvider,
|
|
42
|
+
"51dns": DnscomProvider, # 兼容旧的51dns
|
|
43
|
+
"dns_com": DnscomProvider, # 兼容旧的dns_com
|
|
44
|
+
# he.net
|
|
45
|
+
"he": HeProvider,
|
|
46
|
+
"he_net": HeProvider, # 兼容he.net
|
|
47
|
+
# huawei
|
|
48
|
+
"huaweidns": HuaweiDNSProvider,
|
|
49
|
+
"huawei": HuaweiDNSProvider, # 兼容huawei
|
|
50
|
+
"huaweicloud": HuaweiDNSProvider,
|
|
51
|
+
# callback
|
|
52
|
+
"callback": CallbackProvider,
|
|
53
|
+
"webhook": CallbackProvider, # 兼容
|
|
54
|
+
"http": CallbackProvider, # 兼容
|
|
55
|
+
# debug
|
|
56
|
+
"print": DebugProvider,
|
|
57
|
+
"debug": DebugProvider, # 兼容print
|
|
58
|
+
}
|
|
59
|
+
return mapping.get(provider_name) # type: ignore[return-value]
|