Files
WeChatHookBot/docs/原始CDN下载实现指南_给第三方框架.md

4.6 KiB
Raw Permalink Blame History

原始 CDN 下载实现指南(给第三方框架)

这份文档只讲“原始 Hook 协议怎么下图”,不依赖本项目的 WechatHookClientImageProcessor 封装。

1) 接口结论(按本仓库实际代码验证)

  • 接口:POST /api/cdn_download
  • 请求体字段:
    • fileidCDN 文件标识
    • asekeyAES 密钥(注意字段名是 asekey,不是 aeskey
    • imgType1 原图,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
  • 原图 keyaeskey
  • 缩略图 fileidcdnthumburl
  • 缩略图 keycdnthumbaeskey(若没有可回退 aeskey

本仓库提取逻辑:

  • WechatHookBot/WechatHook/client.py:1386
  • WechatHookBot/WechatHook/client.py:1393

3) 你朋友可直接照抄的实现流程

  1. 解析微信消息 XML拿到
    • 原图:fileid = cdnbigimgurl(或候选)aeskey
    • 缩略图:thumb_fileid = cdnthumburlthumb_key = cdnthumbaeskey or aeskey
  2. 先调用一次原图:imgType=1
  3. 如果失败,再调用缩略图:imgType=2
  4. 成功后检查 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 即可。