From 2a063a08ede401c27c65759be00ed1edd7338472 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 19 Feb 2026 11:37:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=B8=8A=E4=BC=A0=E4=B8=8EMediaPost?= =?UTF-8?q?=E9=93=BE=E8=B7=AF=E5=A2=9E=E5=8A=A0=E7=9B=B4=E8=BF=9E=E5=92=8C?= =?UTF-8?q?urllib=E5=85=9C=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/reverse/assets_upload.py | 38 +++++++++++ app/services/reverse/media_post.py | 98 ++++++++++++++++++++++++--- 2 files changed, 126 insertions(+), 10 deletions(-) diff --git a/app/services/reverse/assets_upload.py b/app/services/reverse/assets_upload.py index e271d01..f2295f1 100644 --- a/app/services/reverse/assets_upload.py +++ b/app/services/reverse/assets_upload.py @@ -179,6 +179,44 @@ class AssetsUploadReverse: "AssetsUpload transient exception, mark as retryable: " f"{err_msg}" ) + # 部分 '"code"' 异常不会走到上面的降级分支,这里再强制兜底一次。 + try: + response = await session.post( + UPLOAD_API, + headers=headers, + json=payload, + timeout=timeout, + ) + if response.status_code == 200: + logger.info( + "AssetsUpload recovered by forced direct fallback after transient error" + ) + return response + except Exception as forced_direct_err: + logger.warning( + "AssetsUpload forced direct fallback failed, " + f"error={forced_direct_err}" + ) + + try: + response = await AssetsUploadReverse._urllib_post( + url=UPLOAD_API, + headers=headers, + payload=payload, + timeout=timeout, + proxy_url=proxy_url, + ) + if response.status_code == 200: + logger.info( + "AssetsUpload recovered by forced urllib fallback after transient error" + ) + return response + except Exception as forced_urllib_err: + logger.warning( + "AssetsUpload forced urllib fallback failed, " + f"error={forced_urllib_err}" + ) + raise UpstreamException( message=f"AssetsUpload transient failure: {err_msg}", details={"status": 403, "error": err_msg}, diff --git a/app/services/reverse/media_post.py b/app/services/reverse/media_post.py index d50f5e3..c341dde 100644 --- a/app/services/reverse/media_post.py +++ b/app/services/reverse/media_post.py @@ -2,6 +2,10 @@ Reverse interface: media post create. """ +import asyncio +import json +import urllib.request +from dataclasses import dataclass from typing import Any from curl_cffi.requests import AsyncSession @@ -16,6 +20,51 @@ MEDIA_POST_API = "https://grok.com/rest/media/post/create" class MediaPostReverse: """/rest/media/post/create reverse interface.""" + @dataclass + class _SimpleResponse: + status_code: int + headers: dict[str, str] + text: str + + def json(self): + return json.loads(self.text or "{}") + + @staticmethod + async def _urllib_post( + url: str, headers: dict[str, str], payload: dict[str, Any], timeout: int, proxy_url: str + ) -> "MediaPostReverse._SimpleResponse": + body = json.dumps(payload).encode("utf-8") + opener = None + if proxy_url: + opener = urllib.request.build_opener( + urllib.request.ProxyHandler({"http": proxy_url, "https": proxy_url}) + ) + req = urllib.request.Request(url=url, data=body, headers=headers, method="POST") + + def _do_post(): + if opener is not None: + with opener.open(req, timeout=timeout) as resp: + status = int(getattr(resp, "status", 200) or 200) + raw_headers = { + str(k).lower(): str(v) for k, v in dict(resp.headers.items()).items() + } + text = resp.read().decode("utf-8", errors="replace") + return status, raw_headers, text + with urllib.request.urlopen(req, timeout=timeout) as resp: + status = int(getattr(resp, "status", 200) or 200) + raw_headers = { + str(k).lower(): str(v) for k, v in dict(resp.headers.items()).items() + } + text = resp.read().decode("utf-8", errors="replace") + return status, raw_headers, text + + status, raw_headers, text = await asyncio.to_thread(_do_post) + return MediaPostReverse._SimpleResponse( + status_code=status, + headers=raw_headers, + text=text, + ) + @staticmethod async def request( session: AsyncSession, @@ -39,6 +88,7 @@ class MediaPostReverse: # Get proxies base_proxy = get_config("proxy.base_proxy_url") proxies = {"http": base_proxy, "https": base_proxy} if base_proxy else None + proxy_url = base_proxy # Build headers headers = build_headers( @@ -64,23 +114,51 @@ class MediaPostReverse: browser = get_config("proxy.browser") async def _do_request(): - response = await session.post( - MEDIA_POST_API, - headers=headers, - json=payload, - timeout=timeout, - proxies=proxies, - impersonate=browser, - ) + try: + response = await session.post( + MEDIA_POST_API, + headers=headers, + json=payload, + timeout=timeout, + proxies=proxies, + impersonate=browser, + ) + except Exception as first_err: + logger.warning( + "MediaPostReverse primary request failed, fallback direct: " + f"error={first_err}" + ) + try: + response = await session.post( + MEDIA_POST_API, + headers=headers, + json=payload, + timeout=timeout, + ) + except Exception as second_err: + logger.warning( + "MediaPostReverse direct curl request failed, " + f"fallback urllib: error={second_err}" + ) + response = await MediaPostReverse._urllib_post( + url=MEDIA_POST_API, + headers=headers, + payload=payload, + timeout=timeout, + proxy_url=proxy_url, + ) if response.status_code != 200: content = "" try: - content = await response.text() + content = (response.text or "").strip().replace("\n", " ") except Exception: pass + if len(content) > 300: + content = f"{content[:300]}...(len={len(content)})" logger.error( - f"MediaPostReverse: Media post create failed, {response.status_code}", + "MediaPostReverse: Media post create failed, " + f"status={response.status_code}, body={content or '-'}", extra={"error_type": "UpstreamException"}, ) raise UpstreamException(