4.6 KiB
4.6 KiB
原始 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:752WechatHookBot/WechatHook/http_client.py:775WechatHookBot/WechatHook/http_client.py:793WechatHookBot/WechatHook/http_client.py:801
2) 参数从哪里来(消息 XML)
图片消息里一般在 <img ... /> 标签:
- 原图 fileid 候选:
cdnbigimgurl/cdnmidimgurl/cdnhdimgurl/fileid - 原图 key:
aeskey - 缩略图 fileid:
cdnthumburl - 缩略图 key:
cdnthumbaeskey(若没有可回退aeskey)
本仓库提取逻辑:
WechatHookBot/WechatHook/client.py:1386WechatHookBot/WechatHook/client.py:1393
3) 你朋友可直接照抄的实现流程
- 解析微信消息 XML,拿到:
- 原图:
fileid = cdnbigimgurl(或候选),aeskey - 缩略图:
thumb_fileid = cdnthumburl,thumb_key = cdnthumbaeskey or aeskey
- 原图:
- 先调用一次原图:
imgType=1 - 如果失败,再调用缩略图:
imgType=2 - 成功后检查
out文件存在且大小> 0
推荐这么做的原因:有些消息原图拉不到,但缩略图能拉到。
4) 最小请求示例(curl)
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 最小实现(可直接给朋友)
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 即可。