新增小红书、XMPP、元宝、Zalo 四个渠道扩展。 小红书渠道扩展主要模块:config, gateway, webhook, outbound, streaming, pairing, security, dedupe, media, status, window XMPP 渠道扩展主要模块:plugin, config, gateway, outbound, streaming, pairing, security, dedupe, accounts, commands, muc, rate_limiter, stanza_utils, status, monitor 元宝渠道扩展主要模块:plugin, client, config_schema, gateway, outbound(chunk/queue/transport), inbound(dispatcher), streaming, pairing, security, accounts, actions, commands, codec(biz/conn), session, shared, utils Zalo 渠道扩展主要模块:api, config, gateway, webhook, outbound, pairing, security, session, polling, monitor, status
117 lines
3.6 KiB
Python
117 lines
3.6 KiB
Python
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")
|