ddns 4.0.0b7__tar.gz → 4.0.1__tar.gz

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.

Files changed (31) hide show
  1. {ddns-4.0.0b7 → ddns-4.0.1}/PKG-INFO +28 -27
  2. {ddns-4.0.0b7 → ddns-4.0.1}/README.md +7 -5
  3. ddns-4.0.1/ddns/__init__.py +19 -0
  4. ddns-4.0.0b7/run.py → ddns-4.0.1/ddns/__main__.py +42 -51
  5. {ddns-4.0.0b7/dns → ddns-4.0.1/ddns/provider}/dnspod_com.py +1 -1
  6. ddns-4.0.1/ddns/util/config.py +317 -0
  7. {ddns-4.0.0b7 → ddns-4.0.1}/ddns.egg-info/PKG-INFO +28 -27
  8. ddns-4.0.1/ddns.egg-info/SOURCES.txt +25 -0
  9. ddns-4.0.1/ddns.egg-info/entry_points.txt +2 -0
  10. ddns-4.0.1/ddns.egg-info/requires.txt +4 -0
  11. ddns-4.0.1/ddns.egg-info/top_level.txt +1 -0
  12. ddns-4.0.1/pyproject.toml +114 -0
  13. {ddns-4.0.0b7 → ddns-4.0.1}/setup.cfg +0 -3
  14. ddns-4.0.0b7/ddns.egg-info/SOURCES.txt +0 -23
  15. ddns-4.0.0b7/ddns.egg-info/entry_points.txt +0 -2
  16. ddns-4.0.0b7/ddns.egg-info/top_level.txt +0 -3
  17. ddns-4.0.0b7/setup.py +0 -240
  18. ddns-4.0.0b7/util/config.py +0 -240
  19. {ddns-4.0.0b7 → ddns-4.0.1}/LICENSE +0 -0
  20. {ddns-4.0.0b7/dns → ddns-4.0.1/ddns/provider}/__init__.py +0 -0
  21. {ddns-4.0.0b7/dns → ddns-4.0.1/ddns/provider}/alidns.py +0 -0
  22. {ddns-4.0.0b7/dns → ddns-4.0.1/ddns/provider}/callback.py +0 -0
  23. {ddns-4.0.0b7/dns → ddns-4.0.1/ddns/provider}/cloudflare.py +0 -0
  24. {ddns-4.0.0b7/dns → ddns-4.0.1/ddns/provider}/dnscom.py +0 -0
  25. {ddns-4.0.0b7/dns → ddns-4.0.1/ddns/provider}/dnspod.py +0 -0
  26. {ddns-4.0.0b7/dns → ddns-4.0.1/ddns/provider}/he.py +0 -0
  27. {ddns-4.0.0b7/dns → ddns-4.0.1/ddns/provider}/huaweidns.py +0 -0
  28. {ddns-4.0.0b7 → ddns-4.0.1/ddns}/util/__init__.py +0 -0
  29. {ddns-4.0.0b7 → ddns-4.0.1/ddns}/util/cache.py +0 -0
  30. {ddns-4.0.0b7 → ddns-4.0.1/ddns}/util/ip.py +0 -0
  31. {ddns-4.0.0b7 → ddns-4.0.1}/ddns.egg-info/dependency_links.txt +0 -0
@@ -1,14 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ddns
3
- Version: 4.0.0b7
4
- Summary: automatically update DNS records to my IP [域名自动指向本机IP]
5
- Home-page: https://ddns.newfuture.cc
6
- Author: NewFuture
7
- Author-email: python@newfuture.cc
8
- License: MIT
9
- Project-URL: Bug Reports, https://github.com/NewFuture/DDNS/issues
3
+ Version: 4.0.1
4
+ Summary: Dynamic DNS client for multiple providers, supporting IPv4 and IPv6.
5
+ Author-email: NewFuture <python@newfuture.cc>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://ddns.newfuture.cc
8
+ Project-URL: Documentation, https://ddns.newfuture.cc
9
+ Project-URL: Repository, https://github.com/NewFuture/DDNS
10
+ Project-URL: Bug Tracker, https://github.com/NewFuture/DDNS/issues
10
11
  Project-URL: Source, https://github.com/NewFuture/DDNS
11
- Keywords: ddns ipv6 ipv4 dns dnspod alidns cloudflare
12
+ Keywords: ddns,ipv6,ipv4,dns,dnspod,alidns,cloudflare
12
13
  Platform: any
13
14
  Classifier: Development Status :: 5 - Production/Stable
14
15
  Classifier: Intended Audience :: Developers
@@ -19,27 +20,25 @@ Classifier: Topic :: Internet
19
20
  Classifier: Topic :: Internet :: Name Service (DNS)
20
21
  Classifier: Topic :: System :: Networking
21
22
  Classifier: Topic :: Software Development
22
- Classifier: License :: OSI Approved :: MIT License
23
23
  Classifier: Programming Language :: Python :: 2.7
24
24
  Classifier: Programming Language :: Python :: 3
25
- Requires-Python: >=2.5, <4
25
+ Classifier: Programming Language :: Python :: 3.6
26
+ Classifier: Programming Language :: Python :: 3.7
27
+ Classifier: Programming Language :: Python :: 3.8
28
+ Classifier: Programming Language :: Python :: 3.9
29
+ Classifier: Programming Language :: Python :: 3.10
30
+ Classifier: Programming Language :: Python :: 3.11
31
+ Classifier: Programming Language :: Python :: 3.12
32
+ Classifier: Programming Language :: Python :: 3.13
33
+ Requires-Python: >=2.7
26
34
  Description-Content-Type: text/markdown
27
35
  License-File: LICENSE
28
- Dynamic: author
29
- Dynamic: author-email
30
- Dynamic: classifier
31
- Dynamic: description
32
- Dynamic: description-content-type
33
- Dynamic: home-page
34
- Dynamic: keywords
35
- Dynamic: license
36
+ Provides-Extra: dev
37
+ Requires-Dist: black; extra == "dev"
38
+ Requires-Dist: flake8; extra == "dev"
36
39
  Dynamic: license-file
37
- Dynamic: platform
38
- Dynamic: project-url
39
- Dynamic: requires-python
40
- Dynamic: summary
41
40
 
42
- # [<img src="doc/img/ddns.svg" width="32px" height="32px"/>](https://ddns.newfuture.cc) [DDNS](https://github.com/NewFuture/DDNS)
41
+ # [<img src="https://ddns.newfuture.cc/doc/img/ddns.svg" width="32px" height="32px"/>](https://ddns.newfuture.cc) [DDNS](https://github.com/NewFuture/DDNS)
43
42
 
44
43
  > 自动更新 DNS 解析 到本机 IP 地址,支持 IPv4 和 IPv6,本地(内网)IP 和公网 IP。
45
44
  > 代理模式,支持自动创建 DNS 记录。
@@ -56,13 +55,13 @@ Dynamic: summary
56
55
  ## Features
57
56
 
58
57
  - 兼容和跨平台:
59
- - [Docker (@NN708)](https://hub.docker.com/r/newfuture/ddns) [![Docker Image Size](https://img.shields.io/docker/image-size/newfuture/ddns/latest?logo=docker&style=social)](https://hub.docker.com/r/newfuture/ddns)[![Docker Platforms](https://img.shields.io/badge/arch-amd64%20%7C%20arm64%20%7C%20arm%2Fv7%20%7C%20arm%2Fv6%20%7C%20ppc64le%20%7C%20s390x%20%7C%20386%20%7C%20mips64le-blue?style=social)](https://hub.docker.com/r/newfuture/ddns)
58
+ - [Docker (@NN708)](https://hub.docker.com/r/newfuture/ddns) [![Docker Image Size](https://img.shields.io/docker/image-size/newfuture/ddns/latest?logo=docker&style=social)](https://hub.docker.com/r/newfuture/ddns)[![Docker Platforms](https://img.shields.io/badge/arch-amd64%20%7C%20arm64%20%7C%20arm%2Fv7%20%7C%20arm%2Fv6%20%7C%20ppc64le%20%7C%20s390x%20%7C%20386%20%7C%20riscv64-blue?style=social)](https://hub.docker.com/r/newfuture/ddns)
60
59
  - [二进制文件](https://github.com/NewFuture/DDNS/releases/latest) ![cross platform](https://img.shields.io/badge/system-windows_%7C%20linux_%7C%20mac-success.svg?style=social)
61
60
 
62
61
  - 配置方式:
63
- - [命令行参数](doc/cli.md)
64
- - [JSON 配置文件](doc/json.md)
65
- - [环境变量配置](doc/env.md)
62
+ - [命令行参数](https://ddns.newfuture.cc/doc/cli.html)
63
+ - [JSON 配置文件](https://ddns.newfuture.cc/doc/json.html)
64
+ - [环境变量配置](https://ddns.newfuture.cc/doc/env.html)
66
65
 
67
66
  - 域名支持:
68
67
  - 多个域名支持
@@ -138,6 +137,8 @@ Dynamic: summary
138
137
  newfuture/ddns
139
138
  ```
140
139
 
140
+ 更多详细说明和高级用法请查看 [Docker 使用文档](doc/docker.md)。
141
+
141
142
  ### ② 快速配置
142
143
 
143
144
  1. 申请 api `token`,填写到对应的 `id` 和 `token` 字段:
@@ -1,4 +1,4 @@
1
- # [<img src="doc/img/ddns.svg" width="32px" height="32px"/>](https://ddns.newfuture.cc) [DDNS](https://github.com/NewFuture/DDNS)
1
+ # [<img src="https://ddns.newfuture.cc/doc/img/ddns.svg" width="32px" height="32px"/>](https://ddns.newfuture.cc) [DDNS](https://github.com/NewFuture/DDNS)
2
2
 
3
3
  > 自动更新 DNS 解析 到本机 IP 地址,支持 IPv4 和 IPv6,本地(内网)IP 和公网 IP。
4
4
  > 代理模式,支持自动创建 DNS 记录。
@@ -15,13 +15,13 @@
15
15
  ## Features
16
16
 
17
17
  - 兼容和跨平台:
18
- - [Docker (@NN708)](https://hub.docker.com/r/newfuture/ddns) [![Docker Image Size](https://img.shields.io/docker/image-size/newfuture/ddns/latest?logo=docker&style=social)](https://hub.docker.com/r/newfuture/ddns)[![Docker Platforms](https://img.shields.io/badge/arch-amd64%20%7C%20arm64%20%7C%20arm%2Fv7%20%7C%20arm%2Fv6%20%7C%20ppc64le%20%7C%20s390x%20%7C%20386%20%7C%20mips64le-blue?style=social)](https://hub.docker.com/r/newfuture/ddns)
18
+ - [Docker (@NN708)](https://hub.docker.com/r/newfuture/ddns) [![Docker Image Size](https://img.shields.io/docker/image-size/newfuture/ddns/latest?logo=docker&style=social)](https://hub.docker.com/r/newfuture/ddns)[![Docker Platforms](https://img.shields.io/badge/arch-amd64%20%7C%20arm64%20%7C%20arm%2Fv7%20%7C%20arm%2Fv6%20%7C%20ppc64le%20%7C%20s390x%20%7C%20386%20%7C%20riscv64-blue?style=social)](https://hub.docker.com/r/newfuture/ddns)
19
19
  - [二进制文件](https://github.com/NewFuture/DDNS/releases/latest) ![cross platform](https://img.shields.io/badge/system-windows_%7C%20linux_%7C%20mac-success.svg?style=social)
20
20
 
21
21
  - 配置方式:
22
- - [命令行参数](doc/cli.md)
23
- - [JSON 配置文件](doc/json.md)
24
- - [环境变量配置](doc/env.md)
22
+ - [命令行参数](https://ddns.newfuture.cc/doc/cli.html)
23
+ - [JSON 配置文件](https://ddns.newfuture.cc/doc/json.html)
24
+ - [环境变量配置](https://ddns.newfuture.cc/doc/env.html)
25
25
 
26
26
  - 域名支持:
27
27
  - 多个域名支持
@@ -97,6 +97,8 @@
97
97
  newfuture/ddns
98
98
  ```
99
99
 
100
+ 更多详细说明和高级用法请查看 [Docker 使用文档](doc/docker.md)。
101
+
100
102
  ### ② 快速配置
101
103
 
102
104
  1. 申请 api `token`,填写到对应的 `id` 和 `token` 字段:
@@ -0,0 +1,19 @@
1
+ # -*- coding:utf-8 -*-
2
+ """
3
+ ddns Package
4
+ """
5
+
6
+ __description__ = "automatically update DNS records to my IP [域名自动指向本机IP]"
7
+
8
+ # 编译时,版本会被替换
9
+ __version__ = "4.0.1"
10
+
11
+ # 时间也会被替换掉
12
+ build_date = "2025-06-23T16:56:46Z"
13
+
14
+ __doc__ = """
15
+ ddns [v{}@{}]
16
+ (i) homepage or docs [文档主页]: https://ddns.newfuture.cc/
17
+ (?) issues or bugs [问题和反馈]: https://github.com/NewFuture/DDNS/issues
18
+ Copyright (c) New Future (MIT License)
19
+ """.format(__version__, build_date)
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env python
2
1
  # -*- coding:utf-8 -*-
3
2
  """
4
3
  DDNS
@@ -10,24 +9,16 @@ from os import path, environ, name as os_name
10
9
  from io import TextIOWrapper
11
10
  from subprocess import check_output
12
11
  from tempfile import gettempdir
13
- from logging import basicConfig, info, warning, error, debug, DEBUG, NOTSET
12
+ from logging import basicConfig, info, warning, error, debug, INFO
14
13
 
15
14
  import sys
16
15
 
17
- from util import ip
18
- from util.cache import Cache
19
- from util.config import init_config, get_config
16
+ from .__init__ import __version__, __description__, __doc__, build_date
17
+ from .util import ip
18
+ from .util.cache import Cache
19
+ from .util.config import init_config, get_config
20
20
 
21
- __version__ = "v4.0.0-beta7@2025-06-13T14:46:22+00:00" # CI 时会被Tag替换
22
- __description__ = "automatically update DNS records to my IP [域名自动指向本机IP]"
23
- __doc__ = """
24
- ddns[%s]
25
- (i) homepage or docs [文档主页]: https://ddns.newfuture.cc/
26
- (?) issues or bugs [问题和帮助]: https://github.com/NewFuture/DDNS/issues
27
- Copyright (c) New Future (MIT License)
28
- """ % (__version__)
29
-
30
- environ["DDNS_VERSION"] = "v4.0.0-beta7"
21
+ environ["DDNS_VERSION"] = __version__
31
22
 
32
23
 
33
24
  def is_false(value):
@@ -99,38 +90,54 @@ def update_ip(ip_type, cache, dns, proxy_list):
99
90
  if not domains:
100
91
  return None
101
92
  if not isinstance(domains, list):
102
- domains = domains.strip('; ').replace(
103
- ',', ';').replace(' ', ';').split(';')
104
- index_rule = get_config('index' + ip_type, "default") # 从配置中获取index配置
93
+ domains = domains.strip('; ').replace(',', ';').replace(' ', ';').split(';')
94
+
95
+ index_rule = get_config('index' + ip_type, "default")
105
96
  address = get_ip(ip_type, index_rule)
106
97
  if not address:
107
98
  error('Fail to get %s address!', ipname)
108
99
  return False
109
- elif cache and (address == cache[ipname]):
100
+
101
+ if cache and (address == cache.get(ipname)):
110
102
  info('%s address not changed, using cache.', ipname)
111
103
  return True
112
- record_type = (ip_type == '4') and 'A' or 'AAAA'
113
- update_fail = False # https://github.com/NewFuture/DDNS/issues/16
104
+
105
+ record_type = 'A' if ip_type == '4' else 'AAAA'
106
+ update_success = False
114
107
  for domain in domains:
115
- domain = domain.lower() # https://github.com/NewFuture/DDNS/issues/431
108
+ domain = domain.lower()
116
109
  if change_dns_record(dns, proxy_list, domain=domain, ip=address, record_type=record_type):
117
- update_fail = True
118
- if cache is not False:
119
- # 如果更新失败删除缓存
120
- cache[ipname] = update_fail and address
110
+ update_success = True
111
+
112
+ if isinstance(cache, dict):
113
+ cache[ipname] = update_success and address
114
+
115
+ return update_success
121
116
 
122
117
 
123
118
  def main():
124
119
  """
125
120
  更新
126
121
  """
127
- init_config(__description__, __doc__, __version__)
122
+ encode = sys.stdout.encoding
123
+ if encode is not None and encode.lower() != 'utf-8' and hasattr(sys.stdout, 'buffer'):
124
+ # 兼容windows 和部分ASCII编码的老旧系统
125
+ sys.stdout = TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
126
+ sys.stderr = TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
127
+ init_config(__description__, __doc__, __version__, build_date)
128
128
 
129
129
  log_level = get_config('log.level')
130
- log_format = get_config('log.format', '%(asctime)s %(levelname)s [%(module)s]: %(message)s')
131
- # Override log format in debug mode to include filename and line number for detailed debugging
132
- if (log_level == DEBUG or log_level == NOTSET) and not log_format:
133
- log_format = '%(asctime)s %(levelname)s [%(filename)s:%(lineno)d]: %(message)s'
130
+ log_format = get_config('log.format')
131
+ if log_format:
132
+ # A custom log format is already set; no further action is required.
133
+ pass
134
+ elif log_level < INFO:
135
+ # 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'
137
+ elif log_level > INFO:
138
+ log_format = '%(asctime)s %(levelname)s: %(message)s'
139
+ else:
140
+ log_format = '%(asctime)s %(levelname)s [%(module)s]: %(message)s'
134
141
  basicConfig(
135
142
  level=log_level,
136
143
  format=log_format,
@@ -142,7 +149,10 @@ def main():
142
149
 
143
150
  # Dynamically import the dns module as configuration
144
151
  dns_provider = str(get_config('dns', 'dnspod').lower())
145
- dns = getattr(__import__('dns', fromlist=[dns_provider]), dns_provider)
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)
146
156
  dns.Config.ID = get_config('id')
147
157
  dns.Config.TOKEN = get_config('token')
148
158
  dns.Config.TTL = get_config('ttl')
@@ -174,23 +184,4 @@ def main():
174
184
 
175
185
 
176
186
  if __name__ == '__main__':
177
- encode = sys.stdout.encoding
178
- if encode is not None and encode.lower() != 'utf-8' and hasattr(sys.stdout, 'buffer'):
179
- # 兼容windows 和部分ASCII编码的老旧系统
180
- sys.stdout = TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
181
- sys.stderr = TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
182
187
  main()
183
-
184
- # Nuitka Project Configuration
185
- # nuitka-project: --mode=onefile
186
- # nuitka-project: --output-filename=ddns
187
- # nuitka-project: --product-name=DDNS
188
- # nuitka-project: --product-version=0.0.0
189
- # nuitka-project: --onefile-tempdir-spec="{TEMP}/{PRODUCT}_{VERSION}"
190
- # nuitka-project: --no-deployment-flag=self-execution
191
- # nuitka-project: --company-name="New Future"
192
- # nuitka-project: --copyright=https://ddns.newfuture.cc
193
- # nuitka-project: --assume-yes-for-downloads
194
- # nuitka-project: --python-flag=no_site,no_asserts,no_docstrings,isolated,static_hashes
195
- # nuitka-project: --nofollow-import-to=tkinter,unittest,pydoc,doctest,distutils,setuptools,lib2to3,test,idlelib,lzma
196
- # nuitka-project: --noinclude-dlls=liblzma.*
@@ -6,7 +6,7 @@ http://www.dnspod.com/docs/domains.html
6
6
  @author: New Future
7
7
  """
8
8
 
9
- from dns.dnspod import * # noqa: F403
9
+ from .dnspod import * # noqa: F403
10
10
 
11
11
  API.SITE = "api.dnspod.com" # noqa: F405
12
12
  API.DEFAULT = "default" # noqa: F405
@@ -0,0 +1,317 @@
1
+ # -*- coding:utf-8 -*-
2
+ from argparse import Action, ArgumentParser, Namespace, RawTextHelpFormatter
3
+ from json import load as loadjson, dump as dumpjson
4
+ from os import stat, environ, path
5
+ from logging import critical, error, getLevelName
6
+ from ast import literal_eval
7
+
8
+ import platform
9
+ import sys
10
+
11
+
12
+ __cli_args = Namespace()
13
+ __config = {} # type: dict
14
+ log_levels = [
15
+ "CRITICAL", # 50
16
+ "ERROR", # 40
17
+ "WARNING", # 30
18
+ "INFO", # 20
19
+ "DEBUG", # 10
20
+ "NOTSET", # 0
21
+ ]
22
+
23
+ # 支持数组的参数列表
24
+ ARRAY_PARAMS = ["index4", "index6", "ipv4", "ipv6", "proxy"]
25
+ # 简单数组,支持’,’, ‘;’ 分隔的参数列表
26
+ SIMPLE_ARRAY_PARAMS = ["ipv4", "ipv6", "proxy"]
27
+
28
+
29
+ def str2bool(v):
30
+ """
31
+ parse string to boolean
32
+ """
33
+ if isinstance(v, bool):
34
+ return v
35
+ if v.lower() in ("yes", "true", "t", "y", "1"):
36
+ return True
37
+ elif v.lower() in ("no", "false", "f", "n", "0"):
38
+ return False
39
+ else:
40
+ return v
41
+
42
+
43
+ def log_level(value):
44
+ """
45
+ parse string to log level
46
+ or getattr(logging, value.upper())
47
+ """
48
+ return getLevelName(value.upper())
49
+
50
+
51
+ def parse_array_string(value, enable_simple_split):
52
+ """
53
+ 解析数组字符串
54
+ 仅当 trim 之后以 '[' 开头以 ']' 结尾时,才尝试使用 ast.literal_eval 解析
55
+ 默认返回原始字符串
56
+ """
57
+ if not isinstance(value, str):
58
+ return value
59
+
60
+ trimmed = value.strip()
61
+ if trimmed.startswith("[") and trimmed.endswith("]"):
62
+ try:
63
+ # 尝试使用 ast.literal_eval 解析数组
64
+ parsed_value = literal_eval(trimmed)
65
+ # 确保解析结果是列表或元组
66
+ if isinstance(parsed_value, (list, tuple)):
67
+ return list(parsed_value)
68
+ except (ValueError, SyntaxError) as e:
69
+ # 解析失败时返回原始字符串
70
+ error("Failed to parse array string: %s. Exception: %s", value, e)
71
+ elif enable_simple_split:
72
+ # 尝试使用逗号或分号分隔符解析
73
+ sep = None
74
+ if ',' in trimmed:
75
+ sep = ','
76
+ elif ';' in trimmed:
77
+ sep = ';'
78
+ if sep:
79
+ return [item.strip() for item in trimmed.split(sep) if item.strip()]
80
+ return value
81
+
82
+
83
+ def get_system_info_str():
84
+ system = platform.system()
85
+ release = platform.release()
86
+ machine = platform.machine()
87
+ arch = platform.architecture()[0] # '64bit' or '32bit'
88
+
89
+ return "{}-{} {} ({})".format(system, release, machine, arch)
90
+
91
+
92
+ def get_python_info_str():
93
+ version = platform.python_version()
94
+ branch, py_build_date = platform.python_build()
95
+ return "Python-{} {} ({})".format(version, branch, py_build_date)
96
+
97
+
98
+ def init_config(description, doc, version, date):
99
+ """
100
+ 配置
101
+ """
102
+ global __cli_args
103
+ parser = ArgumentParser(
104
+ description=description, epilog=doc, formatter_class=RawTextHelpFormatter
105
+ )
106
+ sysinfo = get_system_info_str()
107
+ pyinfo = get_python_info_str()
108
+ version_str = "v{} ({})\n{}\n{}".format(version, date, pyinfo, sysinfo)
109
+ parser.add_argument("-v", "--version", action="version", version=version_str)
110
+ parser.add_argument(
111
+ "-c", "--config", metavar="FILE", help="load config file [配置文件路径]"
112
+ )
113
+ parser.add_argument(
114
+ "--debug",
115
+ action="store_true",
116
+ help="debug mode [调试模式等效 --log.level=DEBUG]",
117
+ )
118
+
119
+ # 参数定义
120
+ parser.add_argument(
121
+ "--dns",
122
+ help="DNS provider [DNS服务提供商]",
123
+ choices=[
124
+ "alidns",
125
+ "cloudflare",
126
+ "dnscom",
127
+ "dnspod",
128
+ "dnspod_com",
129
+ "he",
130
+ "huaweidns",
131
+ "callback",
132
+ ],
133
+ )
134
+ parser.add_argument("--id", help="API ID or email [对应账号ID或邮箱]")
135
+ parser.add_argument("--token", help="API token or key [授权凭证或密钥]")
136
+ parser.add_argument(
137
+ "--index4",
138
+ nargs="*",
139
+ action=ExtendAction,
140
+ metavar="RULE",
141
+ help="IPv4 rules [获取IPv4方式, 多次可配置多规则]",
142
+ )
143
+ parser.add_argument(
144
+ "--index6",
145
+ nargs="*",
146
+ action=ExtendAction,
147
+ metavar="RULE",
148
+ help="IPv6 rules [获取IPv6方式, 多次可配置多规则]",
149
+ )
150
+ parser.add_argument(
151
+ "--ipv4",
152
+ nargs="*",
153
+ action=ExtendAction,
154
+ metavar="DOMAIN",
155
+ help="IPv4 domains [IPv4域名列表, 可配置多个域名]",
156
+ )
157
+ parser.add_argument(
158
+ "--ipv6",
159
+ nargs="*",
160
+ action=ExtendAction,
161
+ metavar="DOMAIN",
162
+ help="IPv6 domains [IPv6域名列表, 可配置多个域名]",
163
+ )
164
+ parser.add_argument("--ttl", type=int, help="DNS TTL(s) [设置域名解析过期时间]")
165
+ parser.add_argument(
166
+ "--proxy",
167
+ nargs="*",
168
+ action=ExtendAction,
169
+ help="HTTP proxy [设置http代理,可配多个代理连接]",
170
+ )
171
+ parser.add_argument(
172
+ "--cache",
173
+ type=str2bool,
174
+ nargs="?",
175
+ const=True,
176
+ help="set cache [启用缓存开关,或传入保存路径]",
177
+ )
178
+ parser.add_argument(
179
+ "--no-cache",
180
+ dest="cache",
181
+ action="store_const",
182
+ const=False,
183
+ help="disable cache [关闭缓存等效 --cache=false]",
184
+ )
185
+ parser.add_argument(
186
+ "--log.file", metavar="FILE", help="log file [日志文件,默认标准输出]"
187
+ )
188
+ parser.add_argument("--log.level", type=log_level, metavar="|".join(log_levels))
189
+ parser.add_argument(
190
+ "--log.format", metavar="FORMAT", help="log format [设置日志打印格式]"
191
+ )
192
+ parser.add_argument(
193
+ "--log.datefmt", metavar="FORMAT", help="date format [日志时间打印格式]"
194
+ )
195
+
196
+ __cli_args = parser.parse_args()
197
+ if __cli_args.debug:
198
+ # 如果启用调试模式,则设置日志级别为 DEBUG
199
+ setattr(__cli_args, "log.level", log_level("DEBUG"))
200
+
201
+ is_configfile_required = not get_config("token") and not get_config("id")
202
+ config_file = get_config("config")
203
+ if not config_file:
204
+ # 未指定配置文件且需要读取文件时,依次查找
205
+ cfgs = [
206
+ path.abspath("config.json"),
207
+ path.expanduser("~/.ddns/config.json"),
208
+ "/etc/ddns/config.json",
209
+ ]
210
+ config_file = next((cfg for cfg in cfgs if path.isfile(cfg)), cfgs[0])
211
+
212
+ if path.isfile(config_file):
213
+ __load_config(config_file)
214
+ __cli_args.config = config_file
215
+ elif is_configfile_required:
216
+ error("Config file is required, but not found: %s", config_file)
217
+ # 如果需要配置文件但没有指定,则自动生成
218
+ if generate_config(config_file):
219
+ sys.stdout.write("Default configure file %s is generated.\n" % config_file)
220
+ sys.exit(1)
221
+ else:
222
+ sys.exit("fail to load config from file: %s\n" % config_file)
223
+
224
+
225
+ def __load_config(config_path):
226
+ """
227
+ 加载配置
228
+ """
229
+ global __config
230
+ try:
231
+ with open(config_path, "r") as configfile:
232
+ __config = loadjson(configfile)
233
+ __config["config_modified_time"] = stat(config_path).st_mtime
234
+ if "log" in __config:
235
+ if "level" in __config["log"] and __config["log"]["level"] is not None:
236
+ __config["log.level"] = log_level(__config["log"]["level"])
237
+ if "file" in __config["log"]:
238
+ __config["log.file"] = __config["log"]["file"]
239
+ if "format" in __config["log"]:
240
+ __config["log.format"] = __config["log"]["format"]
241
+ if "datefmt" in __config["log"]:
242
+ __config["log.datefmt"] = __config["log"]["datefmt"]
243
+ elif "log.level" in __config:
244
+ __config["log.level"] = log_level(__config["log.level"])
245
+ except Exception as e:
246
+ critical("Failed to load config file `%s`: %s", config_path, e)
247
+ raise
248
+ # 重新抛出异常
249
+
250
+
251
+ def get_config(key, default=None):
252
+ """
253
+ 读取配置
254
+ 1. 命令行参数
255
+ 2. 配置文件
256
+ 3. 环境变量
257
+ """
258
+ if hasattr(__cli_args, key) and getattr(__cli_args, key) is not None:
259
+ return getattr(__cli_args, key)
260
+ if key in __config:
261
+ return __config.get(key)
262
+ # 检查环境变量
263
+ env_name = "DDNS_" + key.replace(".", "_") # type:str
264
+ variations = [env_name, env_name.upper(), env_name.lower()]
265
+ value = next((environ.get(v) for v in variations if v in environ), None)
266
+
267
+ # 如果找到环境变量值且参数支持数组,尝试解析为数组
268
+ if value is not None and key in ARRAY_PARAMS:
269
+ return parse_array_string(value, key in SIMPLE_ARRAY_PARAMS)
270
+
271
+ return value if value is not None else default
272
+
273
+
274
+ class ExtendAction(Action):
275
+ """
276
+ 兼容 Python <3.8 的 extend action
277
+ """
278
+
279
+ def __call__(self, parser, namespace, values, option_string=None):
280
+ items = getattr(namespace, self.dest, None)
281
+ if items is None:
282
+ items = []
283
+ # values 可能是单个值或列表
284
+ if isinstance(values, list):
285
+ items.extend(values)
286
+ else:
287
+ items.append(values)
288
+ setattr(namespace, self.dest, items)
289
+
290
+
291
+ def generate_config(config_path):
292
+ """
293
+ 生成配置文件
294
+ """
295
+ configure = {
296
+ "$schema": "https://ddns.newfuture.cc/schema/v4.0.json",
297
+ "id": "YOUR ID or EMAIL for DNS Provider",
298
+ "token": "YOUR TOKEN or KEY for DNS Provider",
299
+ "dns": "dnspod",
300
+ "ipv4": ["newfuture.cc", "ddns.newfuture.cc"],
301
+ "ipv6": ["newfuture.cc", "ipv6.ddns.newfuture.cc"],
302
+ "index4": "default",
303
+ "index6": "default",
304
+ "ttl": None,
305
+ "proxy": None,
306
+ "log": {"level": "INFO"},
307
+ }
308
+ try:
309
+ with open(config_path, "w") as f:
310
+ dumpjson(configure, f, indent=2, sort_keys=True)
311
+ return True
312
+ except IOError:
313
+ critical("Cannot open config file to write: `%s`!", config_path)
314
+ return False
315
+ except Exception as e:
316
+ critical("Failed to write config file `%s`: %s", config_path, e)
317
+ return False