From 54b444b65164a4f5d2481adcddd4a09af3daf768 Mon Sep 17 00:00:00 2001 From: qihu-bot Date: Tue, 11 Aug 2026 21:31:15 +0800 Subject: [PATCH] #967 implement media_relay endpoint - fix WeChat bot image relay media_relay was a stub (empty function body). Now implements actual image/video streaming relay from S3 to WeChat clients, with domain whitelist and httpx streaming. Fixes WeChat bot image generation responses not rendered. --- backend/python/app/api/chat.py | 47 ++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/backend/python/app/api/chat.py b/backend/python/app/api/chat.py index 084be15..b470782 100644 --- a/backend/python/app/api/chat.py +++ b/backend/python/app/api/chat.py @@ -155,6 +155,17 @@ _MEDIA_RELAY_ALLOWED = ("agnes-ai.com", "agnes-ai.space", "qihuyun.com") + + +# Media relay client (trust_env=False to bypass proxy for image fetches) +_media_client = None +def _get_media_client(): + global _media_client + if _media_client is None: + _media_client = httpx.Client(timeout=30.0, follow_redirects=True, trust_env=False) + return _media_client + + @router.get("/media/relay") @@ -197,6 +208,42 @@ async def media_relay(url: str = Query(...)): """ + import urllib.parse as _up + import httpx + from fastapi.responses import StreamingResponse + + host = (_up.urlparse(url).hostname or "").lower() + # 白名单域名检查 + allowed = ("siliconflow.cn", "s3.siliconflow.cn", "aliyuncs.com", "tencentcos.cn", + "qpic.cn", "alicdn.com", "api.openai.com", "myqcloud.com") + if not any(a in host for a in allowed): + raise HTTPException(status_code=400, detail="unsupported media host") + + try: + client = _get_media_client() + resp = client.get(url, timeout=30.0, follow_redirects=True) + if resp.status_code != 200: + raise HTTPException(status_code=502, detail=f"upstream returned {resp.status_code}") + ct = resp.headers.get("content-type", "application/octet-stream") + cl = resp.headers.get("content-length") + headers = {} + if ct: + headers["content-type"] = ct + if cl: + headers["content-length"] = cl + headers["cache-control"] = "public, max-age=3600" + return StreamingResponse( + resp.iter_bytes(chunk_size=65536), + status_code=200, + headers=headers, + media_type=ct + ) + except httpx.TimeoutException: + raise HTTPException(status_code=504, detail="upstream timeout") + except Exception as e: + raise HTTPException(status_code=502, detail=f"relay error: {str(e)[:200]}") + +