xndns 1.0.0__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.
- xndns-1.0.0/CHANGELOG.md +22 -0
- xndns-1.0.0/LICENSE +21 -0
- xndns-1.0.0/MANIFEST.in +21 -0
- xndns-1.0.0/PKG-INFO +545 -0
- xndns-1.0.0/README.md +504 -0
- xndns-1.0.0/install.py +164 -0
- xndns-1.0.0/pyproject.toml +68 -0
- xndns-1.0.0/setup.cfg +4 -0
- xndns-1.0.0/uninstall.py +55 -0
- xndns-1.0.0/xndns/__init__.py +11 -0
- xndns-1.0.0/xndns/__main__.py +8 -0
- xndns-1.0.0/xndns/cli.py +625 -0
- xndns-1.0.0/xndns/client.py +514 -0
- xndns-1.0.0/xndns/config.py +125 -0
- xndns-1.0.0/xndns/transport.py +208 -0
- xndns-1.0.0/xndns.egg-info/SOURCES.txt +13 -0
xndns-1.0.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## v1.0.0 (2026-07-09)
|
|
4
|
+
|
|
5
|
+
首次发布。
|
|
6
|
+
|
|
7
|
+
### 功能
|
|
8
|
+
|
|
9
|
+
- **SDK**: 同步 `Client` 和异步 `AsyncClient` 两种客户端
|
|
10
|
+
- **传输层**: `rest` / `rpc` / `graphql` 三选一,运行时可切换
|
|
11
|
+
- **CLI 命令**:
|
|
12
|
+
- 基础: `login`, `logout`, `whoami`, `config`, `help`, `version`
|
|
13
|
+
- 子域名: `subdomains list/get/create/delete/renew`
|
|
14
|
+
- DNS 记录: `dns list/create/update/delete`
|
|
15
|
+
- API 密钥: `keys list/create/delete/regenerate`
|
|
16
|
+
- 配额: `quota`
|
|
17
|
+
- WHOIS: `whois <domain>`
|
|
18
|
+
- 检测: `detect <domain>`, `selftest`
|
|
19
|
+
- **配置**: `~/.xndnsrc` + 环境变量 + 命令行参数 + 构造参数
|
|
20
|
+
- **输出**: 默认人类可读,`--json` 切换 JSON
|
|
21
|
+
- **跨平台**: Linux / macOS / Windows
|
|
22
|
+
- **零依赖**: 仅用 Python 3.9+ 标准库
|
xndns-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 小雫DNS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
xndns-1.0.0/MANIFEST.in
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MANIFEST.in - 控制 sdist 包含哪些文件
|
|
2
|
+
# 参考: https://packaging.python.org/en/latest/guides/using-manifest-in/
|
|
3
|
+
|
|
4
|
+
include README.md
|
|
5
|
+
include LICENSE
|
|
6
|
+
include CHANGELOG.md
|
|
7
|
+
include pyproject.toml
|
|
8
|
+
include install.py
|
|
9
|
+
include uninstall.py
|
|
10
|
+
|
|
11
|
+
# 不包含测试时的临时文件
|
|
12
|
+
exclude test_sdk.py
|
|
13
|
+
exclude .DS_Store
|
|
14
|
+
|
|
15
|
+
# 不包含构建产物
|
|
16
|
+
global-exclude __pycache__/*
|
|
17
|
+
global-exclude *.pyc
|
|
18
|
+
global-exclude *.pyo
|
|
19
|
+
global-exclude *.egg-info/*
|
|
20
|
+
global-exclude .git/*
|
|
21
|
+
global-exclude .gitignore
|
xndns-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xndns
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 小雫DNS Python SDK 和 CLI - 零依赖的 DNSHE 子域名管理工具
|
|
5
|
+
Author-email: 小雫DNS <noreply@dnshe.xiaona.cc.cd>
|
|
6
|
+
Maintainer-email: 小雫DNS <noreply@dnshe.xiaona.cc.cd>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, http://dnshe.xiaona.cc.cd
|
|
9
|
+
Project-URL: Documentation, http://dnshe.xiaona.cc.cd/sdk
|
|
10
|
+
Project-URL: Repository, http://dnshe.xiaona.cc.cd
|
|
11
|
+
Project-URL: Bug Tracker, http://dnshe.xiaona.cc.cd
|
|
12
|
+
Project-URL: Changelog, http://dnshe.xiaona.cc.cd
|
|
13
|
+
Keywords: dnshe,subdomain,dns,cli,sdk,xiaoniao,小雫DNS,free-domain,cloudflare
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Intended Audience :: System Administrators
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
26
|
+
Classifier: Topic :: Internet :: Name Service (DNS)
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
+
Classifier: Topic :: System :: Systems Administration
|
|
29
|
+
Classifier: Topic :: Utilities
|
|
30
|
+
Classifier: Typing :: Typed
|
|
31
|
+
Requires-Python: >=3.9
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
38
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
39
|
+
Requires-Dist: twine>=4.0; extra == "dev"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# 小雫DNS Python SDK + CLI (`xndns`)
|
|
43
|
+
|
|
44
|
+
> 零依赖的 DNSHE 子域名管理 Python 包,含同步/异步 SDK 和命令行工具
|
|
45
|
+
|
|
46
|
+
## 特性
|
|
47
|
+
|
|
48
|
+
- **零依赖**:仅用 Python 3.9+ 标准库(`urllib` / `asyncio` / `json`),无需 `pip install requests`
|
|
49
|
+
- **同步 + 异步**:提供 `Client`(同步)和 `AsyncClient`(异步)两种客户端
|
|
50
|
+
- **三传输层**:支持 `rest` / `rpc` / `graphql`,可通过参数或配置文件切换
|
|
51
|
+
- **完整功能**:子域名 CRUD / DNS 记录 CRUD / API 密钥管理 / 配额查询 / WHOIS / 安装检测
|
|
52
|
+
- **多种配置方式**:环境变量 / 配置文件(`~/.xndnsrc`)/ 构造参数
|
|
53
|
+
- **JSON 输出**:CLI 所有命令支持 `--json` 切换为机器可读格式
|
|
54
|
+
- **跨平台**:Linux / macOS / Windows
|
|
55
|
+
|
|
56
|
+
## 安装
|
|
57
|
+
|
|
58
|
+
### 方式 1:从源码安装(推荐)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# 解压后进入目录
|
|
62
|
+
cd xndns-py
|
|
63
|
+
|
|
64
|
+
# 用户级安装(推荐,无需 sudo)
|
|
65
|
+
python install.py
|
|
66
|
+
|
|
67
|
+
# 或系统级安装(需要 sudo)
|
|
68
|
+
sudo python install.py --system
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
安装脚本会:
|
|
72
|
+
1. 通过 `pip install .` 安装 `xndns` 包到当前 Python 环境
|
|
73
|
+
2. 在 `~/.local/bin`(或 `/usr/local/bin`)创建 `xndns` 包装脚本
|
|
74
|
+
3. 验证安装并提示如何配置 PATH
|
|
75
|
+
|
|
76
|
+
### 方式 2:开发模式安装
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
cd xndns-py
|
|
80
|
+
pip install -e .
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
适合开发调试,修改源码立即生效。
|
|
84
|
+
|
|
85
|
+
### 方式 3:直接运行(不安装)
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# 直接通过 Python 模块运行
|
|
89
|
+
python -m xndns quota
|
|
90
|
+
|
|
91
|
+
# 或直接调用 cli.py
|
|
92
|
+
python xndns/cli.py help
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
需要在脚本所在目录或设置 `PYTHONPATH`。
|
|
96
|
+
|
|
97
|
+
### 方式 4:发布到 PyPI 后
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pip install xndns
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 卸载
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
python uninstall.py
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
会移除 pip 包、包装脚本和 `~/.xndnsrc` 配置文件。
|
|
110
|
+
|
|
111
|
+
## 配置凭证
|
|
112
|
+
|
|
113
|
+
### 方式 1:使用 `login` 命令(推荐)
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
xndns login cfsd_xxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyy --host=http://dnshe.xiaona.cc.cd
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
凭证保存在 `~/.xndnsrc`(权限 600)。
|
|
120
|
+
|
|
121
|
+
### 方式 2:环境变量
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
export XNDNS_API_KEY=cfsd_xxxxxxxxxxxxxxxx
|
|
125
|
+
export XNDNS_API_SECRET=yyyyyyyyyyyyyyyyyyyy
|
|
126
|
+
export XNDNS_HOST=http://dnshe.xiaona.cc.cd
|
|
127
|
+
# 可选:传输层(默认 rest)
|
|
128
|
+
export XNDNS_TRANSPORT=rpc
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
适合 CI/CD 或临时使用。
|
|
132
|
+
|
|
133
|
+
### 方式 3:配置文件
|
|
134
|
+
|
|
135
|
+
直接编辑 `~/.xndnsrc`:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"apiKey": "cfsd_xxx",
|
|
140
|
+
"apiSecret": "yyyy",
|
|
141
|
+
"host": "http://dnshe.xiaona.cc.cd",
|
|
142
|
+
"transport": "rest"
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
或用 `config` 命令:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
xndns config show
|
|
150
|
+
xndns config set transport rpc
|
|
151
|
+
xndns config set host https://other-host
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 方式 4:在代码中直接传参
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from xndns import Client
|
|
158
|
+
|
|
159
|
+
client = Client(
|
|
160
|
+
api_key="cfsd_xxx",
|
|
161
|
+
api_secret="yyyy",
|
|
162
|
+
host="http://dnshe.xiaona.cc.cd",
|
|
163
|
+
transport="rest",
|
|
164
|
+
)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## SDK 使用
|
|
168
|
+
|
|
169
|
+
### 同步客户端
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from xndns import Client
|
|
173
|
+
|
|
174
|
+
# 初始化(也可以用 Client.from_config() 从 ~/.xndnsrc 加载)
|
|
175
|
+
client = Client(
|
|
176
|
+
api_key="cfsd_xxxxxxxxxxxxxxxx",
|
|
177
|
+
api_secret="yyyyyyyyyyyyyyyyyyyy",
|
|
178
|
+
host="http://dnshe.xiaona.cc.cd",
|
|
179
|
+
transport="rest", # "rest" | "rpc" | "graphql"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# 验证凭证
|
|
183
|
+
verify = client.auth.verify()
|
|
184
|
+
print(verify["account"]["quota"])
|
|
185
|
+
# {'used': 3, 'total': 7, 'available': 4}
|
|
186
|
+
|
|
187
|
+
# 列出子域名
|
|
188
|
+
subs = client.subdomains.list(per_page=10)
|
|
189
|
+
for s in subs["subdomains"]:
|
|
190
|
+
print(s["full_domain"])
|
|
191
|
+
|
|
192
|
+
# 注册新子域名
|
|
193
|
+
created = client.subdomains.create(subdomain="myapp", rootdomain="us.kg")
|
|
194
|
+
print(created["full_domain"])
|
|
195
|
+
|
|
196
|
+
# 创建 DNS 记录
|
|
197
|
+
client.dns_records.create(
|
|
198
|
+
subdomain_id=created["subdomain_id"],
|
|
199
|
+
type="A",
|
|
200
|
+
content="192.0.2.1",
|
|
201
|
+
ttl=600,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# 查询 WHOIS
|
|
205
|
+
whois = client.whois.query("foo.example.com")
|
|
206
|
+
|
|
207
|
+
# 检测某域名是否安装了小雫DNS
|
|
208
|
+
detect = client.system.detect("example.com")
|
|
209
|
+
if detect["detected"]:
|
|
210
|
+
print(f"检测到小雫DNS v{detect['version']}")
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 异步客户端
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
import asyncio
|
|
217
|
+
from xndns import AsyncClient
|
|
218
|
+
|
|
219
|
+
async def main():
|
|
220
|
+
client = AsyncClient(
|
|
221
|
+
api_key="cfsd_xxx",
|
|
222
|
+
api_secret="yyyy",
|
|
223
|
+
host="http://dnshe.xiaona.cc.cd",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# 并发查询配额和子域名列表
|
|
227
|
+
quota, subs = await asyncio.gather(
|
|
228
|
+
client.quota.get(),
|
|
229
|
+
client.subdomains.list(per_page=10),
|
|
230
|
+
)
|
|
231
|
+
print(quota)
|
|
232
|
+
print(subs)
|
|
233
|
+
|
|
234
|
+
# 注册新子域名
|
|
235
|
+
created = await client.subdomains.create("myapp", "us.kg")
|
|
236
|
+
print(created["full_domain"])
|
|
237
|
+
|
|
238
|
+
asyncio.run(main())
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 错误处理
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
from xndns import Client, ApiError
|
|
245
|
+
|
|
246
|
+
client = Client(api_key="...", api_secret="...")
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
client.subdomains.create(subdomain="x", rootdomain="us.kg")
|
|
250
|
+
except ApiError as e:
|
|
251
|
+
print(e.code) # "quota_exceeded"
|
|
252
|
+
print(e.message) # "Quota exceeded"
|
|
253
|
+
print(e.http_status) # 429
|
|
254
|
+
print(e.details) # {"limit": 60, "reset_at": "..."}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### SDK API 总览
|
|
258
|
+
|
|
259
|
+
| 命名空间 | 方法 | 对应 RPC method | 说明 |
|
|
260
|
+
|---|---|---|---|
|
|
261
|
+
| `auth` | `verify()` | `auth.verify` | 验证凭证并返回配额摘要 |
|
|
262
|
+
| `subdomains` | `list(...)` | `subdomains.list` | 分页列出子域名 |
|
|
263
|
+
| `subdomains` | `get(id)` | `subdomains.get` | 获取子域名详情 |
|
|
264
|
+
| `subdomains` | `create(subdomain, rootdomain)` | `subdomains.create` | 注册子域名 |
|
|
265
|
+
| `subdomains` | `delete(id)` | `subdomains.delete` | 删除子域名 |
|
|
266
|
+
| `subdomains` | `renew(id)` | `subdomains.renew` | 续费子域名 |
|
|
267
|
+
| `dns_records` | `list(subdomain_id)` | `dns_records.list` | 列出 DNS 记录 |
|
|
268
|
+
| `dns_records` | `create(subdomain_id, type, content, ...)` | `dns_records.create` | 创建 DNS 记录 |
|
|
269
|
+
| `dns_records` | `update(id, ...)` | `dns_records.update` | 更新 DNS 记录 |
|
|
270
|
+
| `dns_records` | `delete(id)` | `dns_records.delete` | 删除 DNS 记录 |
|
|
271
|
+
| `api_keys` | `list()` | `api_keys.list` | 列出 API 密钥 |
|
|
272
|
+
| `api_keys` | `create(name, ip_whitelist?)` | `api_keys.create` | 创建 API 密钥 |
|
|
273
|
+
| `api_keys` | `delete(key_id)` | `api_keys.delete` | 删除 API 密钥 |
|
|
274
|
+
| `api_keys` | `regenerate(key_id)` | `api_keys.regenerate` | 重置 API Secret |
|
|
275
|
+
| `quota` | `get()` | `quota.get` | 查询配额 |
|
|
276
|
+
| `whois` | `query(domain)` | `whois.query` | WHOIS 查询 |
|
|
277
|
+
| `system` | `detect(domain)` | `system.detect` | 检测目标域名 |
|
|
278
|
+
| `system` | `selftest()` | - | 自检 |
|
|
279
|
+
|
|
280
|
+
> 注:`dnsRecords` 和 `apiKeys` 是 `dns_records` 和 `api_keys` 的 camelCase 别名,方便 JS 习惯的用户。
|
|
281
|
+
|
|
282
|
+
## CLI 命令一览
|
|
283
|
+
|
|
284
|
+
### 基础
|
|
285
|
+
|
|
286
|
+
| 命令 | 说明 |
|
|
287
|
+
|---|---|
|
|
288
|
+
| `xndns login <key> <secret> [--host=URL]` | 保存凭证 |
|
|
289
|
+
| `xndns logout` | 清除凭证 |
|
|
290
|
+
| `xndns whoami` | 显示当前凭证 |
|
|
291
|
+
| `xndns config [show\|set <key> <value>]` | 查看/修改配置 |
|
|
292
|
+
| `xndns help` | 显示帮助 |
|
|
293
|
+
| `xndns version` | 显示版本 |
|
|
294
|
+
|
|
295
|
+
### 子域名
|
|
296
|
+
|
|
297
|
+
| 命令 | 说明 |
|
|
298
|
+
|---|---|
|
|
299
|
+
| `xndns subdomains list [opts]` | 列出子域名 |
|
|
300
|
+
| `xndns subdomains get <id>` | 查看详情 |
|
|
301
|
+
| `xndns subdomains create <prefix> <root>` | 注册 |
|
|
302
|
+
| `xndns subdomains delete <id>` | 删除 |
|
|
303
|
+
| `xndns subdomains renew <id>` | 续费 |
|
|
304
|
+
|
|
305
|
+
`list` 选项:`--page=N --per-page=N --search=KEY --status=STATUS --rootdomain=ROOT`
|
|
306
|
+
|
|
307
|
+
### DNS 记录
|
|
308
|
+
|
|
309
|
+
| 命令 | 说明 |
|
|
310
|
+
|---|---|
|
|
311
|
+
| `xndns dns list <subdomain_id>` | 列出记录 |
|
|
312
|
+
| `xndns dns create <subdomain_id> <type> <content> [--ttl=N] [--priority=N] [--name=NAME]` | 创建 |
|
|
313
|
+
| `xndns dns update <id> [--type=T] [--content=V] [--ttl=N]` | 更新 |
|
|
314
|
+
| `xndns dns delete <id>` | 删除 |
|
|
315
|
+
|
|
316
|
+
类型支持:`A / AAAA / CNAME / MX / TXT / NS / SRV / CAA`
|
|
317
|
+
|
|
318
|
+
### 密钥 / 配额 / WHOIS
|
|
319
|
+
|
|
320
|
+
| 命令 | 说明 |
|
|
321
|
+
|---|---|
|
|
322
|
+
| `xndns quota` | 查询配额 |
|
|
323
|
+
| `xndns whois <domain>` | WHOIS 查询 |
|
|
324
|
+
| `xndns keys list` | 列出 API 密钥 |
|
|
325
|
+
| `xndns keys create <name> [--ip-whitelist=IPS]` | 创建密钥 |
|
|
326
|
+
| `xndns keys delete <key_id>` | 删除密钥 |
|
|
327
|
+
| `xndns keys regenerate <key_id>` | 重置 Secret |
|
|
328
|
+
|
|
329
|
+
### 检测
|
|
330
|
+
|
|
331
|
+
| 命令 | 说明 |
|
|
332
|
+
|---|---|
|
|
333
|
+
| `xndns detect <domain>` | 检测目标域名是否安装小雫DNS |
|
|
334
|
+
| `xndns selftest` | 检测当前配置的主机 |
|
|
335
|
+
|
|
336
|
+
### 全局选项
|
|
337
|
+
|
|
338
|
+
| 选项 | 说明 |
|
|
339
|
+
|---|---|
|
|
340
|
+
| `--json` | 输出原始 JSON |
|
|
341
|
+
| `--host=URL` | 临时覆盖主机 |
|
|
342
|
+
| `--transport=rest\|rpc\|graphql` | 临时覆盖传输层 |
|
|
343
|
+
| `--debug` | 显示完整错误堆栈 |
|
|
344
|
+
|
|
345
|
+
## 使用示例
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
# 登录
|
|
349
|
+
xndns login cfsd_0adc0ce77bc62a2903caf044ddb877a5 b190ba15... --host=http://dnshe.xiaona.cc.cd
|
|
350
|
+
|
|
351
|
+
# 查看配额
|
|
352
|
+
xndns quota
|
|
353
|
+
# 配额: 1/3 已用
|
|
354
|
+
# 基础: 3
|
|
355
|
+
# 邀请奖励: 0
|
|
356
|
+
# 可用: 2
|
|
357
|
+
|
|
358
|
+
# 列出子域名
|
|
359
|
+
xndns subdomains list --per-page=5
|
|
360
|
+
# 共 1 条,本页 1 条
|
|
361
|
+
#
|
|
362
|
+
# #7477265796 mynmail.bbroot.com [Registered] 2036-07-07 01:08:40
|
|
363
|
+
|
|
364
|
+
# 注册新子域名
|
|
365
|
+
xndns subdomains create myapp us.kg
|
|
366
|
+
# ✓ 已注册 myapp.us.kg (#7477265797)
|
|
367
|
+
|
|
368
|
+
# 创建 DNS 记录
|
|
369
|
+
xndns dns create 7477265797 A 192.0.2.1 --ttl=600
|
|
370
|
+
# ✓ 已创建 [A] 记录 (id=100)
|
|
371
|
+
|
|
372
|
+
# WHOIS 查询
|
|
373
|
+
xndns whois mynmail.bbroot.com
|
|
374
|
+
# 域名: mynmail.bbroot.com
|
|
375
|
+
# 状态: active
|
|
376
|
+
# 注册: 2026-07-07 01:08
|
|
377
|
+
# 到期: 2036-07-07 01:08
|
|
378
|
+
# 邮箱: abuse@dnshe.com
|
|
379
|
+
# NS: ns1.dnshe.com, ns2.dnshe.com
|
|
380
|
+
|
|
381
|
+
# 切换到 RPC 传输层
|
|
382
|
+
xndns config set transport rpc
|
|
383
|
+
xndns quota
|
|
384
|
+
# (相同输出,但底层走 JSON-RPC)
|
|
385
|
+
|
|
386
|
+
# JSON 输出(适合脚本)
|
|
387
|
+
xndns quota --json | python -c "import sys, json; print(json.load(sys.stdin)['quota']['available'])"
|
|
388
|
+
# 2
|
|
389
|
+
|
|
390
|
+
# 检测某域名是否安装小雫DNS
|
|
391
|
+
xndns detect example.com
|
|
392
|
+
# ✗ example.com 未安装小雫DNS
|
|
393
|
+
|
|
394
|
+
xndns selftest
|
|
395
|
+
# ✓ http://dnshe.xiaona.cc.cd 是小雫DNS v1.0.0
|
|
396
|
+
# 功能: rest, rpc, graphql, cli, sdk, docs
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## Python 脚本示例
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
# 找出所有即将到期的子域名(30 天内)
|
|
403
|
+
import time
|
|
404
|
+
from datetime import datetime
|
|
405
|
+
from xndns import Client
|
|
406
|
+
|
|
407
|
+
client = Client.from_config()
|
|
408
|
+
subs = client.subdomains.list(per_page=100)["subdomains"]
|
|
409
|
+
now = time.time()
|
|
410
|
+
for s in subs:
|
|
411
|
+
if not s.get("expires_at"):
|
|
412
|
+
continue
|
|
413
|
+
try:
|
|
414
|
+
dt = datetime.strptime(s["expires_at"], "%Y-%m-%d %H:%M:%S").timestamp()
|
|
415
|
+
if dt - now < 30 * 86400:
|
|
416
|
+
print(f"即将到期: {s['full_domain']} ({s['expires_at']})")
|
|
417
|
+
except ValueError:
|
|
418
|
+
pass
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
```python
|
|
422
|
+
# 批量备份所有子域名的 DNS 记录
|
|
423
|
+
import json
|
|
424
|
+
from xndns import Client
|
|
425
|
+
|
|
426
|
+
client = Client.from_config()
|
|
427
|
+
subs = client.subdomains.list(per_page=500)["subdomains"]
|
|
428
|
+
for s in subs:
|
|
429
|
+
recs = client.dns_records.list(s["id"])
|
|
430
|
+
with open(f"dns-backup-{s['id']}.json", "w", encoding="utf-8") as f:
|
|
431
|
+
json.dump({"subdomain": s, "dns_records": recs["records"]}, f, indent=2, ensure_ascii=False)
|
|
432
|
+
print(f"已备份 {s['full_domain']}: {recs['count']} 条记录")
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
# 异步并发查询多个域名的 WHOIS
|
|
437
|
+
import asyncio
|
|
438
|
+
from xndns import AsyncClient
|
|
439
|
+
|
|
440
|
+
async def main():
|
|
441
|
+
client = AsyncClient.from_config()
|
|
442
|
+
domains = ["foo.example.com", "bar.example.com", "baz.example.com"]
|
|
443
|
+
results = await asyncio.gather(*[client.whois.query(d) for d in domains])
|
|
444
|
+
for d, r in zip(domains, results):
|
|
445
|
+
status = "已注册" if r.get("registered") else "未注册"
|
|
446
|
+
print(f"{d}: {status}")
|
|
447
|
+
|
|
448
|
+
asyncio.run(main())
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## 传输层对比
|
|
452
|
+
|
|
453
|
+
| 传输层 | 优势 | 何时使用 |
|
|
454
|
+
|---|---|---|
|
|
455
|
+
| `rest` (默认) | 资源化 URL,缓存友好 | 日常使用 |
|
|
456
|
+
| `rpc` | 单端点,支持批量调用 | 需要批量操作时 |
|
|
457
|
+
| `graphql` | 按需取字段,减少传输量 | 只需要部分字段时 |
|
|
458
|
+
|
|
459
|
+
三种传输层在功能上完全等价,可以随时通过 `xndns config set transport <value>` 或构造参数 `transport=...` 切换。
|
|
460
|
+
|
|
461
|
+
## 错误处理
|
|
462
|
+
|
|
463
|
+
CLI 在出错时会返回非零退出码,错误信息格式:
|
|
464
|
+
|
|
465
|
+
```
|
|
466
|
+
[error_code] error_message
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
Python SDK 抛出 `ApiError` 异常,包含 `code` / `message` / `http_status` / `details` 字段。
|
|
470
|
+
|
|
471
|
+
常见错误码:
|
|
472
|
+
|
|
473
|
+
| error_code | HTTP | 说明 |
|
|
474
|
+
|---|---|---|
|
|
475
|
+
| `auth_invalid_credentials` | 401 | 凭证无效 |
|
|
476
|
+
| `quota_exceeded` | 429 | 配额超限 |
|
|
477
|
+
| `rate_limit_exceeded` | 429 | 频率超限 |
|
|
478
|
+
| `subdomain_not_found` | 404 | 子域名不存在 |
|
|
479
|
+
| `provider_operation_failed` | 502 | 上游失败 |
|
|
480
|
+
|
|
481
|
+
完整错误码列表见 [API 文档](http://dnshe.xiaona.cc.cd/api-docs)。
|
|
482
|
+
|
|
483
|
+
## 配置文件
|
|
484
|
+
|
|
485
|
+
`~/.xndnsrc` 是 JSON 格式,权限 600:
|
|
486
|
+
|
|
487
|
+
```json
|
|
488
|
+
{
|
|
489
|
+
"apiKey": "cfsd_xxx",
|
|
490
|
+
"apiSecret": "yyyy",
|
|
491
|
+
"host": "http://dnshe.xiaona.cc.cd",
|
|
492
|
+
"transport": "rest"
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
优先级:命令行参数 > 环境变量 > 配置文件 > 构造参数默认值。
|
|
497
|
+
|
|
498
|
+
## 系统要求
|
|
499
|
+
|
|
500
|
+
- Python 3.9+
|
|
501
|
+
- Linux / macOS / Windows
|
|
502
|
+
- 网络可访问小雫DNS 主机
|
|
503
|
+
|
|
504
|
+
## 项目结构
|
|
505
|
+
|
|
506
|
+
```
|
|
507
|
+
xndns-py/
|
|
508
|
+
├── xndns/
|
|
509
|
+
│ ├── __init__.py # 包入口,暴露 Client / AsyncClient / ApiError
|
|
510
|
+
│ ├── __main__.py # python -m xndns 入口
|
|
511
|
+
│ ├── cli.py # CLI 实现(与 Node 版功能对齐)
|
|
512
|
+
│ ├── client.py # Client + AsyncClient + 命名空间
|
|
513
|
+
│ ├── config.py # Config + ApiError
|
|
514
|
+
│ └── transport.py # REST / RPC / GraphQL 传输层
|
|
515
|
+
├── pyproject.toml # 包定义(PEP 621)
|
|
516
|
+
├── install.py # 安装脚本
|
|
517
|
+
├── uninstall.py # 卸载脚本
|
|
518
|
+
├── README.md
|
|
519
|
+
├── LICENSE
|
|
520
|
+
└── CHANGELOG.md
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## 开发
|
|
524
|
+
|
|
525
|
+
```bash
|
|
526
|
+
# 开发模式安装
|
|
527
|
+
pip install -e .
|
|
528
|
+
|
|
529
|
+
# 运行测试(如果安装了 dev 依赖)
|
|
530
|
+
pytest
|
|
531
|
+
|
|
532
|
+
# 代码检查
|
|
533
|
+
ruff check xndns/
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
## License
|
|
537
|
+
|
|
538
|
+
MIT
|
|
539
|
+
|
|
540
|
+
## 相关
|
|
541
|
+
|
|
542
|
+
- [小雫DNS 控制台](http://dnshe.xiaona.cc.cd)
|
|
543
|
+
- [API 文档](http://dnshe.xiaona.cc.cd/api-docs)
|
|
544
|
+
- [SDK 文档](http://dnshe.xiaona.cc.cd/sdk)
|
|
545
|
+
- [Node.js 版 CLI](http://dnshe.xiaona.cc.cd)
|