from __future__ import annotations from dataclasses import dataclass, field from slack_sdk.errors import SlackApiError from slack_sdk.web.async_client import AsyncWebClient from yuxi.utils.logging_config import logger @dataclass class ScopesInfo: bot_scopes: list[str] = field(default_factory=list) user_scopes: list[str] = field(default_factory=list) error: str = "" @property def has_chat_write(self) -> bool: return "chat:write" in self.bot_scopes @property def has_chat_write_customize(self) -> bool: return "chat:write.customize" in self.bot_scopes @property def has_files_write(self) -> bool: return "files:write" in self.bot_scopes @property def has_channels_manage(self) -> bool: return "channels:manage" in self.bot_scopes def to_dict(self) -> dict: return { "bot_scopes": self.bot_scopes, "user_scopes": self.user_scopes, "has_chat_write": self.has_chat_write, "has_chat_write_customize": self.has_chat_write_customize, "has_files_write": self.has_files_write, "has_channels_manage": self.has_channels_manage, "error": self.error, } async def fetch_scopes(client: AsyncWebClient, *, include_user: bool = False) -> ScopesInfo: result = ScopesInfo() try: auth = await client.auth_test() if auth.get("ok"): resp = await client.api_call(api_method="auth.scopes", http_verb="GET") scopes = resp.get("headers", {}).get("x-oauth-scopes", "") if isinstance(scopes, str): result.bot_scopes = [s.strip() for s in scopes.split(",") if s.strip()] elif isinstance(scopes, list): result.bot_scopes = scopes else: result.error = f"auth.test failed: {auth.get('error')}" except SlackApiError as e: result.error = str(e) logger.warning(f"Failed to fetch Slack scopes: {e}") except Exception as e: result.error = str(e) logger.warning(f"Failed to fetch Slack scopes: {e}") return result async def validate_required_scopes( client: AsyncWebClient, required: list[str], ) -> tuple[bool, list[str]]: info = await fetch_scopes(client) missing = [s for s in required if s not in info.bot_scopes] return len(missing) == 0, missing