from __future__ import annotations import asyncio from typing import TYPE_CHECKING from yuxi.utils.logging_config import logger from .scry import scry_groups_init if TYPE_CHECKING: from .client import UrbitClient from .settings import SettingsStore _DISCOVERY_INTERVAL = 120.0 class ChannelDiscovery: def __init__( self, client: UrbitClient, auto_discover: bool = False, group_channels: list[str] | None = None, settings_store: SettingsStore | None = None, ): self._client = client self._auto_discover = auto_discover self._group_channels = set(group_channels or []) self._discovered_channels: set[str] = set() self._refresh_task: asyncio.Task | None = None self._on_new_channel: asyncio.Event | None = None self._settings_store = settings_store @property def channels(self) -> set[str]: return self._discovered_channels | self._group_channels async def start(self) -> set[str]: if self._group_channels: self._discovered_channels = set(self._group_channels) if self._settings_store: store_group_channels = self._settings_store.get("groupChannels", []) if isinstance(store_group_channels, list) and store_group_channels: self._discovered_channels |= set(store_group_channels) logger.info(f"[Urbit] Merged {len(store_group_channels)} channels from Settings Store") if self._auto_discover: discovered = await self.discover_channels() self._discovered_channels |= discovered self._refresh_task = asyncio.create_task(self._refresh_loop()) channels = self.channels logger.info(f"[Urbit] Channel discovery started: {len(channels)} channels") return channels async def stop(self) -> None: if self._refresh_task and not self._refresh_task.done(): self._refresh_task.cancel() try: await self._refresh_task except asyncio.CancelledError: pass self._refresh_task = None logger.info("[Urbit] Channel discovery stopped") async def discover_channels(self) -> set[str]: discovered: set[str] = set() try: init_data = await scry_groups_init(self._client) if init_data and isinstance(init_data, dict): groups = init_data.get("groups", {}) for group_id, group_data in groups.items(): channels = group_data.get("channels", {}) for channel_nest in channels: if channel_nest.startswith("chat/"): discovered.add(channel_nest) except Exception as e: logger.warning(f"[Urbit] Channel discovery failed: {e}") if discovered: logger.info(f"[Urbit] Discovered {len(discovered)} chat channels") return discovered async def _refresh_loop(self) -> None: while True: try: await asyncio.sleep(_DISCOVERY_INTERVAL) new_channels = await self.discover_channels() added = new_channels - self._discovered_channels if added: self._discovered_channels |= added logger.info(f"[Urbit] Discovered {len(added)} new channels: {added}") except asyncio.CancelledError: break except Exception as e: logger.warning(f"[Urbit] Channel refresh error: {e}") async def persist_group_channels(self) -> None: if not self._settings_store: return try: chat_channels = [ch for ch in self.channels if ch.startswith("chat/")] self._settings_store.update_key("groupChannels", chat_channels) logger.info(f"[Urbit] Persisted {len(chat_channels)} group channels to Settings Store") except Exception as e: logger.warning(f"[Urbit] Failed to persist group channels: {e}") def add_channel(self, channel_nest: str) -> None: if channel_nest not in self._discovered_channels: self._discovered_channels.add(channel_nest) logger.info(f"[Urbit] Manually added channel: {channel_nest}") def get_chat_nests(self, ship_name: str) -> list[str]: nests: list[str] = [] for channel in self.channels: if channel.startswith("chat/"): group_name = channel[len("chat/") :] nests.append(f"~{ship_name}/{group_name}/chat") return nests