from __future__ import annotations import uuid import httpx from app.mywhoosh.models import MyWhooshToken from app.mywhoosh.tokenstore import MyWhooshTokenStore LOGIN_URL = "https://services.mywhoosh.com/http-service/api/login" ACTIVITIES_BASE = "https://service14.mywhoosh.com/v2/" class MyWhooshError(RuntimeError): pass class MyWhooshAuthError(MyWhooshError): pass class MyWhooshTransientError(MyWhooshError): pass class MyWhooshIntegrationError(MyWhooshError): pass class MyWhooshClient: def __init__(self, token_store: MyWhooshTokenStore, http_client: httpx.AsyncClient | None = None) -> None: self.token_store = token_store self.http = http_client or httpx.AsyncClient(timeout=30.0) self.token = token_store.load() async def login(self, email: str, password: str) -> None: payload = { "Username": email, "Password": password, "Platform": "Android", "Action": 1001, "CorrelationId": str(uuid.uuid4()), "DeviceId": str(uuid.uuid4()), "Authorization": "", } try: response = await self.http.post(LOGIN_URL, json=payload) except httpx.TransportError as exc: raise MyWhooshTransientError("MyWhoosh login request failed") from exc if response.status_code >= 500: raise MyWhooshTransientError(f"MyWhoosh login returned HTTP {response.status_code}") if response.status_code >= 400: raise MyWhooshAuthError(f"MyWhoosh login returned HTTP {response.status_code}") try: body = response.json() except ValueError as exc: raise MyWhooshIntegrationError("MyWhoosh login returned invalid JSON") from exc if body.get("Success") is not True or not body.get("AccessToken"): raise MyWhooshAuthError(str(body.get("Message") or "MyWhoosh login failed")) self.token = MyWhooshToken( access_token=str(body["AccessToken"]), refresh_token=str(body["RefreshToken"]) if body.get("RefreshToken") else None, whoosh_id=str(body["WhooshId"]) if body.get("WhooshId") else None, ) self.token_store.save(self.token) async def ensure_authenticated(self, email: str, password: str) -> None: if self.token is None: await self.login(email, password)