from dataclasses import dataclass from typing import NamedTuple, Protocol from urllib.parse import quote import httpx MASK = "***" class ChatError(Exception): pass def mask_secret(text: str, secret: str) -> str: if not secret: return text for variant in (secret, quote(secret, safe=""), quote(secret)): text = text.replace(variant, MASK) return text def json_body(response: httpx.Response) -> dict: try: body = response.json() except ValueError: return {} return body if isinstance(body, dict) else {} @dataclass(frozen=True) class Button: label: str data: str = "" url: str = "" Buttons = tuple[tuple[Button, ...], ...] class Target(NamedTuple): backend: str target: str @dataclass(frozen=True) class MessageRef: backend: str target: str message_id: str class ChatAdapter(Protocol): name: str default_target: str async def send(self, target: str, text: str, buttons: Buttons = (), silent: bool = False) -> MessageRef: ... async def edit(self, ref: MessageRef, text: str, buttons: Buttons = (), silent: bool = False) -> None: ... async def check(self) -> str: ... async def aclose(self) -> None: ...