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 +2 -2
- ddns/__main__.py +12 -7
- ddns/ip.py +50 -4
- ddns/util/fileio.py +2 -2
- ddns/util/try_run.py +37 -0
- {ddns-4.1.0b3.dist-info → ddns-4.1.0b4.dist-info}/METADATA +2 -2
- {ddns-4.1.0b3.dist-info → ddns-4.1.0b4.dist-info}/RECORD +11 -10
- {ddns-4.1.0b3.dist-info → ddns-4.1.0b4.dist-info}/WHEEL +0 -0
- {ddns-4.1.0b3.dist-info → ddns-4.1.0b4.dist-info}/entry_points.txt +0 -0
- {ddns-4.1.0b3.dist-info → ddns-4.1.0b4.dist-info}/licenses/LICENSE +0 -0
- {ddns-4.1.0b3.dist-info → ddns-4.1.0b4.dist-info}/top_level.txt +0 -0
ddns/__init__.py
CHANGED
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
|
|
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
|
-
|
|
106
|
-
if
|
|
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
|
|
64
|
-
|
|
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=
|
|
68
|
-
|
|
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
|
|
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.
|
|
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
|
|
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=
|
|
3
|
-
ddns/__main__.py,sha256=
|
|
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=
|
|
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=
|
|
25
|
+
ddns/util/fileio.py,sha256=bykJgmdy3gZ79iUQSVpMprRej1PGMJSDSHedIngFdG4,3169
|
|
26
26
|
ddns/util/http.py,sha256=qxfvpN0xt7GZ-GsHFhij4yjH0frWAC52XfaX5ZigFMw,12942
|
|
27
|
-
ddns
|
|
28
|
-
ddns-4.1.
|
|
29
|
-
ddns-4.1.
|
|
30
|
-
ddns-4.1.
|
|
31
|
-
ddns-4.1.
|
|
32
|
-
ddns-4.1.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|