fix max-bot, add tests
This commit is contained in:
parent
7abbaf7e7a
commit
2ad1438e1c
17 changed files with 1621 additions and 494 deletions
153
adapter/max/api_client.py
Normal file
153
adapter/max/api_client.py
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
"""HTTP client for MAX Bot API (platform-api.max.ru)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MaxApiError(Exception):
|
||||
def __init__(self, status: int, payload: Any):
|
||||
super().__init__(f"MAX API error {status}: {payload}")
|
||||
self.status = status
|
||||
self.payload = payload
|
||||
|
||||
|
||||
class MaxBotApi:
|
||||
"""
|
||||
Minimal async client. Auth: raw token in Authorization header (same as official TS SDK).
|
||||
"""
|
||||
|
||||
def __init__(self, token: str, base_url: str = "https://platform-api.max.ru") -> None:
|
||||
self._token = token
|
||||
self._base = base_url.rstrip("/")
|
||||
self._client = httpx.AsyncClient(
|
||||
base_url=self._base,
|
||||
headers={"Authorization": token},
|
||||
timeout=httpx.Timeout(120.0, connect=30.0),
|
||||
)
|
||||
|
||||
async def aclose(self) -> None:
|
||||
await self._client.aclose()
|
||||
|
||||
async def _request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
*,
|
||||
params: dict[str, Any] | None = None,
|
||||
json: Any | None = None,
|
||||
) -> Any:
|
||||
response = await self._client.request(method, path, params=params, json=json)
|
||||
payload: Any
|
||||
try:
|
||||
payload = response.json()
|
||||
except Exception:
|
||||
payload = response.text
|
||||
if response.status_code >= 400:
|
||||
if isinstance(payload, dict):
|
||||
raise MaxApiError(
|
||||
response.status_code,
|
||||
{"code": payload.get("code"), "message": payload.get("message", payload)},
|
||||
)
|
||||
raise MaxApiError(response.status_code, payload)
|
||||
return payload
|
||||
|
||||
async def get_me(self) -> dict[str, Any]:
|
||||
data = await self._request("GET", "/me")
|
||||
return dict(data) if isinstance(data, dict) else {}
|
||||
|
||||
async def get_updates(
|
||||
self,
|
||||
*,
|
||||
marker: int | None = None,
|
||||
limit: int = 100,
|
||||
timeout: int = 30,
|
||||
types: list[str] | None = None,
|
||||
) -> tuple[list[dict[str, Any]], int | None]:
|
||||
params: dict[str, Any] = {"limit": limit, "timeout": timeout}
|
||||
if marker is not None:
|
||||
params["marker"] = marker
|
||||
if types:
|
||||
params["types"] = ",".join(types)
|
||||
data = await self._request("GET", "/updates", params=params)
|
||||
if not isinstance(data, dict):
|
||||
return [], None
|
||||
raw_updates = data.get("updates") or []
|
||||
updates = [u for u in raw_updates if isinstance(u, dict)]
|
||||
marker_out = data.get("marker")
|
||||
return updates, marker_out if isinstance(marker_out, int) else None
|
||||
|
||||
async def send_message_to_chat(
|
||||
self,
|
||||
chat_id: int,
|
||||
*,
|
||||
text: str | None = None,
|
||||
attachments: list[dict[str, Any]] | None = None,
|
||||
fmt: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
params: dict[str, Any] = {"chat_id": chat_id}
|
||||
body: dict[str, Any] = {}
|
||||
if text is not None:
|
||||
body["text"] = text
|
||||
if attachments is not None:
|
||||
body["attachments"] = attachments
|
||||
if fmt:
|
||||
body["format"] = fmt
|
||||
return await self._request("POST", "/messages", params=params, json=body)
|
||||
|
||||
async def send_message_to_user(
|
||||
self,
|
||||
user_id: int,
|
||||
*,
|
||||
text: str | None = None,
|
||||
attachments: list[dict[str, Any]] | None = None,
|
||||
fmt: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
params: dict[str, Any] = {"user_id": user_id}
|
||||
body: dict[str, Any] = {}
|
||||
if text is not None:
|
||||
body["text"] = text
|
||||
if attachments is not None:
|
||||
body["attachments"] = attachments
|
||||
if fmt:
|
||||
body["format"] = fmt
|
||||
return await self._request("POST", "/messages", params=params, json=body)
|
||||
|
||||
async def send_chat_action(self, chat_id: int, action: str) -> Any:
|
||||
return await self._request(
|
||||
"POST",
|
||||
f"/chats/{chat_id}/actions",
|
||||
json={"action": action},
|
||||
)
|
||||
|
||||
async def get_upload_url(self, upload_type: str) -> dict[str, Any]:
|
||||
data = await self._request("POST", "/uploads", params={"type": upload_type})
|
||||
return dict(data) if isinstance(data, dict) else {}
|
||||
|
||||
async def answer_callback(
|
||||
self,
|
||||
callback_id: str,
|
||||
*,
|
||||
message: dict[str, Any] | None = None,
|
||||
notification: str | None = None,
|
||||
) -> Any:
|
||||
body: dict[str, Any] = {}
|
||||
if message is not None:
|
||||
body["message"] = message
|
||||
if notification is not None:
|
||||
body["notification"] = notification
|
||||
return await self._request(
|
||||
"POST",
|
||||
"/answers",
|
||||
params={"callback_id": callback_id},
|
||||
json=body if body else {},
|
||||
)
|
||||
|
||||
async def download_file(self, url: str) -> bytes:
|
||||
response = await self._client.get(url)
|
||||
response.raise_for_status()
|
||||
return response.content
|
||||
Loading…
Add table
Add a link
Reference in a new issue