from __future__ import annotations from typing import TYPE_CHECKING, Any from yuxi.utils.logging_config import logger if TYPE_CHECKING: from .client import UrbitClient class InviteManager: def __init__( self, auto_accept_groups: bool = False, group_invite_allowlist: list[str] | None = None, auto_accept_dm: bool = False, dm_allowlist: list[str] | None = None, client: UrbitClient | None = None, ship_name: str = "", ship_code: str = "", ): self.auto_accept_groups = auto_accept_groups self.group_invite_allowlist = [s.lstrip("~").lower() for s in (group_invite_allowlist or [])] self.auto_accept_dm = auto_accept_dm self.dm_allowlist = [s.lstrip("~").lower() for s in (dm_allowlist or [])] self._pending_invites: dict[str, dict[str, Any]] = {} self._client = client self._ship_name = ship_name self._ship_code = ship_code def should_auto_accept_group_invite(self, inviter_ship: str) -> bool: if not self.auto_accept_groups: return False ship = inviter_ship.lstrip("~").lower() if not self.group_invite_allowlist: return False return ship in self.group_invite_allowlist def should_auto_accept_dm(self, inviter_ship: str) -> bool: if not self.auto_accept_dm: return False ship = inviter_ship.lstrip("~").lower() if not self.dm_allowlist: return False return ship in self.dm_allowlist async def handle_foreign_update(self, foreign_update: dict[str, Any]) -> dict[str, Any] | None: group = foreign_update.get("group", "") ship = foreign_update.get("ship", "").lstrip("~") join = foreign_update.get("join", False) if not group or not ship: return None invite_id = f"{group}:{ship}" if join: self._pending_invites[invite_id] = foreign_update if self.should_auto_accept_group_invite(ship): logger.info(f"[Urbit] Auto-accepting group invite from ~{ship} for {group}") await self._accept_invite(group, ship) self._pending_invites.pop(invite_id, None) return {"action": "auto_accepted", "group": group, "ship": ship} logger.info(f"[Urbit] Group invite pending: {group} from ~{ship}") return {"action": "pending", "group": group, "ship": ship, "invite_id": invite_id} if not join and invite_id in self._pending_invites: self._pending_invites.pop(invite_id, None) logger.info(f"[Urbit] Ship ~{ship} left {group}, invite resolved") return {"action": "left", "group": group, "ship": ship} async def _accept_invite(self, group: str, inviter: str) -> None: if not self._client: logger.warning(f"[Urbit] Cannot accept group invite {group} from ~{inviter}: client not available") return logger.info(f"[Urbit] Accepting group invite: {group} from ~{inviter}") try: payload = { "action": "poke", "ship": self._ship_name, "app": "groups", "mark": "groups-action", "json": { "join": { "group": group, "ship": f"~{inviter.lstrip('~')}", } }, } r = await self._client.put( "/~/channel/groups-0", json=payload, timeout=10.0, ) if r.status_code == 401 and self._ship_code: from .auth import refresh_session_if_needed await refresh_session_if_needed(self._client, self._ship_code) payload["ship"] = self._client.ship_name r = await self._client.put( "/~/channel/groups-0", json=payload, timeout=10.0, ) r.raise_for_status() logger.info(f"[Urbit] Successfully accepted group invite: {group}") except Exception as e: logger.error(f"[Urbit] Failed to accept group invite {group} from ~{inviter}: {e}") def get_pending_invites(self) -> list[dict[str, Any]]: return list(self._pending_invites.values())