Metadata-Version: 2.4
Name: xndns
Version: 1.0.5.2
Summary: 小雫DNS Python SDK 和 CLI - 零依赖的 DNSHE 子域名管理工具
Author-email: 小雫DNS <noreply@dnshe.xiaona.cc.cd>
Maintainer-email: 小雫DNS <noreply@dnshe.xiaona.cc.cd>
License: MIT
Project-URL: Homepage, https://dnshe.xiaona.cc.cd
Project-URL: Documentation, https://dnshe.xiaona.cc.cd/sdk
Project-URL: Repository, https://dnshe.xiaona.cc.cd
Project-URL: Bug Tracker, https://dnshe.xiaona.cc.cd
Project-URL: Changelog, https://dnshe.xiaona.cc.cd
Keywords: dnshe,subdomain,dns,cli,sdk,xiaoniao,小雫DNS,free-domain,cloudflare
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Internet :: Name Service (DNS)
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Utilities
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: ruff>=0.1; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=4.0; extra == "dev"
Dynamic: license-file

# 小雫DNS Python SDK + CLI (`xndns`)

> 零依赖的 DNSHE 子域名管理 Python 包，含同步/异步 SDK 和命令行工具

## 特性

- **零依赖**：仅用 Python 3.9+ 标准库（`urllib` / `asyncio` / `json`），无需 `pip install requests`
- **同步 + 异步**：提供 `Client`（同步）和 `AsyncClient`（异步）两种客户端
- **三传输层**：支持 `rest` / `rpc` / `graphql`，可通过参数或配置文件切换
- **完整功能**：子域名 CRUD / DNS 记录 CRUD / API 密钥管理 / 配额查询 / WHOIS / 安装检测
- **多种配置方式**：环境变量 / 配置文件（`~/.xndnsrc`）/ 构造参数
- **JSON 输出**：CLI 所有命令支持 `--json` 切换为机器可读格式
- **跨平台**：Linux / macOS / Windows

## 安装

### 方式 1：从源码安装（推荐）

```bash
# 解压后进入目录
cd xndns-py

# 用户级安装（推荐，无需 sudo）
python install.py

# 或系统级安装（需要 sudo）
sudo python install.py --system
```

安装脚本会：
1. 通过 `pip install .` 安装 `xndns` 包到当前 Python 环境
2. 在 `~/.local/bin`（或 `/usr/local/bin`）创建 `xndns` 包装脚本
3. 验证安装并提示如何配置 PATH

### 方式 2：开发模式安装

```bash
cd xndns-py
pip install -e .
```

适合开发调试，修改源码立即生效。

### 方式 3：直接运行（不安装）

```bash
# 直接通过 Python 模块运行
python -m xndns quota

# 或直接调用 cli.py
python xndns/cli.py help
```

需要在脚本所在目录或设置 `PYTHONPATH`。

### 方式 4：发布到 PyPI 后

```bash
pip install xndns
```

## 卸载

```bash
python uninstall.py
```

会移除 pip 包、包装脚本和 `~/.xndnsrc` 配置文件。

## 配置凭证

### 方式 1：使用 `login` 命令（推荐）

```bash
xndns login cfsd_xxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyy --host=https://dnshe.xiaona.cc.cd
```

凭证保存在 `~/.xndnsrc`（权限 600）。

### 方式 2：环境变量

```bash
export XNDNS_API_KEY=cfsd_xxxxxxxxxxxxxxxx
export XNDNS_API_SECRET=yyyyyyyyyyyyyyyyyyyy
export XNDNS_HOST=https://dnshe.xiaona.cc.cd
# 可选：传输层（默认 rest）
export XNDNS_TRANSPORT=rpc
```

适合 CI/CD 或临时使用。

### 方式 3：配置文件

直接编辑 `~/.xndnsrc`：

```json
{
  "apiKey": "cfsd_xxx",
  "apiSecret": "yyyy",
  "host": "https://dnshe.xiaona.cc.cd",
  "transport": "rest"
}
```

或用 `config` 命令：

```bash
xndns config show
xndns config set transport rpc
xndns config set host https://other-host
```

### 方式 4：在代码中直接传参

```python
from xndns import Client

client = Client(
    api_key="cfsd_xxx",
    api_secret="yyyy",
    host="https://dnshe.xiaona.cc.cd",
    transport="rest",
)
```

## SDK 使用

### 同步客户端

```python
from xndns import Client

# 初始化（也可以用 Client.from_config() 从 ~/.xndnsrc 加载）
client = Client(
    api_key="cfsd_xxxxxxxxxxxxxxxx",
    api_secret="yyyyyyyyyyyyyyyyyyyy",
    host="https://dnshe.xiaona.cc.cd",
    transport="rest",  # "rest" | "rpc" | "graphql"
)

# 验证凭证
verify = client.auth.verify()
print(verify["account"]["quota"])
# {'used': 3, 'total': 7, 'available': 4}

# 列出子域名
subs = client.subdomains.list(per_page=10)
for s in subs["subdomains"]:
    print(s["full_domain"])

# 注册新子域名
created = client.subdomains.create(subdomain="myapp", rootdomain="us.kg")
print(created["full_domain"])

# 创建 DNS 记录
client.dns_records.create(
    subdomain_id=created["subdomain_id"],
    type="A",
    content="192.0.2.1",
    ttl=600,
)

# 查询 WHOIS
whois = client.whois.query("foo.example.com")

# 检测某域名是否安装了小雫DNS
detect = client.system.detect("example.com")
if detect["detected"]:
    print(f"检测到小雫DNS v{detect['version']}")
```

### 异步客户端

```python
import asyncio
from xndns import AsyncClient

async def main():
    client = AsyncClient(
        api_key="cfsd_xxx",
        api_secret="yyyy",
        host="https://dnshe.xiaona.cc.cd",
    )

    # 并发查询配额和子域名列表
    quota, subs = await asyncio.gather(
        client.quota.get(),
        client.subdomains.list(per_page=10),
    )
    print(quota)
    print(subs)

    # 注册新子域名
    created = await client.subdomains.create("myapp", "us.kg")
    print(created["full_domain"])

asyncio.run(main())
```

### 错误处理

```python
from xndns import Client, ApiError

client = Client(api_key="...", api_secret="...")

try:
    client.subdomains.create(subdomain="x", rootdomain="us.kg")
except ApiError as e:
    print(e.code)         # "quota_exceeded"
    print(e.message)      # "Quota exceeded"
    print(e.http_status)  # 429
    print(e.details)      # {"limit": 60, "reset_at": "..."}
```

### SDK API 总览

| 命名空间 | 方法 | 对应 RPC method | 说明 |
|---|---|---|---|
| `auth` | `verify()` | `auth.verify` | 验证凭证并返回配额摘要 |
| `subdomains` | `list(...)` | `subdomains.list` | 分页列出子域名 |
| `subdomains` | `get(id)` | `subdomains.get` | 获取子域名详情 |
| `subdomains` | `create(subdomain, rootdomain)` | `subdomains.create` | 注册子域名 |
| `subdomains` | `delete(id)` | `subdomains.delete` | 删除子域名 |
| `subdomains` | `renew(id)` | `subdomains.renew` | 续费子域名 |
| `dns_records` | `list(subdomain_id)` | `dns_records.list` | 列出 DNS 记录 |
| `dns_records` | `create(subdomain_id, type, content, ...)` | `dns_records.create` | 创建 DNS 记录 |
| `dns_records` | `update(id, ...)` | `dns_records.update` | 更新 DNS 记录 |
| `dns_records` | `delete(id)` | `dns_records.delete` | 删除 DNS 记录 |
| `api_keys` | `list()` | `api_keys.list` | 列出 API 密钥 |
| `api_keys` | `create(name, ip_whitelist?)` | `api_keys.create` | 创建 API 密钥 |
| `api_keys` | `delete(key_id)` | `api_keys.delete` | 删除 API 密钥 |
| `api_keys` | `regenerate(key_id)` | `api_keys.regenerate` | 重置 API Secret |
| `quota` | `get()` | `quota.get` | 查询配额 |
| `whois` | `query(domain)` | `whois.query` | WHOIS 查询 |
| `system` | `detect(domain)` | `system.detect` | 检测目标域名 |
| `system` | `selftest()` | - | 自检 |

> 注：`dnsRecords` 和 `apiKeys` 是 `dns_records` 和 `api_keys` 的 camelCase 别名，方便 JS 习惯的用户。

## CLI 命令一览

### 基础

| 命令 | 说明 |
|---|---|
| `xndns login <key> <secret> [--host=URL]` | 保存凭证 |
| `xndns logout` | 清除凭证 |
| `xndns whoami` | 显示当前凭证 |
| `xndns config [show\|set <key> <value>]` | 查看/修改配置 |
| `xndns help` | 显示帮助 |
| `xndns version` | 显示版本 |

### 子域名

| 命令 | 说明 |
|---|---|
| `xndns subdomains list [opts]` | 列出子域名 |
| `xndns subdomains get <id>` | 查看详情 |
| `xndns subdomains create <prefix> <root>` | 注册 |
| `xndns subdomains delete <id>` | 删除 |
| `xndns subdomains renew <id>` | 续费 |

`list` 选项：`--page=N --per-page=N --search=KEY --status=STATUS --rootdomain=ROOT`

### DNS 记录

| 命令 | 说明 |
|---|---|
| `xndns dns list <subdomain_id>` | 列出记录 |
| `xndns dns create <subdomain_id> <type> <content> [--ttl=N] [--priority=N] [--name=NAME]` | 创建 |
| `xndns dns update <id> [--type=T] [--content=V] [--ttl=N]` | 更新 |
| `xndns dns delete <id>` | 删除 |

类型支持：`A / AAAA / CNAME / MX / TXT / NS / SRV / CAA`

### 密钥 / 配额 / WHOIS

| 命令 | 说明 |
|---|---|
| `xndns quota` | 查询配额 |
| `xndns whois <domain>` | WHOIS 查询 |
| `xndns keys list` | 列出 API 密钥 |
| `xndns keys create <name> [--ip-whitelist=IPS]` | 创建密钥 |
| `xndns keys delete <key_id>` | 删除密钥 |
| `xndns keys regenerate <key_id>` | 重置 Secret |

### 检测

| 命令 | 说明 |
|---|---|
| `xndns detect <domain>` | 检测目标域名是否安装小雫DNS |
| `xndns selftest` | 检测当前配置的主机 |

### 全局选项

| 选项 | 说明 |
|---|---|
| `--json` | 输出原始 JSON |
| `--host=URL` | 临时覆盖主机 |
| `--transport=rest\|rpc\|graphql` | 临时覆盖传输层 |
| `--debug` | 显示完整错误堆栈 |

## 使用示例

```bash
# 登录
xndns login cfsd_0adc0ce77bc62a2903caf044ddb877a5 b190ba15... --host=https://dnshe.xiaona.cc.cd

# 查看配额
xndns quota
# 配额: 1/3 已用
#   基础: 3
#   邀请奖励: 0
#   可用: 2

# 列出子域名
xndns subdomains list --per-page=5
# 共 1 条，本页 1 条
#
#   #7477265796  mynmail.bbroot.com  [Registered]  2036-07-07 01:08:40

# 注册新子域名
xndns subdomains create myapp us.kg
# ✓ 已注册 myapp.us.kg (#7477265797)

# 创建 DNS 记录
xndns dns create 7477265797 A 192.0.2.1 --ttl=600
# ✓ 已创建 [A] 记录 (id=100)

# WHOIS 查询
xndns whois mynmail.bbroot.com
# 域名:   mynmail.bbroot.com
# 状态:   active
# 注册:   2026-07-07 01:08
# 到期:   2036-07-07 01:08
# 邮箱:   abuse@dnshe.com
# NS:     ns1.dnshe.com, ns2.dnshe.com

# 切换到 RPC 传输层
xndns config set transport rpc
xndns quota
# (相同输出，但底层走 JSON-RPC)

# JSON 输出（适合脚本）
xndns quota --json | python -c "import sys, json; print(json.load(sys.stdin)['quota']['available'])"
# 2

# 检测某域名是否安装小雫DNS
xndns detect example.com
# ✗ example.com 未安装小雫DNS

xndns selftest
# ✓ https://dnshe.xiaona.cc.cd 是小雫DNS v1.0.0
#   功能: rest, rpc, graphql, cli, sdk, docs
```

## Python 脚本示例

```python
# 找出所有即将到期的子域名（30 天内）
import time
from datetime import datetime
from xndns import Client

client = Client.from_config()
subs = client.subdomains.list(per_page=100)["subdomains"]
now = time.time()
for s in subs:
    if not s.get("expires_at"):
        continue
    try:
        dt = datetime.strptime(s["expires_at"], "%Y-%m-%d %H:%M:%S").timestamp()
        if dt - now < 30 * 86400:
            print(f"即将到期: {s['full_domain']} ({s['expires_at']})")
    except ValueError:
        pass
```

```python
# 批量备份所有子域名的 DNS 记录
import json
from xndns import Client

client = Client.from_config()
subs = client.subdomains.list(per_page=500)["subdomains"]
for s in subs:
    recs = client.dns_records.list(s["id"])
    with open(f"dns-backup-{s['id']}.json", "w", encoding="utf-8") as f:
        json.dump({"subdomain": s, "dns_records": recs["records"]}, f, indent=2, ensure_ascii=False)
    print(f"已备份 {s['full_domain']}: {recs['count']} 条记录")
```

```python
# 异步并发查询多个域名的 WHOIS
import asyncio
from xndns import AsyncClient

async def main():
    client = AsyncClient.from_config()
    domains = ["foo.example.com", "bar.example.com", "baz.example.com"]
    results = await asyncio.gather(*[client.whois.query(d) for d in domains])
    for d, r in zip(domains, results):
        status = "已注册" if r.get("registered") else "未注册"
        print(f"{d}: {status}")

asyncio.run(main())
```

## 传输层对比

| 传输层 | 优势 | 何时使用 |
|---|---|---|
| `rest` (默认) | 资源化 URL，缓存友好 | 日常使用 |
| `rpc` | 单端点，支持批量调用 | 需要批量操作时 |
| `graphql` | 按需取字段，减少传输量 | 只需要部分字段时 |

三种传输层在功能上完全等价，可以随时通过 `xndns config set transport <value>` 或构造参数 `transport=...` 切换。

## 错误处理

CLI 在出错时会返回非零退出码，错误信息格式：

```
[error_code] error_message
```

Python SDK 抛出 `ApiError` 异常，包含 `code` / `message` / `http_status` / `details` 字段。

常见错误码：

| error_code | HTTP | 说明 |
|---|---|---|
| `auth_invalid_credentials` | 401 | 凭证无效 |
| `quota_exceeded` | 429 | 配额超限 |
| `rate_limit_exceeded` | 429 | 频率超限 |
| `subdomain_not_found` | 404 | 子域名不存在 |
| `provider_operation_failed` | 502 | 上游失败 |

完整错误码列表见 [API 文档](https://dnshe.xiaona.cc.cd/api-docs)。

## 配置文件

`~/.xndnsrc` 是 JSON 格式，权限 600：

```json
{
  "apiKey": "cfsd_xxx",
  "apiSecret": "yyyy",
  "host": "https://dnshe.xiaona.cc.cd",
  "transport": "rest"
}
```

优先级：命令行参数 > 环境变量 > 配置文件 > 构造参数默认值。

## 系统要求

- Python 3.9+
- Linux / macOS / Windows
- 网络可访问小雫DNS 主机

## 项目结构

```
xndns-py/
├── xndns/
│   ├── __init__.py      # 包入口，暴露 Client / AsyncClient / ApiError
│   ├── __main__.py      # python -m xndns 入口
│   ├── cli.py           # CLI 实现（与 Node 版功能对齐）
│   ├── client.py        # Client + AsyncClient + 命名空间
│   ├── config.py        # Config + ApiError
│   └── transport.py     # REST / RPC / GraphQL 传输层
├── pyproject.toml       # 包定义（PEP 621）
├── install.py           # 安装脚本
├── uninstall.py         # 卸载脚本
├── README.md
├── LICENSE
└── CHANGELOG.md
```

## 开发

```bash
# 开发模式安装
pip install -e .

# 运行测试（如果安装了 dev 依赖）
pytest

# 代码检查
ruff check xndns/
```

## License

MIT

## 相关

- [小雫DNS 控制台](https://dnshe.xiaona.cc.cd)
- [API 文档](https://dnshe.xiaona.cc.cd/api-docs)
- [SDK 文档](https://dnshe.xiaona.cc.cd/sdk)
- [Node.js 版 CLI](https://dnshe.xiaona.cc.cd)
