This commit is contained in:
Daniel
2026-04-28 11:50:55 +08:00
parent 1bbabc2a78
commit 2724e69b4f
20 changed files with 3881 additions and 554 deletions

View File

@@ -69,11 +69,11 @@ class WechatPublisher:
def _resolve_account(self, account: dict | None = None) -> dict[str, str]:
src = account or {}
appid = (src.get("appid") or settings.wechat_appid or "").strip()
secret = (src.get("secret") or settings.wechat_secret or "").strip()
author = (src.get("author") or settings.wechat_author or "").strip()
thumb_media_id = (src.get("thumb_media_id") or settings.wechat_thumb_media_id or "").strip()
thumb_image_path = (src.get("thumb_image_path") or settings.wechat_thumb_image_path or "").strip()
appid = (src.get("appid") or "").strip()
secret = (src.get("secret") or "").strip()
author = (src.get("author") or "").strip()
thumb_media_id = (src.get("thumb_media_id") or "").strip()
thumb_image_path = (src.get("thumb_image_path") or "").strip()
return {
"appid": appid,
"secret": secret,
@@ -132,7 +132,7 @@ class WechatPublisher:
{
"article_type": "news",
"title": req.title[:32] if len(req.title) > 32 else req.title,
"author": (req.author or acct["author"] or settings.wechat_author)[:16],
"author": (req.author or acct["author"] or "AI发糕")[:16],
"digest": (req.summary or "")[:128],
"content": html,
"content_source_url": "",
@@ -246,6 +246,37 @@ class WechatPublisher:
)
return PublishResponse(ok=True, detail="素材上传成功", data=material)
async def upload_article_image(
self, filename: str, content: bytes, request_id: str = "", account: dict | None = None
) -> PublishResponse:
"""上传图文正文图片uploadimg返回可直接插入正文 HTML/Markdown 的 URL。"""
rid = request_id or "-"
acct = self._resolve_account(account)
if not acct["appid"] or not acct["secret"]:
return PublishResponse(ok=False, detail="缺少 WECHAT_APPID / WECHAT_SECRET 配置")
if not content:
return PublishResponse(ok=False, detail="素材文件为空")
token, _, token_err_body = await self._get_access_token(acct["appid"], acct["secret"])
if not token:
return PublishResponse(ok=False, detail=_detail_for_token_error(token_err_body), data=token_err_body)
async with httpx.AsyncClient(timeout=60) as client:
out = await self._upload_article_image_url(client, token, content, filename)
if not out:
return PublishResponse(
ok=False,
detail="正文配图上传失败请检查图片格式与大小jpg/png建议小于 1MB或查看日志 wechat_uploadimg_failed",
)
logger.info(
"wechat_uploadimg_ok rid=%s filename=%s url=%s",
rid,
filename,
out.get("url"),
)
return PublishResponse(ok=True, detail="正文配图上传成功", data=out)
async def _upload_permanent_image(
self, client: httpx.AsyncClient, token: str, content: bytes, filename: str
) -> dict[str, str] | None:
@@ -263,6 +294,23 @@ class WechatPublisher:
return None
return {"media_id": mid, "url": data.get("url") or ""}
async def _upload_article_image_url(
self, client: httpx.AsyncClient, token: str, content: bytes, filename: str
) -> dict[str, str] | None:
url = f"https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token={token}"
ctype = "image/png" if filename.lower().endswith(".png") else "image/jpeg"
files = {"media": (filename, content, ctype)}
r = await client.post(url, files=files)
data = r.json() if r.content else {}
if isinstance(data, dict) and data.get("errcode"):
logger.warning("wechat_uploadimg_failed body=%s", data)
return None
image_url = (data.get("url") if isinstance(data, dict) else "") or ""
if not image_url:
logger.warning("wechat_uploadimg_no_url body=%s", data)
return None
return {"url": image_url}
async def _resolve_thumb_media_id(
self, token: str, rid: str, *, force_skip_explicit: bool = False, account: dict | None = None
) -> str | None: