from __future__ import annotations import asyncio from dataclasses import dataclass from typing import Any from yuxi.utils.logging_config import logger from .bridge import BridgeClient from .login import LoginFlow @dataclass class GatewayContext: bridge_url: str config: dict[str, Any] bridge: BridgeClient | None = None login_flow: LoginFlow | None = None class ZaloGateway: def __init__(self): self._accounts: dict[str, GatewayContext] = {} self._lock = asyncio.Lock() async def start_account(self, profile: str, config: dict[str, Any]) -> GatewayContext: async with self._lock: if profile in self._accounts: logger.info(f"[ZaloUser] Gateway account '{profile}' already started") return self._accounts[profile] bridge_url = config.get("bridge_url", "http://localhost:5556") timeout = config.get("network", {}).get("connect_timeout", 10) bridge = BridgeClient(bridge_url, timeout) await bridge.start() login_flow = LoginFlow(bridge, config) ctx = GatewayContext( bridge_url=bridge_url, config=config, bridge=bridge, login_flow=login_flow, ) self._accounts[profile] = ctx logger.info(f"[ZaloUser] Gateway account '{profile}' started") return ctx async def login_with_qr_start(self, profile: str = "default") -> dict[str, Any]: if profile not in self._accounts: raise ValueError(f"Account '{profile}' not started. Call start_account first.") ctx = self._accounts[profile] if ctx.login_flow is None: ctx.login_flow = LoginFlow(ctx.bridge, ctx.config) result = await ctx.login_flow.start_qr_login() return result async def login_with_qr_wait( self, profile: str = "default", poll_interval: float = 2.0, timeout: float = 120.0, ) -> dict[str, Any]: if profile not in self._accounts: raise ValueError(f"Account '{profile}' not started.") ctx = self._accounts[profile] if ctx.login_flow is None: ctx.login_flow = LoginFlow(ctx.bridge, ctx.config) try: logged_in = await ctx.login_flow.wait_for_login(poll_interval, timeout) return {"status": "success", "logged_in": True} except Exception as e: return {"status": "error", "logged_in": False, "error": str(e)} async def logout_account(self, profile: str = "default") -> dict[str, Any]: if profile not in self._accounts: return {"status": "not_found"} ctx = self._accounts[profile] if ctx.login_flow: await ctx.login_flow.logout() if ctx.bridge: await ctx.bridge.stop() del self._accounts[profile] logger.info(f"[ZaloUser] Gateway account '{profile}' logged out") return {"status": "logged_out"} async def shutdown(self) -> None: for profile in list(self._accounts.keys()): await self.logout_account(profile) def list_accounts(self) -> list[str]: return list(self._accounts.keys()) _zalo_gateway: ZaloGateway | None = None def get_zalo_gateway() -> ZaloGateway: global _zalo_gateway if _zalo_gateway is None: _zalo_gateway = ZaloGateway() return _zalo_gateway