# 迅雷（cnk3x/xunlei）API 下载任务排查说明（v2：问题已解决）

> v2 更新（2026-08-31 04:5x，DSH 会话）：**"API 建任务不被 CLI 认领"问题已解决**——根因是任务 body 构造错误，修正后 API 建的任务被 CLI 立即认领并完整下载（哈希与源一致）。
> v1（2026-08-31 04:31）：首次排查，确认 API 认证可用但建任务无效，交接给后续会话。
> 机器：DSH（192.168.31.76，迅雷 Web UI :2345，容器名 `xunlei`，cnk3x/xunlei + xunlei-pan-cli 3.21.0）

---

## 〇、v2 结论速览（本次突破）

1. **API 建任务不生效的根因 = 任务 body 字段错误**：v1 用的 `space:""` + `params.folder` + `params.predict_type` 是错的；**正确 body 必须带 `space=device_id#...`、`params.target=device_id#...`、`params.parent_folder_id=<目录ID>`、`total_file_count`、`file_id:""`**。
2. **修正后实测通过**：API 创建 → CLI 15~20s 内认领（PENDING→RUNNING）→ 下载引擎跑起来（11.2MB/s）→ 完成（PHASE_TYPE_COMPLETE），`checked_size` 314,581,680/314,581,680（100%）。
3. **下载文件哈希与源完全一致**：`QQ_9.9.33_260813_x64_01.exe`（314,581,680 字节）本地 sha256=`b25c0d3ce9df764074a9118d0ded927e1b2d7ebf60e306112e8df18a040ec492` = GitHub 源 digest（GitHub API 返回的 asset digest）。迅雷多线程下载无损坏。
4. **用户给的 qqdl.gtimg.cn 链接在本机无法下载**（curl/python requests/迅雷下载 SDK 全部 403 Forbidden，含官网无 sign 直链、各 CDN 边缘 IP、带 UA/Referer/cookie/代理），**是腾讯 CDN 拒绝本机网络，与迅雷流程无关**。改用 GitHub 上完全相同的官方构建文件验证了流程。
5. v1 的对照任务（QQ-test/QQ-test2，错误 body）仍然 PENDING 未认领，作为"错误 body 的对照证据"保留在任务列表。

---

## 一、正确创建下载任务的完整调用（v2，已实测可用）

### 1. 提取 pan-auth JWT（每次实时提取，约 3 天有效）
```bash
PAGE=$(curl -s "http://127.0.0.1:2345/webman/3rdparty/pan-xunlei-com/index.cgi/")
JWT=$(echo "$PAGE" | grep -oE 'function uiauth[^}]*return "[^"]+"' | grep -oE '"[^"]+"' | tr -d '"')
```
认证头：`pan-auth: $JWT`、`Device-Space: ""`（必须空串）、`Content-Type: application/json`。
（`x-syno-token` 只是 webman CSRF，不是 API 认证。）

### 2. 获取设备 ID 与下载根目录 ID
```bash
BASE="http://127.0.0.1:2345/webman/3rdparty/pan-xunlei-com/index.cgi"
# 设备 ID（runner 任务的 params.target）
curl -s "$BASE/drive/v1/tasks?type=user%23runner&device_space=" -H "pan-auth: $JWT" -H "Device-Space: "
# → tasks[0].params.target = "device_id#85292dba0b98d9d02db23e5509bc36d6"（本机）
# 下载根目录 ID（drive#folder 列表）
curl -s "$BASE/drive/v1/files?space=<urlencode(device_id)>&limit=200&parent_id=&filters=%7B%22kind%22%3A%7B%22eq%22%3A%22drive%23folder%22%7D%7D&page_token=&device_space=" \
  -H "pan-auth: $JWT" -H "Device-Space: "
# → files[0].id = "84b584c0ee8a20b73ad3a408b9f9b109"（/downloads/ 根目录）
```

### 3. （可选，UI 会先做）注册下载路径
```bash
curl -s -X POST "$BASE/device/download_path" -H "pan-auth: $JWT" -H "Device-Space: " -H "Content-Type: application/json" \
  -d '{"file_id":"84b584c0ee8a20b73ad3a408b9f9b109"}'
```

### 4. 创建任务（关键 body，与前端 `addUrlTask` 完全一致）
```bash
curl -s -X POST "$BASE/drive/v1/task" -H "pan-auth: $JWT" -H "Device-Space: " -H "Content-Type: application/json" -d '{
  "type": "user#download-url",
  "name": "任意任务名",
  "file_name": "QQ_9.9.33_260813_x64_01.exe",
  "file_size": "314581680",                    # 已知大小；未知填 "0" 也可以（v1 用 0 成功落库）
  "space": "device_id#85292dba0b98d9d02db23e5509bc36d6",
  "params": {
    "target": "device_id#85292dba0b98d9d02db23e5509bc36d6",
    "url": "<下载URL>",
    "total_file_count": "1",
    "parent_folder_id": "84b584c0ee8a20b73ad3a408b9f9b109",
    "mime_type": "",                            # 或 application/octet-stream
    "file_id": ""
  }
}'
# 返回 {"HttpStatus":0,"task":{...,"id":"VP0J...","phase":"PHASE_TYPE_PENDING"}}
```
返回后 **15~20 秒内** CLI 认领：phase → `PHASE_TYPE_RUNNING`，`params.real_path=/downloads/<任务名>`，下载目录出现 `.xltd` 临时文件。完成后 phase → `PHASE_TYPE_COMPLETE`，`checked_size == file_size`。

### 5. 其他可用端点
- 任务列表：`GET drive/v1/tasks?space=&limit=N`（space 空串也能列出全部）
- 按 id 查任务：`GET drive/v1/tasks?space=<device_id>&filters={"id":{"in":"<任务id>"}}`
- 删除任务：`DELETE drive/v1/tasks?space=<device_id>&task_ids=<id>`（会中断该任务下载，踩过）
- pyxunlei（[lalaking666/pyxunlei](https://github.com/lalaking666/pyxunlei)，pip 可装）封装了认证/设备发现/磁力下载，其 `download_magnetic` 的 body 与上述一致，可作参考实现。

---

## 二、v1 遗留问题根因分析（已解决，留档）

- v1 的错误 body：`{"space":"","type":"user#download-url","file_size":"0","name":"QQ-test","file_name":"QQ.exe","params":{"url":"...","folder":"/downloads/","predict_type":"1"}}`
  - 错误点 1：顶层 `space` 用了 `""`，应为 `device_id#85292dba...`（任务归属空间）
  - 错误点 2：`params` 缺 `target`（CLI 认领靠它定位设备）
  - 错误点 3：用 `folder`/`predict_type`，实际应传 `parent_folder_id`（下载目录 ID，空串不行——**必须真实目录 ID**）
  - 错误点 4：缺 `total_file_count`、`file_id`
- 现象对照：错误 body → 任务落库但永远 PENDING（"等待中"，message 不变）；正确 body → CLI 15~20s 认领。

## 三、本次实测的两个任务（v2）

| 任务 | body | 结果 |
|---|---|---|
| `QQ-CLI-test`（VP0JQJlN…，已删） | 正确 body + qqdl.gtimg.cn 链接 | ✅ 被 CLI 认领（RUNNING），但下载源 403 → 下载引擎报 `task 1001 status is Failed error` |
| `QQ-GH-test` → 改名 `QQ_9.9.33_260813_x64_01.exe`（VP0JRxCz…） | 正确 body + GitHub 同文件镜像 | ✅ 认领 → 11.2MB/s 下载 → COMPLETE，314,581,680 字节，sha256 与源一致 |
| `QQ-test` / `QQ-test2`（v1 遗留） | 错误 body（space:"" + folder/predict_type） | ❌ 仍 PENDING，未被认领（对照证据，保留） |

## 四、qqdl.gtimg.cn 403 排查记录（v2 修正：非全局拦截，分文件/CDN 行为）

### 4.1 案例 A：QQ NT 9.9.33 Windows exe（Lego Server → 403，下载失败）
链接 `https://qqdl.gtimg.cn/qqfile/QQNTV2/9.9.33/release/497e2f1f/QQ_9.9.33_260813_x64_01.exe?sign=ef09ff4053edda86ecd7cb1d8726bcd1&t=1788121505`：
- curl（直连/代理/HTTP1.0/1.1/各 CDN 边缘 IP）、python requests、迅雷 CLI 下载 SDK → **全部 403 Forbidden**（Server: Lego Server）
- 官网 `windowsConfig.js` 的**无 sign 官方直链**同样 403；带浏览器 UA、Referer、im.qq.com 会话 cookie 均无效
- 该文件被腾讯 CDN（Lego Server）反热链保护，本机网络无法取得 → **绕过方案（已验证）**：GitHub 镜像 [Rodert/qq-versions](https://github.com/Rodert/qq-versions) release `qq-packages-20260813-1d08f1d4` 有完全相同的官方构建 `QQ_9.9.33_260813_x64_01.exe`（314,581,680 字节），GitHub API asset digest 可直接校验，迅雷下载后 sha256 与源一致

### 4.2 案例 B：QQ NT 3.2.32 Linux deb（tencent-cos → 200，直接成功！）
链接 `https://qqdl.gtimg.cn/qqfile/QQNTV2/9.9.33/release/3f89efc5/QQ_3.2.32_260812_amd64_01.deb?sign=5a418a6bdab5cf591cc8ba4c9ea36ff5&t=1788156000`（2026-08-31 13:5x 实测）：
- **curl/迅雷 CLI 直接 200 OK**（Server: **tencent-cos**，非 Lego Server），185,828,040 字节（177MB）
- 迅雷 API 建任务（正确 body）→ CLI 认领 → 下载 → COMPLETE，落盘 sha256=`d085dd89397225061eb9f194308f688129818ed445777e97a4a0a16e13d7b0e8`，与同网络 curl 直连下载的基准哈希**完全一致**
- **结论修正**：qqdl.gtimg.cn 不是整体拦截本机 IP，而是**分文件/CDN 行为**——Lego Server 服务的（如该 exe）反热链 403；tencent-cos 直出的（如该 deb）可正常下载。以后遇到 qqdl 链接先 curl -sI 看 Server 头：`Lego Server` 大概率 403，`tencent-cos` 可直下

### 4.3 UI 解析文件名
`POST drive/v1/resource/list`（body `{"urls":"<链接>"}`）可探测链接元信息（与 UI 添加链接同款）：本次返回 `name: QQ_3.2.32_260812_amd64_01.deb, file_count: 1`。**注意落盘文件名 = 任务 name 而非 file_name**，用 `drive/v1/resource/list` 解析出的 name 作为任务 name 即可让落盘文件名正确（本次实测任务名 QQ-deb-test 落盘为 `QQ-deb-test`，后手动 `mv` 改名）。

### 4.4 案例 C：QQ NT 3.2.32 Linux loongarch64 deb（tencent-cos → 200，直接成功 + 文件名直接正确）
链接 `https://qqdl.gtimg.cn/qqfile/QQNTV2/9.9.33/release/3f89efc5/QQ_3.2.32_260812_loongarch64_01.deb?sign=b3ddab317c297fdd00efe781afba6faa&t=1788156389`（2026-08-31 14:0x 实测）：
- **先 `drive/v1/resource/list` 解析文件名** → `QQ_3.2.32_260812_loongarch64_01.deb`；HEAD 确认 200（Server: tencent-cos），Content-Length 179,336,078（171MB）
- **建任务时 name 直接用解析出的文件名** → 落盘名自动正确（`/downloads/QQ_3.2.32_260812_loongarch64_01.deb`），无需 mv
- 迅雷下载 → COMPLETE，sha256=`400553f6d646da4e523b22ba652334f08f74b377c465392dda65c2e6c9f908fd`，与同网络 curl 直连基准**完全一致**
- **最佳实践确立**：`resource/list 解析文件名 → 用解析名做任务 name → add → wait → 哈希校验`（已封装进 skill 脚本 `~/.dsh/skills/xunlei-download/scripts/xunlei.sh`）

## 五、运维经验（本次新踩/确认）

1. **容器重启**：CLI 云连接（watchMqtt / MySync.Reverse）会周期性掉线，掉线后 `drive/v1/*` 接口可能长时间无响应（本地 `device/now` 仍秒回）。`docker restart xunlei` 可恢复（约 25s 内 runner 重新 start）。重启前确认无正在下载的任务（看 `xunlei-downloads/` 下有无 `.xltd`）。
2. **CLI 上报云端的 PATCH 有已知噪音**：`phase can not update from [PHASE_TYPE_RUNNING] to [PHASE_TYPE_PENDING]`（error_code 9, HttpStatus 400）——CLI 重启后旧任务状态同步的已知现象，不影响新任务。
3. **下载是否开始以 `.xltd` 文件增长为准**；任务 message="已添加" 只是已认领。
4. 下载完成的文件名为**任务 name**（不是 file_name），如任务名 `QQ-GH-test` 落盘为 `/downloads/QQ-GH-test`（无扩展名），需要的话任务创建后用 `mv` 改名。

---

## 六、任务现状（2026-08-31 14:0x）

- ✅ `QQ_3.2.32_260812_loongarch64_01.deb`（179,336,078 字节，sha256 `400553f6…`，与直连基准一致）——**用户原始 qqdl 链接 + 解析文件名做任务名，落盘名直接正确**
- ✅ `QQ_3.2.32_260812_amd64_01.deb`（185,828,040 字节，sha256 `d085dd89…`，与直连基准一致）已在 `~/Downloads/xunlei-downloads/`（= dl-hub/09-迅雷下载，下载中心可直取）——**用户原始 qqdl 链接直下成功（tencent-cos）**
- ✅ `QQ_9.9.33_260813_x64_01.exe`（314,581,680 字节，sha256 `b25c0d3c…`，GitHub 镜像）仍在下载目录
- ✅ 8Turbo（v1 遗留，COMPLETE）、qwen3vl int4 TE（v1 遗留）仍在下载目录
- ⏳ `QQ-test`/`QQ-test2`（错误 body 对照）保持 PENDING，可随时删除
- ✅ **"API 创建下载任务不被 CLI 认领"已彻底解决，流程已封装为 skill（xunlei-download），后续 DSH 机下载文件默认走迅雷**
