chore: sync current WechatHookBot workspace

This commit is contained in:
2026-03-09 15:48:45 +08:00
parent 4016c1e6eb
commit 9119e2307d
195 changed files with 24438 additions and 17498 deletions

View File

@@ -0,0 +1,152 @@
# 原始 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` 即可。