import asyncio import time from enum import Enum from typing import Optional, Any class EGovStatus(str, Enum): IDLE = "idle" STARTING = "starting" WAITING_MFA = "waiting_mfa" LOGGED_IN = "logged_in" DOWNLOADING = "downloading" ERROR = "error" class EGovState: def __init__(self): self.status: EGovStatus = EGovStatus.IDLE self.message: str = "" self.mfa_event: asyncio.Event = asyncio.Event() self.mfa_code: Optional[str] = None self.screenshot_b64: Optional[str] = None self.last_download: Optional[dict] = None # {filename, path, size} # ブラウザセッション(Playwright 方式, 後方互換用) self._playwright: Optional[Any] = None self._browser: Optional[Any] = None self._context: Optional[Any] = None self._page: Optional[Any] = None # ── OAuth2 / e-Gov API 方式 ────────────────────────────── self.access_token: Optional[str] = None self.refresh_token: Optional[str] = None self.token_expires_at: Optional[float] = None # unix timestamp self._pkce_verifier: Optional[str] = None self._oauth_state: Optional[str] = None def is_token_valid(self) -> bool: """アクセストークンが有効かどうかを返す(60秒マージン)""" if not self.access_token: return False if self.token_expires_at and time.time() >= self.token_expires_at - 60: return False return True def set_tokens(self, token_response: dict): """トークンレスポンスをステートにセット""" self.access_token = token_response.get("access_token") self.refresh_token = token_response.get("refresh_token") expires_in = token_response.get("expires_in", 300) self.token_expires_at = time.time() + int(expires_in) def reset(self): self.status = EGovStatus.IDLE self.message = "" self.mfa_event = asyncio.Event() self.mfa_code = None self.screenshot_b64 = None self.last_download = None self.access_token = None self.refresh_token = None self.token_expires_at = None self._pkce_verifier = None self._oauth_state = None async def close_browser(self): """ブラウザを閉じてリソースを解放""" try: if self._browser: await self._browser.close() except Exception: pass try: if self._playwright: await self._playwright.stop() except Exception: pass self._playwright = None self._browser = None self._context = None self._page = None # モジュールレベルのシングルトン state = EGovState()