ddns 4.0.0b2__tar.gz → 4.0.0b3__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ddns
3
- Version: 4.0.0b2
3
+ Version: 4.0.0b3
4
4
  Summary: automatically update DNS records to dynamic local IP [自动更新DNS记录指向本地IP]
5
5
  Home-page: https://ddns.newfuture.cc
6
6
  Author: NewFuture
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ddns
3
- Version: 4.0.0b2
3
+ Version: 4.0.0b3
4
4
  Summary: automatically update DNS records to dynamic local IP [自动更新DNS记录指向本地IP]
5
5
  Home-page: https://ddns.newfuture.cc
6
6
  Author: NewFuture
@@ -19,7 +19,7 @@ from util import ip
19
19
  from util.cache import Cache
20
20
  from util.config import init_config, get_config
21
21
 
22
- __version__ = "v4.0.0-beta2@2025-06-03T15:36:22+00:00" # CI 时会被Tag替换
22
+ __version__ = "v4.0.0-beta3@2025-06-04T17:36:22+00:00" # CI 时会被Tag替换
23
23
  __description__ = "automatically update DNS records to dynamic local IP [自动更新DNS记录指向本地IP]"
24
24
  __doc__ = """
25
25
  ddns[%s]
@@ -28,7 +28,7 @@ ddns[%s]
28
28
  Copyright (c) New Future (MIT License)
29
29
  """ % (__version__)
30
30
 
31
- environ["DDNS_VERSION"] = "v4.0.0-beta2"
31
+ environ["DDNS_VERSION"] = "v4.0.0-beta3"
32
32
 
33
33
  if getattr(sys, 'frozen', False):
34
34
  # https://github.com/pyinstaller/pyinstaller/wiki/Recipe-OpenSSL-Certificate
@@ -47,7 +47,7 @@ def get_ip(ip_type, index="default"):
47
47
  debug("get_ip(%s, %s)", ip_type, index)
48
48
  if index is False: # disabled
49
49
  return False
50
- elif isinstance(index, list): # 如果获取到的规则是列表,则依次判断列表中每一个规则,直到找到一个可以正确获取到的IP
50
+ elif isinstance(index, list): # 如果获取到的规则是列表,则依次判断列表中每一个规则,直到获取到IP
51
51
  for i in index:
52
52
  value = get_ip(ip_type, i)
53
53
  if value:
@@ -66,7 +66,7 @@ def get_ip(ip_type, index="default"):
66
66
  else:
67
67
  value = getattr(ip, index + "_v" + ip_type)()
68
68
  except Exception as e:
69
- error(e)
69
+ error("Failed to get %s address: %s", ip_type, e)
70
70
  return value
71
71
 
72
72
 
@@ -81,7 +81,7 @@ def change_dns_record(dns, proxy_list, **kw):
81
81
  try:
82
82
  return dns.update_record(domain, kw['ip'], record_type=record_type)
83
83
  except Exception as e:
84
- error(e)
84
+ error("Failed to update %s record for %s: %s", record_type, domain, e)
85
85
  return False
86
86
 
87
87
 
@@ -136,11 +136,11 @@ def main():
136
136
 
137
137
  info("DDNS[ %s ] run: %s %s", __version__, os_name, sys.platform)
138
138
  if get_config("config"):
139
- info("loaded Config from: %s", path.abspath(get_config('config')))
139
+ info('loaded Config from: %s', path.abspath(get_config('config')))
140
140
 
141
141
  proxy = get_config('proxy') or 'DIRECT'
142
142
  proxy_list = proxy if isinstance(
143
- proxy, list) else proxy.strip('; ').replace(',', ';').split(';')
143
+ proxy, list) else proxy.strip(';').replace(',', ';').split(';')
144
144
 
145
145
  cache_config = get_config('cache', True)
146
146
  if cache_config is False:
@@ -151,12 +151,12 @@ def main():
151
151
  cache = Cache(cache_config)
152
152
 
153
153
  if cache is False:
154
- info("Cache is disabled!")
155
- elif get_config("config_modified_time") is None or get_config("config_modified_time") >= cache.time:
156
- warning("Cache file is out of dated.")
154
+ info('Cache is disabled!')
155
+ elif not get_config('config_modified_time') or get_config('config_modified_time') >= cache.time:
156
+ warning('Cache file is out of dated.')
157
157
  cache.clear()
158
158
  else:
159
- debug("Cache is empty.")
159
+ debug('Cache is empty.')
160
160
  update_ip('4', cache, dns, proxy_list)
161
161
  update_ip('6', cache, dns, proxy_list)
162
162
 
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding:utf-8 -*-
3
+ from argparse import Action, ArgumentParser, Namespace, RawTextHelpFormatter
4
+ from json import load as loadjson, dump as dumpjson
5
+ from os import stat, environ, path
6
+ from logging import error, getLevelName
7
+
8
+ import sys
9
+
10
+
11
+ __cli_args = Namespace()
12
+ __config = {} # type: dict
13
+ log_levels = ['CRITICAL', 'FATAL', 'ERROR',
14
+ 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET']
15
+
16
+
17
+ def str2bool(v):
18
+ """
19
+ parse string to boolean
20
+ """
21
+ if isinstance(v, bool):
22
+ return v
23
+ if v.lower() in ('yes', 'true', 't', 'y', '1'):
24
+ return True
25
+ elif v.lower() in ('no', 'false', 'f', 'n', '0'):
26
+ return False
27
+ else:
28
+ return v
29
+
30
+
31
+ def log_level(value):
32
+ """
33
+ parse string to log level
34
+ """
35
+ return getLevelName(value.upper())
36
+
37
+
38
+ def init_config(description, doc, version):
39
+ """
40
+ 配置
41
+ """
42
+ global __cli_args
43
+ parser = ArgumentParser(description=description,
44
+ epilog=doc, formatter_class=RawTextHelpFormatter)
45
+ parser.add_argument('-v', '--version',
46
+ action='version', version=version)
47
+ parser.add_argument('-c', '--config', help='run with config file [配置文件路径]')
48
+
49
+ # 参数定义
50
+ parser.add_argument('--dns', help='DNS Provider [DNS服务提供商]', choices=[
51
+ 'alidns', 'cloudflare', 'dnscom', 'dnspod', 'dnspod_com', 'he', 'huaweidns', 'callback'])
52
+ parser.add_argument('--id', help='api ID [授权账户]')
53
+ parser.add_argument('--token', help='api token or Secret key [授权访问凭证或密钥]')
54
+ parser.add_argument('--index4', nargs='*', action=ExtendAction,
55
+ help='list to get ipv4 [IPV4 获取方式]')
56
+ parser.add_argument('--index6', nargs='*', action=ExtendAction,
57
+ help='list to get ipv6 [IPV6获取方式]')
58
+ parser.add_argument('--ipv4', nargs='*', action=ExtendAction,
59
+ help='ipv4 domain list [IPV4域名列表]')
60
+ parser.add_argument('--ipv6', nargs='*', action=ExtendAction,
61
+ help='ipv6 domain list [IPV6域名列表]')
62
+ parser.add_argument('--ttl', type=int, help='ttl for DNS [DNS 解析 TTL 时间]')
63
+ parser.add_argument('--proxy', nargs='*', action=ExtendAction,
64
+ help='https proxy [设置http 代理,多代理逐个尝试直到成功]')
65
+ parser.add_argument('--cache', type=str2bool, nargs='?',
66
+ const=True, help='cache flag [启用缓存,可配配置路径或开关]')
67
+ parser.add_argument('--log.file', metavar='LOG_FILE',
68
+ help='log file [日志文件,默认标准输出]')
69
+ parser.add_argument('--log.level', type=log_level,
70
+ metavar='|'.join(log_levels))
71
+
72
+ __cli_args = parser.parse_args()
73
+ is_configfile_required = not get_config("token") and not get_config("id")
74
+ config_file = get_config("config")
75
+ if not config_file:
76
+ # 未指定配置文件且需要读取文件时,依次查找
77
+ cfgs = [
78
+ path.abspath('config.json'),
79
+ path.expanduser('~/.ddns/config.json'),
80
+ '/etc/ddns/config.json'
81
+ ]
82
+ config_file = next((cfg for cfg in cfgs if path.isfile(cfg)), cfgs[0])
83
+
84
+ if path.isfile(config_file):
85
+ __load_config(config_file)
86
+ __cli_args.config = config_file
87
+ elif is_configfile_required:
88
+ error('Config file is required, but not found: %s', config_file)
89
+ # 如果需要配置文件但没有指定,则自动生成
90
+ if generate_config(config_file):
91
+ sys.stdout.write(
92
+ 'Default configure file %s is generated.\n' % config_file)
93
+ sys.exit(1)
94
+ else:
95
+ sys.exit('fail to load config from file: %s\n' % config_file)
96
+
97
+
98
+ def __load_config(config_path):
99
+ """
100
+ 加载配置
101
+ """
102
+ global __config
103
+ try:
104
+ with open(config_path, 'r') as configfile:
105
+ __config = loadjson(configfile)
106
+ __config["config_modified_time"] = stat(config_path).st_mtime
107
+ if 'log' in __config:
108
+ if 'level' in __config['log'] and __config['log']['level'] is not None:
109
+ __config['log.level'] = log_level(__config['log']['level'])
110
+ if 'file' in __config['log']:
111
+ __config['log.file'] = __config['log']['file']
112
+ elif 'log.level' in __config:
113
+ __config['log.level'] = log_level(__config['log.level'])
114
+ except Exception as e:
115
+ error('Failed to load config file `%s`: %s', config_path, e)
116
+ raise
117
+ # 重新抛出异常
118
+
119
+
120
+ def get_config(key, default=None):
121
+ """
122
+ 读取配置
123
+ 1. 命令行参数
124
+ 2. 配置文件
125
+ 3. 环境变量
126
+ """
127
+ if hasattr(__cli_args, key) and getattr(__cli_args, key) is not None:
128
+ return getattr(__cli_args, key)
129
+ if key in __config:
130
+ return __config.get(key)
131
+ env_name = 'DDNS_' + key.replace('.', '_') # type:str
132
+ if env_name in environ: # 环境变量
133
+ return environ.get(env_name)
134
+ elif env_name.upper() in environ: # 大写环境变量
135
+ return environ.get(env_name.upper())
136
+ elif env_name.lower() in environ: # 小写环境变量
137
+ return environ.get(env_name.lower())
138
+ return default
139
+
140
+
141
+ class ExtendAction(Action):
142
+ """
143
+ 兼容 Python <3.8 的 extend action
144
+ """
145
+
146
+ def __call__(self, parser, namespace, values, option_string=None):
147
+ items = getattr(namespace, self.dest, None)
148
+ if items is None:
149
+ items = []
150
+ # values 可能是单个值或列表
151
+ if isinstance(values, list):
152
+ items.extend(values)
153
+ else:
154
+ items.append(values)
155
+ setattr(namespace, self.dest, items)
156
+
157
+
158
+ def generate_config(config_path):
159
+ """
160
+ 生成配置文件
161
+ """
162
+ configure = {
163
+ '$schema': 'https://ddns.newfuture.cc/schema/v4.0.json',
164
+ 'id': 'YOUR ID or EMAIL for DNS Provider',
165
+ 'token': 'YOUR TOKEN or KEY for DNS Provider',
166
+ 'dns': 'dnspod',
167
+ 'ipv4': [
168
+ 'newfuture.cc',
169
+ 'ddns.newfuture.cc'
170
+ ],
171
+ 'ipv6': [
172
+ 'newfuture.cc',
173
+ 'ipv6.ddns.newfuture.cc'
174
+ ],
175
+ 'index4': 'default',
176
+ 'index6': 'default',
177
+ 'ttl': None,
178
+ 'proxy': None,
179
+ 'log': {
180
+ 'level': 'INFO',
181
+ 'file': None
182
+ }
183
+ }
184
+ try:
185
+ with open(config_path, 'w') as f:
186
+ dumpjson(configure, f, indent=2, sort_keys=True)
187
+ return True
188
+ except IOError:
189
+ error('Cannot open config file to write: `%s`!', config_path)
190
+ return False
191
+ except Exception as e:
192
+ error('Failed to write config file `%s`: %s', config_path, e)
193
+ return False
@@ -1,169 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding:utf-8 -*-
3
- from argparse import Action, ArgumentParser, Namespace, RawTextHelpFormatter
4
- from json import load as loadjson, dump as dumpjson
5
- from os import stat, environ
6
- from logging import error, getLevelName
7
-
8
- from time import time
9
-
10
- import sys
11
-
12
-
13
- __cli_args = Namespace()
14
- __config = {} # type: dict
15
- log_levels = ['CRITICAL', 'FATAL', 'ERROR',
16
- 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET']
17
-
18
-
19
- def str2bool(v):
20
- """
21
- parse string to boolean
22
- """
23
- if isinstance(v, bool):
24
- return v
25
- if v.lower() in ('yes', 'true', 't', 'y', '1'):
26
- return True
27
- elif v.lower() in ('no', 'false', 'f', 'n', '0'):
28
- return False
29
- else:
30
- return v
31
-
32
-
33
- def log_level(value):
34
- """
35
- parse string to log level
36
- """
37
- return getLevelName(value.upper())
38
-
39
-
40
- def init_config(description, doc, version):
41
- """
42
- 配置
43
- """
44
- global __cli_args
45
- parser = ArgumentParser(description=description,
46
- epilog=doc, formatter_class=RawTextHelpFormatter)
47
- parser.add_argument('-v', '--version',
48
- action='version', version=version)
49
- parser.add_argument('-c', '--config', help="run with config file [配置文件路径]")
50
-
51
- # 参数定义
52
- parser.add_argument('--dns', help="DNS Provider [DNS服务提供商]", choices=[
53
- 'alidns', 'cloudflare', 'dnscom', 'dnspod', 'dnspod_com', 'he', 'huaweidns', 'callback'])
54
- parser.add_argument('--id', help="api ID [授权账户]")
55
- parser.add_argument('--token', help="api token or Secret key [授权访问凭证或密钥]")
56
- parser.add_argument('--index4', nargs="*", action=ExtendAction,
57
- help="list to get ipv4 [IPV4 获取方式]")
58
- parser.add_argument('--index6', nargs="*", action=ExtendAction,
59
- help="list to get ipv6 [IPV6获取方式]")
60
- parser.add_argument('--ipv4', nargs="*", action=ExtendAction,
61
- help="ipv4 domain list [IPV4域名列表]")
62
- parser.add_argument('--ipv6', nargs="*", action=ExtendAction,
63
- help="ipv6 domain list [IPV6域名列表]")
64
- parser.add_argument('--ttl', type=int, help="ttl for DNS [DNS 解析 TTL 时间]")
65
- parser.add_argument('--proxy', nargs="*", action=ExtendAction,
66
- help="https proxy [设置http 代理,多代理逐个尝试直到成功]")
67
- parser.add_argument('--cache', type=str2bool, nargs='?',
68
- const=True, help="cache flag [启用缓存,可配配置路径或开关]")
69
- parser.add_argument('--log.file', metavar="LOG_FILE",
70
- help="log file [日志文件,默认标准输出]")
71
- parser.add_argument('--log.level', type=log_level,
72
- metavar="|".join(log_levels))
73
-
74
- __cli_args = parser.parse_args()
75
- is_configfile_optional = get_config("token") or get_config("id")
76
- config_file = get_config("config")
77
- if not is_configfile_optional or config_file is not None:
78
- __load_config(config_file or "config.json", is_configfile_optional)
79
- __cli_args.config = config_file or "config.json"
80
-
81
-
82
- def __load_config(path="config.json", skip_auto_generation=False):
83
- """
84
- 加载配置
85
- """
86
- global __config
87
- try:
88
- with open(path) as configfile:
89
- __config = loadjson(configfile)
90
- __config["config_modified_time"] = stat(path).st_mtime
91
- if 'log' in __config:
92
- if 'level' in __config['log'] and __config['log']['level'] is not None:
93
- __config['log.level'] = log_level(__config['log']['level'])
94
- if 'file' in __config['log']:
95
- __config['log.file'] = __config['log']['file']
96
- elif 'log.level' in __config:
97
- __config['log.level'] = log_level(__config['log.level'])
98
- except IOError:
99
- if skip_auto_generation:
100
- __config["config_modified_time"] = time()
101
- return
102
- error(' Config file `%s` does not exist!' % path)
103
- with open(path, 'w') as configfile:
104
- configure = {
105
- "$schema": "https://ddns.newfuture.cc/schema/v4.0.json",
106
- "id": "YOUR ID or EMAIL for DNS Provider",
107
- "token": "YOUR TOKEN or KEY for DNS Provider",
108
- "dns": "dnspod",
109
- "ipv4": [
110
- "newfuture.cc",
111
- "ddns.newfuture.cc"
112
- ],
113
- "ipv6": [
114
- "newfuture.cc",
115
- "ipv6.ddns.newfuture.cc"
116
- ],
117
- "index4": "default",
118
- "index6": "default",
119
- "ttl": None,
120
- "proxy": None,
121
- "log": {
122
- "level": "INFO",
123
- "file": None
124
- }
125
- }
126
- dumpjson(configure, configfile, indent=2, sort_keys=True)
127
- sys.stdout.write(
128
- "New template configure file `%s` is generated.\n" % path)
129
- sys.exit(1)
130
- except Exception as e:
131
- sys.exit('fail to load config from file: %s\n%s' % (path, e))
132
-
133
-
134
- def get_config(key, default=None):
135
- """
136
- 读取配置
137
- 1. 命令行参数
138
- 2. 配置文件
139
- 3. 环境变量
140
- """
141
- if hasattr(__cli_args, key) and getattr(__cli_args, key) is not None:
142
- return getattr(__cli_args, key)
143
- if key in __config:
144
- return __config.get(key)
145
- env_name = 'DDNS_' + key.replace('.', '_') # type:str
146
- if env_name in environ: # 环境变量
147
- return environ.get(env_name)
148
- elif env_name.upper() in environ: # 大写环境变量
149
- return environ.get(env_name.upper())
150
- elif env_name.lower() in environ: # 小写环境变量
151
- return environ.get(env_name.lower())
152
- return default
153
-
154
-
155
- class ExtendAction(Action):
156
- """
157
- 兼容 Python <3.8 的 extend action
158
- """
159
-
160
- def __call__(self, parser, namespace, values, option_string=None):
161
- items = getattr(namespace, self.dest, None)
162
- if items is None:
163
- items = []
164
- # values 可能是单个值或列表
165
- if isinstance(values, list):
166
- items.extend(values)
167
- else:
168
- items.append(values)
169
- setattr(namespace, self.dest, items)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes