Files
site_aegisone/max_bot/app/max_api.py
T

160 lines
6.3 KiB
Python

import os
import re
import hmac
import hashlib
import logging
from datetime import datetime
from typing import Optional
import aiohttp
logger = logging.getLogger(__name__)
MAX_API_URL = "https://platform-api.max.ru"
class MaxAPIClient:
def __init__(self, token: str, test_mode: bool = False):
self.token = token
self.test_mode = test_mode
self._session: Optional[aiohttp.ClientSession] = None
self.test_messages: list = []
async def _get_session(self) -> aiohttp.ClientSession:
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession()
return self._session
async def close(self):
if self._session and not self._session.closed:
await self._session.close()
def _headers(self) -> dict:
return {"Authorization": self.token, "Content-Type": "application/json"}
async def _request(self, method: str, path: str, **kwargs) -> dict:
if self.test_mode:
logger.info(f"[TEST_MODE] {method} {path} kwargs={kwargs}")
return {"ok": True, "test_mode": True}
session = await self._get_session()
url = f"{MAX_API_URL}{path}"
async with session.request(method, url, headers=self._headers(), **kwargs) as resp:
if resp.status >= 400:
body = await resp.text()
logger.error(f"Max API error {resp.status}: {body}")
resp.raise_for_status()
return await resp.json()
async def get_me(self) -> dict:
return await self._request("GET", "/me")
async def send_message(self, user_id: int = None, chat_id: int = None, text: str = "",
attachments: list = None, format: str = None,
disable_link_preview: bool = False, notify: bool = True) -> dict:
body = {"text": text}
if attachments:
body["attachments"] = attachments
if format:
body["format"] = format
if not disable_link_preview:
body["disable_link_preview"] = False
if not notify:
body["notify"] = False
params = {}
if user_id:
params["user_id"] = user_id
if chat_id:
params["chat_id"] = chat_id
if self.test_mode:
msg = {"role": "bot", "text": text, "format": format}
if attachments:
msg["attachments"] = attachments
self.test_messages.append(msg)
return await self._request("POST", "/messages", json=body, params=params)
async def send_message_with_keyboard(self, user_id: int, text: str, buttons: list,
format: str = None) -> dict:
attachments = [{
"type": "inline_keyboard",
"payload": {"buttons": buttons}
}]
return await self.send_message(user_id=user_id, text=text, attachments=attachments, format=format)
async def edit_message(self, message_id: int, text: str, attachments: list = None) -> dict:
body = {"text": text}
if attachments:
body["attachments"] = attachments
return await self._request("PUT", f"/messages/{message_id}", json=body)
async def get_message(self, message_id: int) -> dict:
return await self._request("GET", f"/messages/{message_id}")
async def get_messages(self, user_id: int = None, chat_id: int = None, limit: int = 10) -> dict:
params = {"limit": limit}
if user_id:
params["user_id"] = user_id
if chat_id:
params["chat_id"] = chat_id
return await self._request("GET", "/messages", params=params)
async def answer_callback(self, callback_id: str, text: str = "", show_alert: bool = False) -> dict:
body = {"callback_id": callback_id}
if text:
body["text"] = text
if show_alert:
body["show_alert"] = True
return await self._request("POST", "/answers", json=body)
async def subscribe_webhook(self, url: str, update_types: list = None, secret: str = None) -> dict:
body = {"url": url}
if update_types:
body["update_types"] = update_types
if secret:
body["secret"] = secret
return await self._request("POST", "/subscriptions", json=body)
async def get_subscriptions(self) -> dict:
return await self._request("GET", "/subscriptions")
async def unsubscribe_webhook(self) -> dict:
return await self._request("DELETE", "/subscriptions")
async def upload_file(self, file_path: str) -> dict:
session = await self._get_session()
url = f"{MAX_API_URL}/uploads"
with open(file_path, "rb") as f:
data = aiohttp.FormData()
data.add_field("file", f, filename=os.path.basename(file_path))
async with session.post(url, headers={"Authorization": self.token}, data=data) as resp:
resp.raise_for_status()
return await resp.json()
def verify_webhook_secret(self, header_value: str, expected_secret: str) -> bool:
if not expected_secret:
return True
return hmac.compare_digest(header_value, expected_secret)
async def send_file(self, user_id: int, file_path: str, caption: str = "") -> dict:
upload = await self.upload_file(file_path)
file_id = upload.get("file_id", "")
body = {"text": caption, "attachments": [{"type": "file", "payload": {"file_id": file_id}}]}
return await self._request("POST", "/messages", json=body, params={"user_id": user_id})
async def get_file_url(self, file_id: str) -> str:
resp = await self._request("GET", f"/uploads/{file_id}")
return resp.get("url", "")
async def send_contact_request(self, user_id: int, text: str = "Пожалуйста, отправьте ваш контакт") -> dict:
attachments = [{
"type": "contact_request",
"payload": {"text": text}
}]
return await self.send_message(user_id=user_id, text=text, attachments=attachments)
def verify_contact_hash(self, vcf_info: str, access_token: str, expected_hash: str) -> bool:
vcf_normalized = vcf_info.replace("\\r\\n", "\r\n")
computed = hmac.new(access_token.encode(), vcf_normalized.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(computed, expected_hash)