from __future__ import annotations import json from typing import Any from urllib import parse, request as urllib_request class TelegramAPI: def __init__(self, token: str, proxy_url: str | None = None) -> None: self.base_url = f"https://api.telegram.org/bot{token}" handlers = [] if proxy_url: handlers.append(urllib_request.ProxyHandler({"https": proxy_url, "http": proxy_url})) self.opener = urllib_request.build_opener(*handlers) if handlers else urllib_request.build_opener() def _post(self, method: str, payload: dict[str, Any]) -> dict[str, Any]: data = json.dumps(payload).encode("utf-8") req = urllib_request.Request( f"{self.base_url}/{method}", data=data, headers={"Content-Type": "application/json"}, method="POST", ) with self.opener.open(req, timeout=90) as response: return json.loads(response.read().decode("utf-8")) def _get(self, method: str, params: dict[str, Any]) -> dict[str, Any]: query = parse.urlencode(params) with self.opener.open(f"{self.base_url}/{method}?{query}", timeout=90) as response: return json.loads(response.read().decode("utf-8")) def get_updates(self, offset: int | None, timeout: int) -> list[dict[str, Any]]: params: dict[str, Any] = {"timeout": timeout} if offset is not None: params["offset"] = offset response = self._get("getUpdates", params) return response.get("result", []) def send_message( self, chat_id: int, text: str, *, parse_mode: str | None = None, reply_markup: dict[str, Any] | None = None, ) -> int: payload: dict[str, Any] = {"chat_id": chat_id, "text": text} if parse_mode: payload["parse_mode"] = parse_mode if reply_markup: payload["reply_markup"] = reply_markup response = self._post("sendMessage", payload) result = response.get("result") or {} return int(result.get("message_id", 0)) def edit_message_text( self, chat_id: int, message_id: int, text: str, *, parse_mode: str | None = None, reply_markup: dict[str, Any] | None = None, ) -> None: try: payload: dict[str, Any] = { "chat_id": chat_id, "message_id": message_id, "text": text, } if parse_mode: payload["parse_mode"] = parse_mode if reply_markup is not None: payload["reply_markup"] = reply_markup self._post( "editMessageText", payload, ) except Exception as exc: if "message is not modified" in str(exc).lower(): return raise def send_chat_action(self, chat_id: int, action: str = "typing") -> None: self._post( "sendChatAction", { "chat_id": chat_id, "action": action, }, ) def answer_callback_query(self, callback_query_id: str, text: str | None = None) -> None: payload: dict[str, Any] = {"callback_query_id": callback_query_id} if text: payload["text"] = text self._post("answerCallbackQuery", payload)