from __future__ import annotations from enum import StrEnum class TelegramErrorKind(StrEnum): RETRYABLE = "retryable" RATE_LIMITED = "rate_limited" AUTH = "auth" CHAT_NOT_FOUND = "chat_not_found" BOT_BLOCKED = "bot_blocked" PARSE_ERROR = "parse_error" MEDIA_TOO_LARGE = "media_too_large" FATAL = "fatal" NETWORK = "network" def classify_error( status_code: int | None, error_body: dict | str | None, ) -> tuple[TelegramErrorKind, str, float | None]: description = "" if isinstance(error_body, dict): description = error_body.get("description", "") elif isinstance(error_body, str): description = error_body retry_after = None if isinstance(error_body, dict): params = error_body.get("parameters", {}) retry_after = params.get("retry_after") if status_code == 429: return TelegramErrorKind.RATE_LIMITED, description, float(retry_after) if retry_after else 30.0 if status_code == 401: return TelegramErrorKind.AUTH, description, None if status_code == 403: if "bot was kicked" in description.lower() or "bot is not a member" in description.lower(): return TelegramErrorKind.BOT_BLOCKED, description, None return TelegramErrorKind.AUTH, description, None if status_code == 400: if "chat not found" in description.lower(): return TelegramErrorKind.CHAT_NOT_FOUND, description, None if "can't parse entities" in description.lower() or "parse" in description.lower(): return TelegramErrorKind.PARSE_ERROR, description, None if "file is too big" in description.lower() or "request entity too large" in description.lower(): return TelegramErrorKind.MEDIA_TOO_LARGE, description, None return TelegramErrorKind.FATAL, description, None if status_code and status_code >= 500: return TelegramErrorKind.RETRYABLE, description, None if status_code is None: return TelegramErrorKind.NETWORK, description, None return TelegramErrorKind.FATAL, description, None def is_retryable(kind: TelegramErrorKind) -> bool: return kind in (TelegramErrorKind.RETRYABLE, TelegramErrorKind.RATE_LIMITED, TelegramErrorKind.NETWORK) MAX_RETRIES = 3