import logging import httpx from yuxi.channel.extensions.zalo.errors import ( ZaloApiError, ZaloAuthError, ZaloNetworkError, ZaloRateLimitError, ) logger = logging.getLogger(__name__) class ZaloBotApi: BASE = "https://bot-api.zaloplatforms.com" def __init__(self, token: str, proxy: str | None = None): self._token = token timeout = httpx.Timeout(35.0) self._client = httpx.AsyncClient(proxy=proxy, timeout=timeout) async def close(self): await self._client.aclose() async def _call(self, method: str, body: dict | None = None) -> dict: url = f"{self.BASE}/bot{self._token}/{method}" try: resp = await self._client.post(url, json=body or {}) resp.raise_for_status() except httpx.HTTPStatusError as e: if e.response.status_code == 408: raise error_code = -1 description = "Unknown error" try: data = e.response.json() error_code = data.get("error_code", -1) description = data.get("description", "Unknown error") except (ValueError, AttributeError): description = f"HTTP {e.response.status_code}" raise self._classify_error(error_code, description) from e except httpx.RequestError as e: raise ZaloNetworkError(f"Request failed: {e}") from e data = resp.json() if not data.get("ok"): error_code = data.get("error_code", -1) description = data.get("description", "Unknown error") raise self._classify_error(error_code, description) return data @staticmethod def _classify_error(error_code: int, description: str) -> ZaloApiError: if error_code in (100, 101, 102): return ZaloAuthError(error_code, description) if error_code == 103: return ZaloRateLimitError(error_code, description) return ZaloApiError(error_code, description) async def get_me(self) -> dict: return await self._call("getMe") async def send_message(self, chat_id: str, text: str) -> dict: return await self._call( "sendMessage", { "chat_id": chat_id, "text": text, }, ) async def send_photo(self, chat_id: str, photo_url: str, caption: str = "") -> dict: body: dict = { "chat_id": chat_id, "photo": photo_url, } if caption: body["caption"] = caption return await self._call("sendPhoto", body) async def send_sticker(self, chat_id: str, sticker_id: str) -> dict: return await self._call( "sendSticker", { "chat_id": chat_id, "sticker": sticker_id, }, ) async def send_chat_action(self, chat_id: str, action: str = "typing") -> dict: return await self._call( "sendChatAction", { "chat_id": chat_id, "action": action, }, ) async def get_updates(self, timeout: int = 30) -> dict: return await self._call("getUpdates", {"timeout": timeout}) async def set_webhook(self, url: str, secret_token: str) -> dict: return await self._call( "setWebhook", { "url": url, "secret_token": secret_token, }, ) async def delete_webhook(self) -> dict: return await self._call("deleteWebhook") async def get_webhook_info(self) -> dict: return await self._call("getWebhookInfo")