"""Async HTTP-Client für die MCM REST-API. Die TUI kommuniziert ausschließlich über diesen Client mit dem Backend. Dadurch läuft die TUI sowohl im Terminal (python main.py) als auch im Browser (textual serve tui_standalone.py) ohne Änderung. """ from __future__ import annotations import logging from typing import Any import httpx from config import settings logger = logging.getLogger(__name__) class MCMApiClient: """Thin async wrapper um die MCM REST-API.""" def __init__(self) -> None: self._base = f"http://127.0.0.1:{settings.port}/api/v1" self._headers = {"Authorization": f"Bearer {settings.api_key}"} # ── Konversationen ───────────────────────────────────────────────────────── async def get_conversations(self, channel: str | None = None) -> list[dict[str, Any]]: params: dict[str, Any] = {} if channel: params["channel"] = channel return await self._get("/conversations", params=params) async def get_conversation(self, conv_id: str) -> dict[str, Any] | None: try: return await self._get(f"/conversations/{conv_id}") except httpx.HTTPStatusError: return None async def get_messages( self, conv_id: str, limit: int = 100, offset: int = 0 ) -> list[dict[str, Any]]: return await self._get( f"/conversations/{conv_id}/messages", params={"limit": limit, "offset": offset}, ) # ── Nachrichten senden ───────────────────────────────────────────────────── async def send_message( self, channel: str, text: str, recipient_phone: str | None = None, recipient_telegram_id: str | None = None, reply_to_id: str | None = None, ) -> dict[str, Any]: body: dict[str, Any] = {"channel": channel, "text": text} if recipient_phone: body["recipient_phone"] = recipient_phone if recipient_telegram_id: body["recipient_telegram_id"] = recipient_telegram_id if reply_to_id: body["reply_to_id"] = reply_to_id return await self._post("/messages", body) # ── Kontakte ─────────────────────────────────────────────────────────────── async def get_contacts(self) -> list[dict[str, Any]]: return await self._get("/contacts") # ── Status ───────────────────────────────────────────────────────────────── async def get_channel_status(self) -> dict[str, Any]: return await self._get("/channels/status") # ── Interne Hilfsmethoden ────────────────────────────────────────────────── async def _get(self, path: str, params: dict | None = None) -> Any: async with httpx.AsyncClient(timeout=5.0, follow_redirects=True) as client: resp = await client.get( self._base + path, headers=self._headers, params=params ) resp.raise_for_status() return resp.json() async def _post(self, path: str, body: dict) -> Any: async with httpx.AsyncClient(timeout=10.0, follow_redirects=True) as client: resp = await client.post( self._base + path, headers=self._headers, json=body ) resp.raise_for_status() return resp.json()