ddns 4.1.0b1__tar.gz → 4.1.0b2__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.
- {ddns-4.1.0b1 → ddns-4.1.0b2}/PKG-INFO +22 -13
- {ddns-4.1.0b1 → ddns-4.1.0b2}/README.md +21 -12
- ddns-4.1.0b2/ddns/__init__.py +12 -0
- ddns-4.1.0b2/ddns/__main__.py +118 -0
- {ddns-4.1.0b1/ddns/util → ddns-4.1.0b2/ddns}/cache.py +42 -11
- {ddns-4.1.0b1/ddns/util → ddns-4.1.0b2/ddns}/ip.py +11 -9
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/provider/__init__.py +24 -4
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/provider/_base.py +78 -198
- ddns-4.1.0b2/ddns/provider/_signature.py +113 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/provider/alidns.py +31 -14
- ddns-4.1.0b2/ddns/provider/aliesa.py +129 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/provider/callback.py +9 -10
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/provider/cloudflare.py +8 -8
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/provider/dnscom.py +5 -5
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/provider/dnspod.py +3 -4
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/provider/dnspod_com.py +1 -1
- ddns-4.1.0b2/ddns/provider/edgeone.py +82 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/provider/he.py +4 -4
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/provider/huaweidns.py +8 -8
- ddns-4.1.0b2/ddns/provider/namesilo.py +159 -0
- ddns-4.1.0b2/ddns/provider/noip.py +103 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/provider/tencentcloud.py +7 -7
- ddns-4.1.0b2/ddns/util/comment.py +88 -0
- ddns-4.1.0b2/ddns/util/http.py +228 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns.egg-info/PKG-INFO +22 -13
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns.egg-info/SOURCES.txt +21 -7
- ddns-4.1.0b1/tests/test_util_cache.py → ddns-4.1.0b2/tests/test_cache.py +195 -15
- ddns-4.1.0b2/tests/test_config_cli.py +547 -0
- ddns-4.1.0b2/tests/test_config_config.py +393 -0
- ddns-4.1.0b2/tests/test_config_env.py +552 -0
- ddns-4.1.0b2/tests/test_config_file.py +572 -0
- ddns-4.1.0b2/tests/test_config_init.py +427 -0
- ddns-4.1.0b1/tests/test_hmac_sha256_authorization.py → ddns-4.1.0b2/tests/test_provider__signature.py +4 -4
- {ddns-4.1.0b1 → ddns-4.1.0b2}/tests/test_provider_alidns.py +33 -33
- ddns-4.1.0b2/tests/test_provider_aliesa.py +533 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/tests/test_provider_base.py +81 -30
- ddns-4.1.0b1/tests/test_provider_simple.py → ddns-4.1.0b2/tests/test_provider_base_simple.py +38 -100
- {ddns-4.1.0b1 → ddns-4.1.0b2}/tests/test_provider_callback.py +42 -42
- {ddns-4.1.0b1 → ddns-4.1.0b2}/tests/test_provider_cloudflare.py +47 -47
- {ddns-4.1.0b1 → ddns-4.1.0b2}/tests/test_provider_debug.py +16 -16
- {ddns-4.1.0b1 → ddns-4.1.0b2}/tests/test_provider_dnscom.py +37 -39
- {ddns-4.1.0b1 → ddns-4.1.0b2}/tests/test_provider_dnspod.py +11 -14
- {ddns-4.1.0b1 → ddns-4.1.0b2}/tests/test_provider_dnspod_com.py +15 -15
- ddns-4.1.0b2/tests/test_provider_edgeone.py +507 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/tests/test_provider_he.py +37 -37
- {ddns-4.1.0b1 → ddns-4.1.0b2}/tests/test_provider_huaweidns.py +24 -24
- ddns-4.1.0b2/tests/test_provider_namesilo.py +317 -0
- ddns-4.1.0b2/tests/test_provider_noip.py +386 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/tests/test_provider_tencentcloud.py +12 -12
- ddns-4.1.0b2/tests/test_util_comment.py +248 -0
- ddns-4.1.0b2/tests/test_util_http.py +270 -0
- ddns-4.1.0b1/ddns/__init__.py +0 -21
- ddns-4.1.0b1/ddns/__main__.py +0 -193
- ddns-4.1.0b1/ddns/util/config.py +0 -314
- ddns-4.1.0b1/ddns/util/http.py +0 -277
- ddns-4.1.0b1/tests/test_config_ssl.py +0 -191
- ddns-4.1.0b1/tests/test_util_http.py +0 -597
- {ddns-4.1.0b1 → ddns-4.1.0b2}/LICENSE +0 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/__builtins__.pyi +0 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/provider/debug.py +0 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns/util/__init__.py +0 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns.egg-info/dependency_links.txt +0 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns.egg-info/entry_points.txt +0 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns.egg-info/requires.txt +0 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/ddns.egg-info/top_level.txt +0 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/pyproject.toml +0 -0
- {ddns-4.1.0b1 → ddns-4.1.0b2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ddns
|
|
3
|
-
Version: 4.1.
|
|
3
|
+
Version: 4.1.0b2
|
|
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
|
|
@@ -84,12 +84,16 @@ Dynamic: license-file
|
|
|
84
84
|
- 服务商支持:
|
|
85
85
|
- [DNSPOD](https://www.dnspod.cn/) ([配置指南](doc/providers/dnspod.md))
|
|
86
86
|
- [阿里 DNS](http://www.alidns.com/) ([配置指南](doc/providers/alidns.md)) ⚡
|
|
87
|
-
- [
|
|
88
|
-
- [
|
|
89
|
-
- [
|
|
90
|
-
- [
|
|
91
|
-
- [
|
|
87
|
+
- [阿里云边缘安全加速(ESA)](https://esa.console.aliyun.com/) ([配置指南](doc/providers/aliesa.md)) ⚡
|
|
88
|
+
- [DNS.COM](https://www.dns.com/) ([配置指南](doc/providers/51dns.md)) (@loftor-git)
|
|
89
|
+
- [DNSPOD 国际版](https://www.dnspod.com/) ([配置指南](doc/providers/dnspod_com.md))
|
|
90
|
+
- [CloudFlare](https://www.cloudflare.com/) ([配置指南](doc/providers/cloudflare.md)) (@tongyifan)
|
|
91
|
+
- [HE.net](https://dns.he.net/) ([配置指南](doc/providers/he.md)) (@NN708) (不支持自动创建记录)
|
|
92
|
+
- [华为云](https://huaweicloud.com/) ([配置指南](doc/providers/huaweidns.md)) (@cybmp3) ⚡
|
|
93
|
+
- [NameSilo](https://www.namesilo.com/) ([配置指南](doc/providers/namesilo.md))
|
|
92
94
|
- [腾讯云](https://cloud.tencent.com/) ([配置指南](doc/providers/tencentcloud.md)) ⚡
|
|
95
|
+
- [腾讯云 EdgeOne](https://cloud.tencent.com/product/teo) ([配置指南](doc/providers/edgeone.md)) ⚡
|
|
96
|
+
- [No-IP](https://www.noip.com/) ([配置指南](doc/providers/noip.md))
|
|
93
97
|
- 自定义回调 API ([配置指南](doc/providers/callback.md))
|
|
94
98
|
|
|
95
99
|
> ⚡ 标记的服务商使用高级 HMAC-SHA256 签名认证,提供企业级安全保障
|
|
@@ -161,12 +165,16 @@ Dynamic: license-file
|
|
|
161
165
|
|
|
162
166
|
- **DNSPOD(中国版)**: [创建 token](https://support.dnspod.cn/Kb/showarticle/tsid/227/) | [详细配置文档](doc/providers/dnspod.md)
|
|
163
167
|
- **阿里云 DNS**: [申请 accesskey](https://help.aliyun.com/document_detail/87745.htm) | [详细配置文档](doc/providers/alidns.md)
|
|
164
|
-
-
|
|
165
|
-
- **
|
|
166
|
-
- **
|
|
167
|
-
- **
|
|
168
|
-
-
|
|
168
|
+
- **阿里云边缘安全加速(ESA)**: [申请 accesskey](https://help.aliyun.com/document_detail/87745.htm) | [详细配置文档](doc/providers/aliesa.md)
|
|
169
|
+
- **51DNS(dns.com)**: [API Key/Secret](https://www.dns.com/member/apiSet) | [详细配置文档](doc/providers/51dns.md)
|
|
170
|
+
- **DNSPOD(国际版)**: [获取 token](https://www.dnspod.com/docs/info.html#get-the-user-token) | [详细配置文档](doc/providers/dnspod_com.md)
|
|
171
|
+
- **CloudFlare**: [API Key](https://support.cloudflare.com/hc/en-us/articles/200167836-Where-do-I-find-my-Cloudflare-API-key-)(除了 `email + API KEY`,也可使用 `Token`,**需要list Zone 权限**) | [详细配置文档](doc/providers/cloudflare.md)
|
|
172
|
+
- **HE.net**: [DDNS 文档](https://dns.he.net/docs.html)(仅需将设置的密码填入 `token` 字段,`id` 字段可留空) | [详细配置文档](doc/providers/he.md)
|
|
173
|
+
- **华为云 DNS**: [APIKEY 申请](https://console.huaweicloud.com/iam/)(点左边访问密钥,然后点新增访问密钥) | [详细配置文档](doc/providers/huaweidns.md)
|
|
174
|
+
- **NameSilo**: [API Key](https://www.namesilo.com/account/api-manager)(API Manager 中获取 API Key) | [详细配置文档](doc/providers/namesilo.md)
|
|
169
175
|
- **腾讯云 DNS**: [详细配置文档](doc/providers/tencentcloud.md)
|
|
176
|
+
- **腾讯云 EdgeOne**: [详细配置文档](doc/providers/edgeone.md)
|
|
177
|
+
- **No-IP**: [用户名和密码](https://www.noip.com/)(使用 No-IP 账户的用户名和密码) | [详细配置文档](doc/providers/noip.md)
|
|
170
178
|
- **自定义回调**: 参数填写方式请查看下方的自定义回调配置说明
|
|
171
179
|
|
|
172
180
|
2. 修改配置文件,`ipv4` 和 `ipv6` 字段,为待更新的域名,详细参照配置说明
|
|
@@ -217,7 +225,7 @@ python -m ddns -c /path/to/config.json
|
|
|
217
225
|
| :----: | :----------------: | :------: | :---------: | :----------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
218
226
|
| id | string | √ | 无 | api 访问 ID | Cloudflare 为邮箱(使用 Token 时留空)<br>HE.net 可留空<br>华为云为 Access Key ID (AK) |
|
|
219
227
|
| token | string | √ | 无 | api 授权 token | 部分平台叫 secret key,**反馈粘贴时删除** |
|
|
220
|
-
| dns | string | No | `"dnspod"` | dns 服务商 | 阿里 DNS 为 `alidns`,Cloudflare 为 `cloudflare`,dns.com 为 `dnscom`,DNSPOD 国内为 `dnspod`,DNSPOD 国际为 `dnspod_com`,HE.net 为 `he`,华为云为 `huaweidns`,腾讯云为 `tencentcloud`,自定义回调为 `callback`。部分服务商有[详细配置文档](doc/providers/) |
|
|
228
|
+
| dns | string | No | `"dnspod"` | dns 服务商 | 阿里 DNS 为 `alidns`,阿里ESA为 `aliesa`,Cloudflare 为 `cloudflare`,dns.com 为 `dnscom`,DNSPOD 国内为 `dnspod`,DNSPOD 国际为 `dnspod_com`,HE.net 为 `he`,华为云为 `huaweidns`,NameSilo 为 `namesilo`,腾讯云为 `tencentcloud`,腾讯云EdgeOne为 `edgeone`,No-IP 为 `noip`,自定义回调为 `callback`。部分服务商有[详细配置文档](doc/providers/) |
|
|
221
229
|
| ipv4 | array | No | `[]` | ipv4 域名列表 | 为 `[]` 时,不会获取和更新 IPv4 地址 |
|
|
222
230
|
| ipv6 | array | No | `[]` | ipv6 域名列表 | 为 `[]` 时,不会获取和更新 IPv6 地址 |
|
|
223
231
|
| index4 | string\|int\|array | No | `"default"` | ipv4 获取方式 | 可设置 `网卡`、`内网`、`公网`、`正则` 等方式 |
|
|
@@ -266,7 +274,7 @@ python -m ddns -c /path/to/config.json
|
|
|
266
274
|
"$schema": "https://ddns.newfuture.cc/schema/v4.0.json",
|
|
267
275
|
"id": "12345",
|
|
268
276
|
"token": "mytokenkey",
|
|
269
|
-
"dns": "dnspod 或 dnspod_com 或 alidns 或 dnscom 或 cloudflare 或 he 或 huaweidns 或 tencentcloud 或 callback",
|
|
277
|
+
"dns": "dnspod 或 dnspod_com 或 alidns 或 aliesa 或 dnscom 或 cloudflare 或 he 或 huaweidns 或 namesilo 或 tencentcloud 或 noip 或 callback",
|
|
270
278
|
"ipv4": ["ddns.newfuture.cc", "ipv4.ddns.newfuture.cc"],
|
|
271
279
|
"ipv6": ["ddns.newfuture.cc", "ipv6.ddns.newfuture.cc"],
|
|
272
280
|
"index4": 0,
|
|
@@ -330,6 +338,7 @@ Docker 镜像在无额外参数的情况下,已默认启用每 5 分钟执行
|
|
|
330
338
|
使用系统自带的 IE 浏览器访问一次对应的 API 即可
|
|
331
339
|
|
|
332
340
|
- alidns 打开: <https://alidns.aliyuncs.com>
|
|
341
|
+
- aliesa 打开: <https://esa.cn-hangzhou.aliyuncs.com>
|
|
333
342
|
- cloudflare 打开: <https://api.cloudflare.com>
|
|
334
343
|
- dns.com 打开: <https://www.dns.com>
|
|
335
344
|
- dnspod.cn 打开: <https://dnsapi.cn>
|
|
@@ -39,12 +39,16 @@
|
|
|
39
39
|
- 服务商支持:
|
|
40
40
|
- [DNSPOD](https://www.dnspod.cn/) ([配置指南](doc/providers/dnspod.md))
|
|
41
41
|
- [阿里 DNS](http://www.alidns.com/) ([配置指南](doc/providers/alidns.md)) ⚡
|
|
42
|
-
- [
|
|
43
|
-
- [
|
|
44
|
-
- [
|
|
45
|
-
- [
|
|
46
|
-
- [
|
|
42
|
+
- [阿里云边缘安全加速(ESA)](https://esa.console.aliyun.com/) ([配置指南](doc/providers/aliesa.md)) ⚡
|
|
43
|
+
- [DNS.COM](https://www.dns.com/) ([配置指南](doc/providers/51dns.md)) (@loftor-git)
|
|
44
|
+
- [DNSPOD 国际版](https://www.dnspod.com/) ([配置指南](doc/providers/dnspod_com.md))
|
|
45
|
+
- [CloudFlare](https://www.cloudflare.com/) ([配置指南](doc/providers/cloudflare.md)) (@tongyifan)
|
|
46
|
+
- [HE.net](https://dns.he.net/) ([配置指南](doc/providers/he.md)) (@NN708) (不支持自动创建记录)
|
|
47
|
+
- [华为云](https://huaweicloud.com/) ([配置指南](doc/providers/huaweidns.md)) (@cybmp3) ⚡
|
|
48
|
+
- [NameSilo](https://www.namesilo.com/) ([配置指南](doc/providers/namesilo.md))
|
|
47
49
|
- [腾讯云](https://cloud.tencent.com/) ([配置指南](doc/providers/tencentcloud.md)) ⚡
|
|
50
|
+
- [腾讯云 EdgeOne](https://cloud.tencent.com/product/teo) ([配置指南](doc/providers/edgeone.md)) ⚡
|
|
51
|
+
- [No-IP](https://www.noip.com/) ([配置指南](doc/providers/noip.md))
|
|
48
52
|
- 自定义回调 API ([配置指南](doc/providers/callback.md))
|
|
49
53
|
|
|
50
54
|
> ⚡ 标记的服务商使用高级 HMAC-SHA256 签名认证,提供企业级安全保障
|
|
@@ -116,12 +120,16 @@
|
|
|
116
120
|
|
|
117
121
|
- **DNSPOD(中国版)**: [创建 token](https://support.dnspod.cn/Kb/showarticle/tsid/227/) | [详细配置文档](doc/providers/dnspod.md)
|
|
118
122
|
- **阿里云 DNS**: [申请 accesskey](https://help.aliyun.com/document_detail/87745.htm) | [详细配置文档](doc/providers/alidns.md)
|
|
119
|
-
-
|
|
120
|
-
- **
|
|
121
|
-
- **
|
|
122
|
-
- **
|
|
123
|
-
-
|
|
123
|
+
- **阿里云边缘安全加速(ESA)**: [申请 accesskey](https://help.aliyun.com/document_detail/87745.htm) | [详细配置文档](doc/providers/aliesa.md)
|
|
124
|
+
- **51DNS(dns.com)**: [API Key/Secret](https://www.dns.com/member/apiSet) | [详细配置文档](doc/providers/51dns.md)
|
|
125
|
+
- **DNSPOD(国际版)**: [获取 token](https://www.dnspod.com/docs/info.html#get-the-user-token) | [详细配置文档](doc/providers/dnspod_com.md)
|
|
126
|
+
- **CloudFlare**: [API Key](https://support.cloudflare.com/hc/en-us/articles/200167836-Where-do-I-find-my-Cloudflare-API-key-)(除了 `email + API KEY`,也可使用 `Token`,**需要list Zone 权限**) | [详细配置文档](doc/providers/cloudflare.md)
|
|
127
|
+
- **HE.net**: [DDNS 文档](https://dns.he.net/docs.html)(仅需将设置的密码填入 `token` 字段,`id` 字段可留空) | [详细配置文档](doc/providers/he.md)
|
|
128
|
+
- **华为云 DNS**: [APIKEY 申请](https://console.huaweicloud.com/iam/)(点左边访问密钥,然后点新增访问密钥) | [详细配置文档](doc/providers/huaweidns.md)
|
|
129
|
+
- **NameSilo**: [API Key](https://www.namesilo.com/account/api-manager)(API Manager 中获取 API Key) | [详细配置文档](doc/providers/namesilo.md)
|
|
124
130
|
- **腾讯云 DNS**: [详细配置文档](doc/providers/tencentcloud.md)
|
|
131
|
+
- **腾讯云 EdgeOne**: [详细配置文档](doc/providers/edgeone.md)
|
|
132
|
+
- **No-IP**: [用户名和密码](https://www.noip.com/)(使用 No-IP 账户的用户名和密码) | [详细配置文档](doc/providers/noip.md)
|
|
125
133
|
- **自定义回调**: 参数填写方式请查看下方的自定义回调配置说明
|
|
126
134
|
|
|
127
135
|
2. 修改配置文件,`ipv4` 和 `ipv6` 字段,为待更新的域名,详细参照配置说明
|
|
@@ -172,7 +180,7 @@ python -m ddns -c /path/to/config.json
|
|
|
172
180
|
| :----: | :----------------: | :------: | :---------: | :----------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
173
181
|
| id | string | √ | 无 | api 访问 ID | Cloudflare 为邮箱(使用 Token 时留空)<br>HE.net 可留空<br>华为云为 Access Key ID (AK) |
|
|
174
182
|
| token | string | √ | 无 | api 授权 token | 部分平台叫 secret key,**反馈粘贴时删除** |
|
|
175
|
-
| dns | string | No | `"dnspod"` | dns 服务商 | 阿里 DNS 为 `alidns`,Cloudflare 为 `cloudflare`,dns.com 为 `dnscom`,DNSPOD 国内为 `dnspod`,DNSPOD 国际为 `dnspod_com`,HE.net 为 `he`,华为云为 `huaweidns`,腾讯云为 `tencentcloud`,自定义回调为 `callback`。部分服务商有[详细配置文档](doc/providers/) |
|
|
183
|
+
| dns | string | No | `"dnspod"` | dns 服务商 | 阿里 DNS 为 `alidns`,阿里ESA为 `aliesa`,Cloudflare 为 `cloudflare`,dns.com 为 `dnscom`,DNSPOD 国内为 `dnspod`,DNSPOD 国际为 `dnspod_com`,HE.net 为 `he`,华为云为 `huaweidns`,NameSilo 为 `namesilo`,腾讯云为 `tencentcloud`,腾讯云EdgeOne为 `edgeone`,No-IP 为 `noip`,自定义回调为 `callback`。部分服务商有[详细配置文档](doc/providers/) |
|
|
176
184
|
| ipv4 | array | No | `[]` | ipv4 域名列表 | 为 `[]` 时,不会获取和更新 IPv4 地址 |
|
|
177
185
|
| ipv6 | array | No | `[]` | ipv6 域名列表 | 为 `[]` 时,不会获取和更新 IPv6 地址 |
|
|
178
186
|
| index4 | string\|int\|array | No | `"default"` | ipv4 获取方式 | 可设置 `网卡`、`内网`、`公网`、`正则` 等方式 |
|
|
@@ -221,7 +229,7 @@ python -m ddns -c /path/to/config.json
|
|
|
221
229
|
"$schema": "https://ddns.newfuture.cc/schema/v4.0.json",
|
|
222
230
|
"id": "12345",
|
|
223
231
|
"token": "mytokenkey",
|
|
224
|
-
"dns": "dnspod 或 dnspod_com 或 alidns 或 dnscom 或 cloudflare 或 he 或 huaweidns 或 tencentcloud 或 callback",
|
|
232
|
+
"dns": "dnspod 或 dnspod_com 或 alidns 或 aliesa 或 dnscom 或 cloudflare 或 he 或 huaweidns 或 namesilo 或 tencentcloud 或 noip 或 callback",
|
|
225
233
|
"ipv4": ["ddns.newfuture.cc", "ipv4.ddns.newfuture.cc"],
|
|
226
234
|
"ipv6": ["ddns.newfuture.cc", "ipv6.ddns.newfuture.cc"],
|
|
227
235
|
"index4": 0,
|
|
@@ -285,6 +293,7 @@ Docker 镜像在无额外参数的情况下,已默认启用每 5 分钟执行
|
|
|
285
293
|
使用系统自带的 IE 浏览器访问一次对应的 API 即可
|
|
286
294
|
|
|
287
295
|
- alidns 打开: <https://alidns.aliyuncs.com>
|
|
296
|
+
- aliesa 打开: <https://esa.cn-hangzhou.aliyuncs.com>
|
|
288
297
|
- cloudflare 打开: <https://api.cloudflare.com>
|
|
289
298
|
- dns.com 打开: <https://www.dns.com>
|
|
290
299
|
- dnspod.cn 打开: <https://dnsapi.cn>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# -*- coding:utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
DDNS
|
|
4
|
+
@author: NewFuture, rufengsuixing
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from io import TextIOWrapper
|
|
8
|
+
from subprocess import check_output
|
|
9
|
+
from logging import getLogger
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from .__init__ import __version__, __description__, build_date
|
|
13
|
+
from .config import load_config, Config # noqa: F401
|
|
14
|
+
from .provider import get_provider_class, SimpleProvider
|
|
15
|
+
from . import ip
|
|
16
|
+
from .cache import Cache
|
|
17
|
+
|
|
18
|
+
logger = getLogger()
|
|
19
|
+
# Set user agent for All Providers
|
|
20
|
+
SimpleProvider.user_agent = SimpleProvider.user_agent.format(version=__version__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_ip(ip_type, rules):
|
|
24
|
+
"""
|
|
25
|
+
get IP address
|
|
26
|
+
"""
|
|
27
|
+
if rules is False: # disabled
|
|
28
|
+
return False
|
|
29
|
+
for i in rules:
|
|
30
|
+
try:
|
|
31
|
+
logger.debug("get_ip:(%s, %s)", ip_type, i)
|
|
32
|
+
if str(i).isdigit(): # 数字 local eth
|
|
33
|
+
return getattr(ip, "local_v" + ip_type)(i)
|
|
34
|
+
elif i.startswith("cmd:"): # cmd
|
|
35
|
+
return str(check_output(i[4:]).strip().decode("utf-8"))
|
|
36
|
+
elif i.startswith("shell:"): # shell
|
|
37
|
+
return str(check_output(i[6:], shell=True).strip().decode("utf-8"))
|
|
38
|
+
elif i.startswith("url:"): # 自定义 url
|
|
39
|
+
return getattr(ip, "public_v" + ip_type)(i[4:])
|
|
40
|
+
elif i.startswith("regex:"): # 正则 regex
|
|
41
|
+
return getattr(ip, "regex_v" + ip_type)(i[6:])
|
|
42
|
+
else:
|
|
43
|
+
return getattr(ip, i + "_v" + ip_type)()
|
|
44
|
+
except Exception as e:
|
|
45
|
+
logger.error("Failed to get %s address: %s", ip_type, e)
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def update_ip(dns, cache, index_rule, domains, record_type, config):
|
|
50
|
+
# type: (SimpleProvider, Cache | None, list[str]|bool, list[str], str, Config) -> bool | None
|
|
51
|
+
"""
|
|
52
|
+
更新IP并变更DNS记录
|
|
53
|
+
"""
|
|
54
|
+
if not domains:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
ip_type = "4" if record_type == "A" else "6"
|
|
58
|
+
address = get_ip(ip_type, index_rule)
|
|
59
|
+
if not address:
|
|
60
|
+
logger.error("Fail to get %s address!", ip_type)
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
update_success = False
|
|
64
|
+
|
|
65
|
+
for domain in domains:
|
|
66
|
+
domain = domain.lower()
|
|
67
|
+
cache_key = "{}:{}".format(domain, record_type)
|
|
68
|
+
if cache and cache.get(cache_key) == address:
|
|
69
|
+
logger.info("%s[%s] address not changed, using cache: %s", domain, record_type, address)
|
|
70
|
+
update_success = True
|
|
71
|
+
else:
|
|
72
|
+
try:
|
|
73
|
+
result = dns.set_record(domain, address, record_type=record_type, ttl=config.ttl, line=config.line)
|
|
74
|
+
if result:
|
|
75
|
+
logger.warning("set %s[IPv%s]: %s successfully.", domain, ip_type, address)
|
|
76
|
+
update_success = True
|
|
77
|
+
if isinstance(cache, dict):
|
|
78
|
+
cache[cache_key] = address
|
|
79
|
+
else:
|
|
80
|
+
logger.error("Failed to update %s record for %s", record_type, domain)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.exception("Failed to update %s record for %s: %s", record_type, domain, e)
|
|
83
|
+
return update_success
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def run(config):
|
|
87
|
+
# type: (Config) -> bool
|
|
88
|
+
"""
|
|
89
|
+
Run the DDNS update process
|
|
90
|
+
"""
|
|
91
|
+
# 设置IP模块的SSL验证配置
|
|
92
|
+
ip.ssl_verify = config.ssl
|
|
93
|
+
|
|
94
|
+
# dns provider class
|
|
95
|
+
provider_class = get_provider_class(config.dns)
|
|
96
|
+
dns = provider_class(
|
|
97
|
+
config.id, config.token, endpoint=config.endpoint, logger=logger, proxy=config.proxy, verify_ssl=config.ssl
|
|
98
|
+
)
|
|
99
|
+
cache = Cache.new(config.cache, config.md5(), logger)
|
|
100
|
+
return (
|
|
101
|
+
update_ip(dns, cache, config.index4, config.ipv4, "A", config) is not False
|
|
102
|
+
and update_ip(dns, cache, config.index6, config.ipv6, "AAAA", config) is not False
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def main():
|
|
107
|
+
encode = sys.stdout.encoding
|
|
108
|
+
if encode is not None and encode.lower() != "utf-8" and hasattr(sys.stdout, "buffer"):
|
|
109
|
+
# 兼容windows 和部分ASCII编码的老旧系统
|
|
110
|
+
sys.stdout = TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
|
111
|
+
sys.stderr = TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
|
112
|
+
logger.name = "ddns"
|
|
113
|
+
config = load_config(__description__, __version__, build_date)
|
|
114
|
+
run(config)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
main()
|
|
@@ -4,10 +4,11 @@ cache module
|
|
|
4
4
|
文件缓存
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from logging import getLogger, Logger # noqa: F401
|
|
7
8
|
from os import path, stat
|
|
8
|
-
from
|
|
9
|
+
from json import load, dump
|
|
10
|
+
from tempfile import gettempdir
|
|
9
11
|
from time import time
|
|
10
|
-
from logging import getLogger, Logger # noqa: F401
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class Cache(dict):
|
|
@@ -40,18 +41,20 @@ class Cache(dict):
|
|
|
40
41
|
file = self.__filename
|
|
41
42
|
|
|
42
43
|
self.__logger.debug("load cache data from %s", file)
|
|
43
|
-
if file
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
if file:
|
|
45
|
+
try:
|
|
46
|
+
with open(file, "r") as data:
|
|
46
47
|
loaded_data = load(data)
|
|
47
48
|
self.clear()
|
|
48
49
|
self.update(loaded_data)
|
|
49
50
|
self.__time = stat(file).st_mtime
|
|
50
51
|
return self
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
except (IOError, OSError):
|
|
53
|
+
self.__logger.info("cache file not exist or cannot be opened")
|
|
54
|
+
except ValueError:
|
|
55
|
+
pass
|
|
56
|
+
except Exception as e:
|
|
57
|
+
self.__logger.warning(e)
|
|
55
58
|
else:
|
|
56
59
|
self.__logger.info("cache file not exist")
|
|
57
60
|
|
|
@@ -63,10 +66,10 @@ class Cache(dict):
|
|
|
63
66
|
def sync(self):
|
|
64
67
|
"""Sync the write buffer with the cache files and clear the buffer."""
|
|
65
68
|
if self.__changed and self.__filename:
|
|
66
|
-
with open(self.__filename, "
|
|
69
|
+
with open(self.__filename, "w") as data:
|
|
67
70
|
# 只保存非私有字段(不以__开头的字段)
|
|
68
71
|
filtered_data = {k: v for k, v in super(Cache, self).items() if not k.startswith("__")}
|
|
69
|
-
dump(filtered_data, data)
|
|
72
|
+
dump(filtered_data, data, separators=(",", ":"))
|
|
70
73
|
self.__logger.debug("save cache data to %s", self.__filename)
|
|
71
74
|
self.__time = time()
|
|
72
75
|
self.__changed = False
|
|
@@ -150,3 +153,31 @@ class Cache(dict):
|
|
|
150
153
|
|
|
151
154
|
def __del__(self):
|
|
152
155
|
self.close()
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def new(config_cache, hash, logger):
|
|
159
|
+
# type: (str|bool, str, Logger) -> Cache|None
|
|
160
|
+
"""
|
|
161
|
+
new cache from a file path.
|
|
162
|
+
:param path: Path to the cache file.
|
|
163
|
+
:param logger: Optional logger for debug messages.
|
|
164
|
+
:return: Cache instance with loaded data.
|
|
165
|
+
"""
|
|
166
|
+
if config_cache is False:
|
|
167
|
+
cache = None
|
|
168
|
+
elif config_cache is True:
|
|
169
|
+
cache_path = path.join(gettempdir(), "ddns.%s.cache" % hash)
|
|
170
|
+
cache = Cache(cache_path, logger)
|
|
171
|
+
else:
|
|
172
|
+
cache = Cache(config_cache, logger)
|
|
173
|
+
|
|
174
|
+
if cache is None:
|
|
175
|
+
logger.debug("Cache is disabled!")
|
|
176
|
+
elif cache.time + 72 * 3600 < time(): # 72小时有效期
|
|
177
|
+
logger.info("Cache file is outdated.")
|
|
178
|
+
cache.clear()
|
|
179
|
+
elif len(cache) == 0:
|
|
180
|
+
logger.debug("Cache is empty.")
|
|
181
|
+
else:
|
|
182
|
+
logger.debug("Cache loaded with %d entries.", len(cache))
|
|
183
|
+
return cache
|
|
@@ -5,10 +5,10 @@ from os import name as os_name, popen
|
|
|
5
5
|
from socket import socket, getaddrinfo, gethostname, AF_INET, AF_INET6, SOCK_DGRAM
|
|
6
6
|
from logging import debug, error
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
from .util.http import send_http_request
|
|
9
|
+
|
|
10
|
+
# 模块级别的SSL验证配置,默认使用auto模式
|
|
11
|
+
ssl_verify = "auto"
|
|
12
12
|
|
|
13
13
|
# IPV4正则
|
|
14
14
|
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{0,1}[0-9]){0,1}[0-9])"
|
|
@@ -48,13 +48,15 @@ def local_v4(i=0): # 本地ipv4地址
|
|
|
48
48
|
def _open(url, reg):
|
|
49
49
|
try:
|
|
50
50
|
debug("open: %s", url)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
.read()
|
|
54
|
-
.decode("utf8", "ignore")
|
|
51
|
+
response = send_http_request(
|
|
52
|
+
method="GET", url=url, headers={"User-Agent": "Mozilla/5.0 ddns"}, verify_ssl=ssl_verify
|
|
55
53
|
)
|
|
54
|
+
res = response.body
|
|
56
55
|
debug("response: %s", res)
|
|
57
|
-
|
|
56
|
+
match = compile(reg).search(res)
|
|
57
|
+
if match:
|
|
58
|
+
return match.group()
|
|
59
|
+
error("No match found in response: %s", res)
|
|
58
60
|
except Exception as e:
|
|
59
61
|
error(e)
|
|
60
62
|
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# coding=utf-8
|
|
2
|
-
from ._base import SimpleProvider
|
|
2
|
+
from ._base import SimpleProvider
|
|
3
3
|
from .alidns import AlidnsProvider
|
|
4
|
+
from .aliesa import AliesaProvider
|
|
4
5
|
from .callback import CallbackProvider
|
|
5
6
|
from .cloudflare import CloudflareProvider
|
|
7
|
+
from .debug import DebugProvider
|
|
6
8
|
from .dnscom import DnscomProvider
|
|
7
9
|
from .dnspod import DnspodProvider
|
|
8
10
|
from .dnspod_com import DnspodComProvider
|
|
11
|
+
from .edgeone import EdgeOneProvider
|
|
9
12
|
from .he import HeProvider
|
|
10
13
|
from .huaweidns import HuaweiDNSProvider
|
|
14
|
+
from .namesilo import NamesiloProvider
|
|
15
|
+
from .noip import NoipProvider
|
|
11
16
|
from .tencentcloud import TencentCloudProvider
|
|
12
|
-
|
|
17
|
+
|
|
18
|
+
__all__ = ["SimpleProvider", "get_provider_class"]
|
|
13
19
|
|
|
14
20
|
|
|
15
21
|
def get_provider_class(provider_name):
|
|
@@ -32,15 +38,22 @@ def get_provider_class(provider_name):
|
|
|
32
38
|
"tencentcloud": TencentCloudProvider,
|
|
33
39
|
"tencent": TencentCloudProvider, # 兼容tencent
|
|
34
40
|
"qcloud": TencentCloudProvider, # 兼容qcloud
|
|
41
|
+
# tencent cloud edgeone
|
|
42
|
+
"edgeone": EdgeOneProvider,
|
|
43
|
+
"teo": EdgeOneProvider, # 兼容teo (EdgeOne产品的API名称)
|
|
44
|
+
"tencentedgeone": EdgeOneProvider, # 兼容tencentedgeone
|
|
35
45
|
# cloudflare
|
|
36
46
|
"cloudflare": CloudflareProvider,
|
|
37
47
|
# aliyun alidns
|
|
38
48
|
"alidns": AlidnsProvider,
|
|
39
49
|
"aliyun": AlidnsProvider, # 兼容aliyun
|
|
50
|
+
# aliyun esa
|
|
51
|
+
"aliesa": AliesaProvider,
|
|
52
|
+
"esa": AliesaProvider, # 兼容esa
|
|
40
53
|
# dns.com
|
|
41
54
|
"dnscom": DnscomProvider,
|
|
42
|
-
"51dns": DnscomProvider, #
|
|
43
|
-
"dns_com": DnscomProvider, #
|
|
55
|
+
"51dns": DnscomProvider, # 兼容51dns
|
|
56
|
+
"dns_com": DnscomProvider, # 兼容dns_com
|
|
44
57
|
# he.net
|
|
45
58
|
"he": HeProvider,
|
|
46
59
|
"he_net": HeProvider, # 兼容he.net
|
|
@@ -48,6 +61,13 @@ def get_provider_class(provider_name):
|
|
|
48
61
|
"huaweidns": HuaweiDNSProvider,
|
|
49
62
|
"huawei": HuaweiDNSProvider, # 兼容huawei
|
|
50
63
|
"huaweicloud": HuaweiDNSProvider,
|
|
64
|
+
# namesilo
|
|
65
|
+
"namesilo": NamesiloProvider,
|
|
66
|
+
"namesilo_com": NamesiloProvider, # 兼容namesilo.com
|
|
67
|
+
# no-ip
|
|
68
|
+
"noip": NoipProvider,
|
|
69
|
+
"no-ip": NoipProvider, # 兼容no-ip
|
|
70
|
+
"noip_com": NoipProvider, # 兼容noip.com
|
|
51
71
|
# callback
|
|
52
72
|
"callback": CallbackProvider,
|
|
53
73
|
"webhook": CallbackProvider, # 兼容
|