Metadata-Version: 2.5
Name: qingniao
Version: 0.5.1
Summary: 青鸟 SDK — 统一封装多个自建项目的对外 API 接口
Project-URL: Homepage, https://github.com/leookun/qingniao
Project-URL: Repository, https://github.com/leookun/qingniao
Author: darren
License: MIT
Keywords: 2fauth,accountbox,api-client,fusionmail,sdk,totp
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.0
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: twine>=5.1; extra == 'dev'
Provides-Extra: socks
Requires-Dist: httpx[socks]; extra == 'socks'
Description-Content-Type: text/markdown

# qingniao

青鸟 SDK — 统一封装多个自建项目的对外 API 接口。

## 安装

```bash
pip install qingniao
```

## 快速开始

### 方式一：统一门面（推荐）

```python
from qingniao import Qingniao

# 从环境变量自动配置
# export ACCOUNTBOX_API_KEY=your_key
# export FUSIONMAIL_API_KEY=your_key
# export GPTMAIL_API_KEY=your_key
# export GPTMAIL_BASE_URL=https://mail.chatgpt.org.uk
qn = Qingniao()

# 访问各项目客户端
qn.accountbox.get_websites()
qn.fusionmail.receive_mail(email="user@example.com")
qn.gptmail.list_emails(email="demo@example.com")
```

### 方式二：单独使用某个客户端

```python
from qingniao import AccountBoxClient, FusionMailClient, TwoFAuthClient, GPTMailClient

ab = AccountBoxClient(api_key="your_key", base_url="http://localhost:5095")
ab.get_websites()

fm = FusionMailClient(api_key="your_key", base_url="http://localhost:3333")
fm.receive_mail(email="user@example.com")

tf = TwoFAuthClient(api_key="your_pat", base_url="https://my-2fauth.app")
tf.create_from_qrcode("/path/to/code.png")

gm = GPTMailClient(api_key="your_key", base_url="https://mail.chatgpt.org.uk")
gm.generate_email()
```

## 2FAuth 用法

```python
from qingniao import TwoFAuthClient

tf = TwoFAuthClient(api_key="your_pat", base_url="https://my-2fauth.app")

# 扫码创建 2FA 账号（不填 group_id 进入默认分组）
account = tf.create_from_qrcode("/path/to/code.png")
account = tf.create_from_qrcode("/path/to/code.png", group_id=1)

# 用明文密钥（base32 secret）直接创建，无需 QR 码图片
# 可选参数：service、otp_type（totp/hotp）、digits、algorithm、period、counter
account = tf.create_from_secret("abc@outlook.com", "JBSWY3DPEHPK3PXP", service="GitHub")
# group_id 随创建请求一并发送，单次请求完成归组
account = tf.create_from_secret("abc@outlook.com", "JBSWY3DPEHPK3PXP", service="GitHub", group_id=1)

# 按邮箱获取验证码
otp = tf.get_otp_by_account("abc@outlook.com")
otp = tf.get_otp_by_account("abc@outlook.com", group_id=2)
print(otp.password)  # "654321"

# 分组管理
groups = tf.get_groups()
tf.create_group("Social")
tf.assign_to_group(group_id=1, account_ids=[5, 6, 7])
```

## GPTMail 用法

GPTMail 提供临时邮箱服务，适合验证码接收、自动化测试、爬虫调试等场景。

```python
from qingniao import GPTMailClient

gm = GPTMailClient(api_key="your_key", base_url="https://mail.chatgpt.org.uk")

# 1. 生成临时邮箱
email = gm.generate_email()               # 随机邮箱
email = gm.generate_email(prefix="demo")  # 指定前缀

# 2. 拉取该邮箱的邮件列表
mails = gm.list_emails(email="demo@example.com")

# 3. 读取单封邮件详情
detail = gm.get_email(email_id="e5a7f3...")

# 4. 删除单封邮件
gm.delete_email(email_id="e5a7f3...")

# 5. 清空整个收件箱（返回删除的邮件数量）
deleted = gm.clear_inbox(email="demo@example.com")
print(deleted)
```

> 提示：`GPTMAIL_BASE_URL` 为必填项，指向 GPTMail 服务地址（如 `https://mail.chatgpt.org.uk`）。测试可用公开 key `PUBLIC_API_KEY`，但公开 key 不返回 `usage` 额度信息。

## Resin 用法

Resin（https://github.com/Resinat/Resin）是一个**代理池网关**，把海量代理订阅聚合成统一
代理入口，并通过 `Platform + Account` 业务身份实现**粘性会话**（同一账号稳定命中同一出口
IP，节点故障自动切换到同 IP 节点）。

`ResinClient` 以**正向代理**方式接入，同时支持 **HTTP** 与 **SOCKS5** 两种协议，
由 `proxy_url` 的 scheme 决定。它**不继承 `BaseClient`**，而是直接透传上游服务的原始
`httpx.Response`。

### HTTP 正向代理

```python
from qingniao import ResinClient

# 一个实例 = 一个粘性账号身份；account 相同即绑定同一出口 IP
client = ResinClient(
    proxy_url="http://127.0.0.1:2260",   # Resin HTTP 正向代理地址
    proxy_token="my-token",              # Resin 代理令牌（RESIN_PROXY_TOKEN）
    platform="Default",                  # 平台（节点池），默认 Default
    account="user_1",                    # 业务账号；None 则随机路由（非粘性）
)

resp = client.get("https://api.example.com/v1/orders")
print(resp.status_code)     # 上游原始响应状态码
print(resp.json())          # 上游原始响应体（透传，非 SDK 信封）

# 其它方法同样可用
client.post("https://api.example.com/v1/orders", json={"k": "v"})
client.put("https://api.example.com/v1/orders/1", json={"k": "v"})
client.patch("https://api.example.com/v1/orders/1", json={"k": "v"})
client.delete("https://api.example.com/v1/orders/1")

client.close()
```

### SOCKS5 正向代理

```python
from qingniao import ResinClient

# 需先安装 SOCKS5 依赖
# pip install "qingniao[socks]"

client = ResinClient(
    proxy_url="socks5://127.0.0.1:2260",     # 或 socks5h:// 由 Resin 远端解析 DNS
    proxy_token="my-token",
    platform="Default",
    account="user_1",
)
resp = client.get("https://api.example.com/ip")
print(resp.json())
```

### 粘性会话说明

- **粘性**：构造时指定 `account`，同账号请求稳定命中同一出口 IP；不同账号请**各建一个
  `ResinClient` 实例**（一个实例 = 一个粘性账号身份）。
- **非粘性**：`account=None` 时仅用 `platform`（或 Default）身份，走随机路由。
- 认证身份即 `platform.account`，SDK 自动注入到代理认证（HTTP 走 Basic，SOCKS5 走 RFC1929）。

### Resin 环境变量

| 变量 | 说明 |
|------|------|
| `RESIN_PROXY_URL` | Resin 代理地址（必填，含 scheme，如 `http://host:2260` 或 `socks5://host:2260`）|
| `RESIN_PROXY_TOKEN` | Resin 代理令牌 |
| `RESIN_PLATFORM` | 平台名（默认 `Default`）|
| `RESIN_ACCOUNT` | 业务账号（粘性身份，缺省非粘性随机路由）|

也可用环境变量配置：

```python
# export RESIN_PROXY_URL=http://127.0.0.1:2260
# export RESIN_PROXY_TOKEN=my-token
# export RESIN_PLATFORM=Default
# export RESIN_ACCOUNT=user_1
from qingniao import ResinClient
client = ResinClient()   # 全部从环境变量读取
```

## 环境变量

| 变量 | 说明 |
|------|------|
| `ACCOUNTBOX_API_KEY` | AccountBox API 密钥 |
| `ACCOUNTBOX_BASE_URL` | AccountBox Base URL（可选，有默认值）|
| `FUSIONMAIL_API_KEY` | FusionMail API 密钥 |
| `FUSIONMAIL_BASE_URL` | FusionMail Base URL（可选，有默认值）|
| `2FAUTH_API_KEY` | 2FAuth Personal Access Token |
| `2FAUTH_BASE_URL` | 2FAuth Base URL（必填，自托管实例地址）|
| `GPTMAIL_API_KEY` | GPTMail API 密钥（测试可用公开 key `PUBLIC_API_KEY`）|
| `GPTMAIL_BASE_URL` | GPTMail Base URL（必填，如 `https://mail.chatgpt.org.uk`）|
| `RESIN_PROXY_URL` | Resin 代理地址（必填，含 scheme，如 `http://host:2260` 或 `socks5://host:2260`）|
| `RESIN_PROXY_TOKEN` | Resin 代理令牌 |
| `RESIN_PLATFORM` | Resin 平台名（默认 `Default`）|
| `RESIN_ACCOUNT` | Resin 业务账号（粘性身份，缺省非粘性随机路由）|

## 新项目接入

接入一个新项目只需 4 步：

1. 创建 `src/qingniao/myproject/` 子包
2. 在 `client.py` 中继承 `BaseClient`，实现 `client_name`、`api_prefix`、`auth_header` 三个属性
3. 在 `models.py` 中用 Pydantic v2 定义响应模型
4. 在 `__init__.py` 中用 `@register_client("myproject")` 注册

无需修改任何公共代码。

> 例外：以**正向代理**接入的服务（如 Resin）不遵循上述 BaseClient 流程——它不继承
> `BaseClient`、不进门面，直接透传上游原始响应。详见上文「Resin 用法」与
> `.trellis/spec/backend/sdk-package-pattern.md` 的 Extension 章节。

## License

MIT