import asyncio from base64 import urlsafe_b64decode from datetime import datetime, timezone from functools import lru_cache from typing import TYPE_CHECKING, Any, Literal, Optional from pydantic import BaseModel, Field from hibiapi.api.bika.constants import BikaConstants from hibiapi.utils.net import BaseNetClient if TYPE_CHECKING: from .api import BikaEndpoints class BikaLogin(BaseModel): email: str password: str class JWTHeader(BaseModel): alg: str typ: Literal["JWT"] class JWTBody(BaseModel): id: str = Field(alias="_id") iat: datetime exp: datetime @lru_cache(maxsize=4) def load_jwt(token: str): def b64pad(data: str): return data + "=" * (-len(data) % 4) head, body, _ = token.split(".") head_data = JWTHeader.parse_raw(urlsafe_b64decode(b64pad(head))) body_data = JWTBody.parse_raw(urlsafe_b64decode(b64pad(body))) return head_data, body_data class NetRequest(BaseNetClient): _token: Optional[str] = None def __init__(self): super().__init__( headers=BikaConstants.DEFAULT_HEADERS.copy(), proxies=BikaConstants.CONFIG["proxy"].as_dict(), ) self.auth_lock = asyncio.Lock() @property def token(self) -> Optional[str]: if self._token is None: return None _, body = load_jwt(self._token) return None if body.exp < datetime.now(timezone.utc) else self._token async def login(self, endpoint: "BikaEndpoints"): login_data = BikaConstants.CONFIG["account"].get(BikaLogin) login_result: dict[str, Any] = await endpoint.request( "auth/sign-in", body=login_data.dict(), no_token=True, ) assert login_result["code"] == 200, login_result["message"] if not ( isinstance(login_data := login_result.get("data"), dict) and "token" in login_data ): raise ValueError("failed to read Bika account token.") self._token = login_data["token"]