Files
zyrarbot/zyrarbot/ui/api/status_broadcaster.py
T

63 lines
2.2 KiB
Python

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)