153 lines
4.6 KiB
Markdown
153 lines
4.6 KiB
Markdown
# 原始 CDN 下载实现指南(给第三方框架)
|
||
|
||
这份文档只讲“原始 Hook 协议怎么下图”,不依赖本项目的 `WechatHookClient` 或 `ImageProcessor` 封装。
|
||
|
||
## 1) 接口结论(按本仓库实际代码验证)
|
||
|
||
- 接口:`POST /api/cdn_download`
|
||
- 请求体字段:
|
||
- `fileid`:CDN 文件标识
|
||
- `asekey`:AES 密钥(注意字段名是 `asekey`,不是 `aeskey`)
|
||
- `imgType`:`1` 原图,`2` 缩略图
|
||
- `out`:本地保存路径(Hook 所在机器可写路径)
|
||
- 成功判定:响应 JSON 中 `errCode == 1`
|
||
|
||
已在代码中看到的依据:
|
||
|
||
- `WechatHookBot/WechatHook/http_client.py:752`
|
||
- `WechatHookBot/WechatHook/http_client.py:775`
|
||
- `WechatHookBot/WechatHook/http_client.py:793`
|
||
- `WechatHookBot/WechatHook/http_client.py:801`
|
||
|
||
---
|
||
|
||
## 2) 参数从哪里来(消息 XML)
|
||
|
||
图片消息里一般在 `<img ... />` 标签:
|
||
|
||
- 原图 fileid 候选:`cdnbigimgurl` / `cdnmidimgurl` / `cdnhdimgurl` / `fileid`
|
||
- 原图 key:`aeskey`
|
||
- 缩略图 fileid:`cdnthumburl`
|
||
- 缩略图 key:`cdnthumbaeskey`(若没有可回退 `aeskey`)
|
||
|
||
本仓库提取逻辑:
|
||
|
||
- `WechatHookBot/WechatHook/client.py:1386`
|
||
- `WechatHookBot/WechatHook/client.py:1393`
|
||
|
||
---
|
||
|
||
## 3) 你朋友可直接照抄的实现流程
|
||
|
||
1. 解析微信消息 XML,拿到:
|
||
- 原图:`fileid = cdnbigimgurl(或候选)`,`aeskey`
|
||
- 缩略图:`thumb_fileid = cdnthumburl`,`thumb_key = cdnthumbaeskey or aeskey`
|
||
2. 先调用一次原图:`imgType=1`
|
||
3. 如果失败,再调用缩略图:`imgType=2`
|
||
4. 成功后检查 `out` 文件存在且大小 `> 0`
|
||
|
||
推荐这么做的原因:有些消息原图拉不到,但缩略图能拉到。
|
||
|
||
---
|
||
|
||
## 4) 最小请求示例(curl)
|
||
|
||
```bash
|
||
curl -X POST "http://127.0.0.1:8888/api/cdn_download" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"fileid": "<cdnbigimgurl>",
|
||
"asekey": "<aeskey>",
|
||
"imgType": 1,
|
||
"out": "D:/temp/wx_img_001.jpg"
|
||
}'
|
||
```
|
||
|
||
---
|
||
|
||
## 5) Python 最小实现(可直接给朋友)
|
||
|
||
```python
|
||
import os
|
||
import requests
|
||
import xml.etree.ElementTree as ET
|
||
|
||
|
||
def parse_img_xml(xml_text: str):
|
||
root = ET.fromstring(xml_text)
|
||
img = root.find(".//img")
|
||
if img is None:
|
||
raise ValueError("xml里没有<img>标签")
|
||
|
||
fileid = (
|
||
img.get("cdnbigimgurl", "")
|
||
or img.get("cdnmidimgurl", "")
|
||
or img.get("cdnhdimgurl", "")
|
||
or img.get("fileid", "")
|
||
)
|
||
aeskey = img.get("aeskey", "")
|
||
|
||
thumb_fileid = img.get("cdnthumburl", "")
|
||
thumb_key = img.get("cdnthumbaeskey", "") or aeskey
|
||
|
||
return fileid, aeskey, thumb_fileid, thumb_key
|
||
|
||
|
||
def cdn_download(base_url: str, fileid: str, aeskey: str, out_path: str, img_type: int = 1, timeout: int = 60):
|
||
payload = {
|
||
"fileid": fileid,
|
||
"asekey": aeskey, # 注意这里是 asekey
|
||
"imgType": img_type,
|
||
"out": out_path,
|
||
}
|
||
resp = requests.post(f"{base_url}/api/cdn_download", json=payload, timeout=timeout)
|
||
resp.raise_for_status()
|
||
data = resp.json()
|
||
ok = isinstance(data, dict) and data.get("errCode") == 1
|
||
if not ok:
|
||
return False, data
|
||
if not os.path.exists(out_path) or os.path.getsize(out_path) <= 0:
|
||
return False, {"error": "hook返回成功但文件未落盘", "resp": data}
|
||
return True, data
|
||
|
||
|
||
def download_image_with_fallback(base_url: str, xml_text: str, out_path: str):
|
||
fileid, aeskey, thumb_fileid, thumb_key = parse_img_xml(xml_text)
|
||
|
||
if fileid and aeskey:
|
||
ok, data = cdn_download(base_url, fileid, aeskey, out_path, img_type=1)
|
||
if ok:
|
||
return out_path
|
||
|
||
if thumb_fileid and thumb_key:
|
||
ok, data = cdn_download(base_url, thumb_fileid, thumb_key, out_path, img_type=2)
|
||
if ok:
|
||
return out_path
|
||
|
||
raise RuntimeError("原图/缩略图都下载失败")
|
||
```
|
||
|
||
---
|
||
|
||
## 6) 常见坑(你教朋友时重点强调)
|
||
|
||
- `asekey` 字段名拼错:写成 `aeskey` 会直接失败。
|
||
- `fileid` 取错:优先 `cdnbigimgurl`,不要只盯一个字段。
|
||
- 只试原图不试缩略图:很多“偶发失败”是这么来的。
|
||
- 路径不可写:`out` 必须是 Hook 进程有权限写入的本机路径。
|
||
- 仅看 HTTP 200:必须再看 `errCode` 和文件是否真正写出来。
|
||
|
||
---
|
||
|
||
## 7) 与 `download_img` 的区别(避免混淆)
|
||
|
||
- `/api/cdn_download`:走 `fileid + asekey` 这条 CDN 参数下载链路(你现在要的)。
|
||
- `/api/download_img`:走 `MsgId/to_user/from_user/total_len...` 这条“按消息参数”下载链路。
|
||
|
||
`/api/download_img` 的官方文档在:
|
||
|
||
- `新接口/下载图片.md:1`
|
||
|
||
如果你朋友框架已经能拿到 `<img>` 里的 `cdnbigimgurl/aeskey`,优先实现 `/api/cdn_download` 即可。
|
||
|