"""Slack API calls: conversations.list, users.list. Use a global client for better performance.""" import httpx import logging import time logger = logging.getLogger(__name__) # Basic client configuration _client = httpx.Client(timeout=30.0) class SlackApiError(Exception): """Raised when Slack returns a non-OK response that warrants a retry.""" pass def _get_api(url: str, params: dict) -> dict: """Internal helper for GET Slack API; returns parsed JSON.""" try: resp = _client.get(url, params=params) # Handle Rate Limiting (429) at the HTTP layer first if resp.status_code == 429: retry_after = int(resp.headers.get("Retry-After", 1)) logger.warning(f"Slack API Rate Limited. Retrying after {retry_after}s.") time.sleep(retry_after) # Simple one-time retry after sleep for now, or let it fail to be picked up by Celery resp = _client.get(url, params=params) resp.raise_for_status() data = resp.json() if not data.get("ok"): error = data.get("error") # If rate limited via JSON body (rare but possible in some endpoints) if error == "ratelimited": raise SlackApiError(f"Slack API Rate Limited: {error}") # Other errors are likely configuration/permission issues, return as is return data return data except httpx.RequestError as e: # Network errors should bubble up to trigger Celery retry logger.error(f"Network error calling Slack API: {e}") raise except httpx.HTTPStatusError as e: # 5xx errors should bubble up to trigger Celery retry if 500 <= e.response.status_code < 600: logger.error(f"Slack Server Error {e.response.status_code}: {e}") raise logger.error(f"Slack Client Error {e.response.status_code}: {e}") return {"ok": False, "error": f"HTTP {e.response.status_code}"} except SlackApiError as e: # Explicit retry-able errors logger.warning(str(e)) raise def fetch_conversations_list(token: str, cursor: str = "") -> dict: """GET conversations.list.""" params = {"token": token, "pretty": 1, "cursor": cursor, "limit": 1000} return _get_api("https://slack.com/api/conversations.list", params) def fetch_conversations_members(token: str, channel_id: str, cursor: str = "") -> dict: """GET conversations.members.""" params = {"token": token, "pretty": 1, "channel": channel_id, "cursor": cursor, "limit": 1000} return _get_api("https://slack.com/api/conversations.members", params) def fetch_users_list(token: str, cursor: str = "") -> dict: """GET users.list.""" params = {"token": token, "pretty": 1, "cursor": cursor, "limit": 1000} return _get_api("https://slack.com/api/users.list", params) def lookup_user_by_email(token: str, email: str) -> dict: """GET users.lookupByEmail.""" params = {"token": token, "email": email} return _get_api("https://slack.com/api/users.lookupByEmail", params)