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 ADDED
@@ -0,0 +1,5 @@
1
+ # coding=utf-8
2
+ # flake8: noqa: F401
3
+ from typing import *
4
+ from .provider import SimpleProvider
5
+ import logging
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.0.1"
9
+ __version__ = "4.1.0b1"
10
10
 
11
11
  # 时间也会被替换掉
12
- build_date = "2025-06-23T16:56:46Z"
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) New Future (MIT License)
19
- """.format(__version__, build_date)
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: New Future
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, info, warning, error, debug, INFO
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 isinstance(value, str):
31
- return value.strip().lower() in ['false', 'none']
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('cmd:'): # cmd
54
- value = str(check_output(index[4:]).strip().decode('utf-8'))
55
- elif index.startswith('shell:'): # shell
56
- value = str(check_output(
57
- index[6:], shell=True).strip().decode('utf-8'))
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('regex:'): # 正则 regex
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 ['DIRECT', 'NONE']):
72
- dns.Config.PROXY = None
71
+ if not proxy or (proxy.upper() in ["DIRECT", "NONE"]):
72
+ dns.set_proxy(None)
73
73
  else:
74
- dns.Config.PROXY = proxy
75
- record_type, domain = kw['record_type'], kw['domain']
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.update_record(domain, kw['ip'], record_type=record_type)
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 = 'ipv' + ip_type
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('; ').replace(',', ';').replace(' ', ';').split(';')
93
+ domains = domains.strip("; ").replace(",", ";").replace(" ", ";").split(";")
94
94
 
95
- index_rule = get_config('index' + ip_type, "default")
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('Fail to get %s address!', ipname)
98
+ error("Fail to get %s address!", ipname)
99
99
  return False
100
100
 
101
- if cache and (address == cache.get(ipname)):
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
- if change_dns_record(dns, proxy_list, domain=domain, ip=address, record_type=record_type):
110
- update_success = True
111
-
112
- if isinstance(cache, dict):
113
- cache[ipname] = update_success and address
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() != 'utf-8' and hasattr(sys.stdout, 'buffer'):
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='utf-8')
126
- sys.stderr = TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
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('log.level')
130
- log_format = get_config('log.format')
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 = '%(asctime)s %(levelname)s [%(module)s.%(funcName)s](%(filename)s:%(lineno)d): %(message)s'
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 = '%(asctime)s %(levelname)s: %(message)s'
143
+ log_format = "%(asctime)s %(levelname)s: %(message)s"
139
144
  else:
140
- log_format = '%(asctime)s %(levelname)s [%(module)s]: %(message)s'
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('log.datefmt', '%Y-%m-%dT%H:%M:%S'),
145
- filename=get_config('log.file'),
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
- info("DDNS[ %s ] run: %s %s", __version__, os_name, sys.platform)
155
+ debug("DDNS[ %s ] run: %s %s", __version__, os_name, sys.platform)
149
156
 
150
- # Dynamically import the dns module as configuration
151
- dns_provider = str(get_config('dns', 'dnspod').lower())
152
- # dns_module = __import__(
153
- # '.dns', fromlist=[dns_provider], package=__package__)
154
- dns = getattr(__import__('ddns.provider', fromlist=[dns_provider]), dns_provider)
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('loaded Config from: %s', path.abspath(get_config('config')))
164
+ info("loaded Config from: %s", path.abspath(get_config("config"))) # type: ignore
162
165
 
163
- proxy = get_config('proxy') or 'DIRECT'
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('cache', True)
169
+ cache_config = get_config("cache", True) # type: bool | str # type: ignore
168
170
  if cache_config is False:
169
- cache = cache_config
171
+ cache = None
170
172
  elif cache_config is True:
171
- cache = Cache(path.join(gettempdir(), 'ddns.cache'))
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 False:
176
- info('Cache is disabled!')
177
- elif not get_config('config_modified_time') or get_config('config_modified_time') >= cache.time:
178
- warning('Cache file is outdated.')
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('Cache is empty.')
182
- update_ip('4', cache, dns, proxy_list)
183
- update_ip('6', cache, dns, proxy_list)
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__ == '__main__':
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]