from __future__ import annotations import time import httpx import markdown2 from app.config import settings from app.schemas import PublishResponse, WechatPublishRequest class WechatPublisher: def __init__(self) -> None: self._access_token = None self._expires_at = 0 async def publish_draft(self, req: WechatPublishRequest) -> PublishResponse: if not settings.wechat_appid or not settings.wechat_secret: return PublishResponse(ok=False, detail="缺少 WECHAT_APPID / WECHAT_SECRET 配置") token = await self._get_access_token() if not token: return PublishResponse(ok=False, detail="获取微信 access_token 失败") html = markdown2.markdown(req.body_markdown) payload = { "articles": [ { "title": req.title, "author": req.author or settings.wechat_author, "digest": req.summary, "content": html, "content_source_url": "", "need_open_comment": 0, "only_fans_can_comment": 0, } ] } async with httpx.AsyncClient(timeout=25) as client: url = f"https://api.weixin.qq.com/cgi-bin/draft/add?access_token={token}" r = await client.post(url, json=payload) data = r.json() if data.get("errcode", 0) != 0: return PublishResponse(ok=False, detail=f"微信发布失败: {data}", data=data) return PublishResponse(ok=True, detail="已发布到公众号草稿箱", data=data) async def _get_access_token(self) -> str | None: now = int(time.time()) if self._access_token and now < self._expires_at - 60: return self._access_token async with httpx.AsyncClient(timeout=20) as client: r = await client.get( "https://api.weixin.qq.com/cgi-bin/token", params={ "grant_type": "client_credential", "appid": settings.wechat_appid, "secret": settings.wechat_secret, }, ) data = r.json() token = data.get("access_token") if not token: return None self._access_token = token self._expires_at = now + int(data.get("expires_in", 7200)) return token