import json import logging import httpx logger = logging.getLogger(__name__) SEND_ALL_URL = "https://m.api.weibo.com/2/messages/sendall.json" DELETE_URL = "https://m.api.weibo.com/2/messages/delete" class WeiboBroadcast: def __init__(self, gateway=None): self._gateway = gateway self._http: httpx.AsyncClient | None = None async def send_by_group( self, group_id: int, msg_type: str, content: dict, ) -> dict | None: """ 按分组群发。 msg_type: text / articles / news content: - text: {"text": "消息内容"} - articles: {"articles": [{"display_name": "标题", "summary": "摘要", "image_url": "...", "url": "..."}]} - news: {"news": [{"display_name": "标题", "summary": "摘要", "image_url": "...", "url": "..."}]} """ return await self._send_all({"filter": {"group_id": group_id}}, msg_type, content) async def send_by_users( self, user_ids: list[int], msg_type: str, content: dict, ) -> dict | None: """按 UID 列表群发""" return await self._send_all({"touser": user_ids}, msg_type, content) async def _send_all(self, target: dict, msg_type: str, content: dict) -> dict | None: if self._http is None: self._http = httpx.AsyncClient(timeout=30.0) token = self._gateway.access_token if self._gateway else None if not token: logger.error("No access_token for broadcast") return None payload = { "access_token": token, **target, "type": msg_type, "data": json.dumps(content), } try: resp = await self._http.post(SEND_ALL_URL, data=payload) data = resp.json() if data.get("error_code"): logger.error("Broadcast failed: %s", data) return data except Exception: logger.exception("Broadcast error") return None async def delete_broadcast(self, msg_id: str) -> bool: """删除已发送的群发消息(仅 news 和 image 类型支持)""" if self._http is None: self._http = httpx.AsyncClient(timeout=15.0) token = self._gateway.access_token if self._gateway else None if not token: return False try: resp = await self._http.post( DELETE_URL, data={"access_token": token, "id": msg_id}, ) data = resp.json() return "error_code" not in data except Exception: logger.exception("Delete broadcast error") return False async def close(self): if self._http: await self._http.aclose() self._http = None