from __future__ import annotations import asyncio import contextlib import logging from zyrarbot.ui.api.connection_manager import ConnectionManager from zyrarbot.ui.api.status_service import StatusService logger = logging.getLogger(__name__) BROADCAST_INTERVAL_SECONDS = 1.0 class StatusBroadcaster: """Rozgłasza stan bota po WebSocketach - zarówno cyklicznie, jak i na żądanie.""" def __init__(self, status: StatusService, connections: ConnectionManager): self._status = status self._connections = connections self._task: asyncio.Task[None] | None = None async def broadcast(self) -> None: if not self._connections.has_listeners: return try: await self._connections.broadcast(self._snapshot_for_connection) except Exception as error: # noqa: BLE001 - rozgłaszanie nie może ubić pętli logger.error("Błąd rozgłaszania stanu: %s", error) def _snapshot_for_connection(self, connection, guild_id: int | None) -> dict: payload = self._status.snapshot(guild_id) user = connection.app.state.auth.user_for_token( connection.cookies.get("zyrarbot_session") ) allowed = connection.app.state.auth.guild_ids( user, [guild.id for guild in connection.app.state.guilds.guilds()] ) if user else [] payload["guilds"] = [item for item in payload["guilds"] if int(item["guild_id"]) in allowed] return payload def start(self) -> None: if self._task is None or self._task.done(): self._task = asyncio.create_task(self._loop()) async def stop(self) -> None: if self._task is None: return self._task.cancel() with contextlib.suppress(asyncio.CancelledError): await self._task self._task = None async def _loop(self) -> None: while True: try: await asyncio.sleep(BROADCAST_INTERVAL_SECONDS) await self.broadcast() except asyncio.CancelledError: break except Exception as error: # noqa: BLE001 logger.error("Błąd pętli rozgłaszania stanu: %s", error)