ddns 4.1.0b3__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/__init__.py CHANGED
@@ -6,7 +6,7 @@ ddns Package
6
6
  __description__ = "automatically update DNS records to my IP [域名自动指向本机IP]"
7
7
 
8
8
  # 编译时,版本会被替换
9
- __version__ = "4.1.0b3"
9
+ __version__ = "4.1.0b4"
10
10
 
11
11
  # 时间也会被替换掉
12
- build_date = "2025-08-11T15:00:49Z"
12
+ build_date = "2025-08-16T02:41:26Z"
ddns/__main__.py CHANGED
@@ -4,16 +4,16 @@ 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 sys
10
+ from subprocess import check_output
11
11
 
12
- from .__init__ import __version__, __description__, build_date
13
- from .config import load_configs, Config # noqa: F401
14
- from .provider import get_provider_class, SimpleProvider # noqa: F401
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
19
 
@@ -102,11 +102,16 @@ def run(config):
102
102
 
103
103
 
104
104
  def main():
105
- encode = sys.stdout.encoding
106
- if encode is not None and encode.lower() != "utf-8" and hasattr(sys.stdout, "buffer"):
105
+ stdout = sys.stdout # pythonw 模式无 stdout
106
+ if stdout and stdout.encoding and stdout.encoding.lower() != "utf-8" and hasattr(stdout, "buffer"):
107
107
  # 兼容windows 和部分ASCII编码的老旧系统
108
108
  sys.stdout = TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
109
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
+
110
115
  logger.name = "ddns"
111
116
 
112
117
  # 使用多配置加载器,它会自动处理单个和多个配置
ddns/ip.py CHANGED
@@ -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)
@@ -60,12 +78,40 @@ def _open(url, reg):
60
78
  error(e)
61
79
 
62
80
 
63
- def public_v4(url="https://api-ipv4.ip.sb/ip", reg=IPV4_REG): # 公网IPV4地址
64
- return _open(url, reg)
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")
65
106
 
66
107
 
67
- def public_v6(url="https://api-ipv6.ip.sb/ip", reg=IPV6_REG): # 公网IPV6地址
68
- return _open(url, reg)
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")
69
115
 
70
116
 
71
117
  def _ip_regex_match(parrent_regex, match_regex):
ddns/util/fileio.py CHANGED
@@ -23,7 +23,7 @@ def _ensure_directory_exists(file_path): # type: (str) -> None
23
23
  os.makedirs(directory)
24
24
 
25
25
 
26
- def read_file_safely(file_path, encoding="utf-8", default=None): # type: (str, str, str|None) -> str | None
26
+ def read_file_safely(file_path, encoding="utf-8", default=None): # type: (str, str, str|None) -> str
27
27
  """
28
28
  Safely read file content with UTF-8 encoding, return None if file doesn't exist or can't be read
29
29
 
@@ -37,7 +37,7 @@ def read_file_safely(file_path, encoding="utf-8", default=None): # type: (str,
37
37
  try:
38
38
  return read_file(file_path, encoding)
39
39
  except Exception:
40
- return default
40
+ return default # type: ignore
41
41
 
42
42
 
43
43
  def write_file_safely(file_path, content, encoding="utf-8"): # type: (str, str, str) -> bool
ddns/util/try_run.py ADDED
@@ -0,0 +1,37 @@
1
+ # -*- coding:utf-8 -*-
2
+ """
3
+ Utility: Safe command execution wrapper used across the project.
4
+ Provides a single try_run function with consistent behavior.
5
+ """
6
+
7
+ import subprocess
8
+ import sys
9
+
10
+
11
+ def try_run(command, logger=None, **kwargs):
12
+ # type: (list, object, **object) -> str | None
13
+ """Safely run a subprocess command and return decoded output or None on failure.
14
+
15
+ Args:
16
+ command (list): Command array to execute
17
+ logger (object, optional): Logger instance for debug output
18
+ **kwargs: Additional arguments passed to subprocess.check_output
19
+
20
+ Returns:
21
+ str or None: Command output as string, or None if command failed
22
+
23
+ - Adds a default timeout=60s on Python 3 to avoid hangs
24
+ - Decodes output as text via universal_newlines=True
25
+ - Logs at debug level when logger is provided
26
+ """
27
+ try:
28
+ if sys.version_info[0] >= 3 and "timeout" not in kwargs:
29
+ kwargs["timeout"] = 60
30
+ return subprocess.check_output(command, universal_newlines=True, **kwargs) # type: ignore
31
+ except Exception as e: # noqa: BLE001 - broad for subprocess safety
32
+ if logger is not None:
33
+ try:
34
+ logger.debug("Command failed: %s", e) # type: ignore
35
+ except Exception:
36
+ pass
37
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ddns
3
- Version: 4.1.0b3
3
+ Version: 4.1.0b4
4
4
  Summary: Dynamic DNS client for multiple providers, supporting IPv4 and IPv6.
5
5
  Author-email: NewFuture <python@newfuture.cc>
6
6
  License-Expression: MIT
@@ -138,7 +138,7 @@ Dynamic: license-file
138
138
  也可使用一键安装脚本自动下载并安装对应平台的二进制:
139
139
 
140
140
  ```bash
141
- curl -fSL https://ddns.newfuture.cc/install.sh | sh
141
+ curl -#fSL https://ddns.newfuture.cc/install.sh | sh
142
142
  ```
143
143
  提示:安装到系统目录(如 /usr/local/bin)可能需要 root 或 sudo 权限;若权限不足,可改为 `sudo sh` 运行。
144
144
 
@@ -1,8 +1,8 @@
1
1
  ddns/__builtins__.pyi,sha256=9ZCOh51Aq6nBUZTt_0TBKpzcUeTxi-KBw-9TkXIGSH4,128
2
- ddns/__init__.py,sha256=vLLUdvQpiifrwKcrJcQ0Qxcb0jIQOtbY4qMRVvek5a8,258
3
- ddns/__main__.py,sha256=Ie1Lnnh6fjLPJKLayFbVjLxCKw5lm-0JVab1FjeEj2E,5217
2
+ ddns/__init__.py,sha256=KJr4XPaFaRCOLq4Fwvs0mOh6lIRhExsFH6w4ms402u8,258
3
+ ddns/__main__.py,sha256=VgANBMJ01nI5-nRYyKkIcLG0GOO_AKjHRdqVV0cwLrk,5366
4
4
  ddns/cache.py,sha256=A1s3rnOJbPrGEjbGbbISrVg46lZFzZ84WPdzHaCtYBk,5971
5
- ddns/ip.py,sha256=RjHeU54gRV8k8vXbMYFpe6ucq-FCox-KKlAUG3eyy8Q,3886
5
+ ddns/ip.py,sha256=6H5jYv-TT-o0wSoFAGOlTAbf-TAS92Kx6vafi3LXTA4,5151
6
6
  ddns/provider/__init__.py,sha256=qAw-R-l7nUA9L96Tmr2n-T7_g8T9Mah7u615yg2zScY,2793
7
7
  ddns/provider/_base.py,sha256=q31rqiiqEmM5Kt7SLdRjcREDrlGdRAQX6mNoe8ULPz8,20065
8
8
  ddns/provider/_signature.py,sha256=fF8XxMDkXjia96d1gIVYWc72MVVe65AAnD7qr51qKXA,4140
@@ -22,11 +22,12 @@ ddns/provider/noip.py,sha256=RivoLhWJJJuiBzEbtRnnHiBkcLfbvAGglZKKY9s_lcY,4035
22
22
  ddns/provider/tencentcloud.py,sha256=aEWqQ00EWUG-JWW2D0IuJDWZ1OdbTPheGeYSlIh7bqQ,7159
23
23
  ddns/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  ddns/util/comment.py,sha256=_B8sRpCJksNspsdld4ha03W1TAi-MyPRK7pL7ym0j9k,2199
25
- ddns/util/fileio.py,sha256=5foYm8DeTFC0-T3HumiD9gBNNdowsGqbxj0eX5YEhPc,3160
25
+ ddns/util/fileio.py,sha256=bykJgmdy3gZ79iUQSVpMprRej1PGMJSDSHedIngFdG4,3169
26
26
  ddns/util/http.py,sha256=qxfvpN0xt7GZ-GsHFhij4yjH0frWAC52XfaX5ZigFMw,12942
27
- ddns-4.1.0b3.dist-info/licenses/LICENSE,sha256=MI-ECjp-Vl7WZLiSPY6r5VwrOReNiICVB1QCXiUGt_s,1111
28
- ddns-4.1.0b3.dist-info/METADATA,sha256=nmFqWzfvMDNOMyDimNU2miZJrajSkabH4RDOO0rH7hM,19635
29
- ddns-4.1.0b3.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
30
- ddns-4.1.0b3.dist-info/entry_points.txt,sha256=2-VbA-WZcjebkZrGKvUCuBBRYF4xQNMoLIoGaS234WU,44
31
- ddns-4.1.0b3.dist-info/top_level.txt,sha256=Se0wn3T8Bc4pj55dGwVrCe8BFwmFCBwQVHF1bTyV0o0,5
32
- ddns-4.1.0b3.dist-info/RECORD,,
27
+ ddns/util/try_run.py,sha256=juDPxvT5xUabK2DiQNtFIyaATehtserHCVkP7_joSyU,1304
28
+ ddns-4.1.0b4.dist-info/licenses/LICENSE,sha256=MI-ECjp-Vl7WZLiSPY6r5VwrOReNiICVB1QCXiUGt_s,1111
29
+ ddns-4.1.0b4.dist-info/METADATA,sha256=CtRAgOz4l9v8t-l5QxIgBWV2tF2jGtglSfeEbbOC4yg,19636
30
+ ddns-4.1.0b4.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
31
+ ddns-4.1.0b4.dist-info/entry_points.txt,sha256=2-VbA-WZcjebkZrGKvUCuBBRYF4xQNMoLIoGaS234WU,44
32
+ ddns-4.1.0b4.dist-info/top_level.txt,sha256=Se0wn3T8Bc4pj55dGwVrCe8BFwmFCBwQVHF1bTyV0o0,5
33
+ ddns-4.1.0b4.dist-info/RECORD,,
File without changes