import hashlib import hmac import logging import httpx logger = logging.getLogger(__name__) def _compute_appsecret_proof(access_token: str, app_secret: str) -> str: return hmac.new( app_secret.encode("utf-8"), access_token.encode("utf-8"), hashlib.sha256, ).hexdigest() class WorkplaceGroups: def __init__(self, api_version: str = "v24.0"): self._api_version = api_version self._base_url = f"https://graph.facebook.com/{api_version}" async def list_groups(self, access_token: str = "", community_id: str = "", app_secret: str = "") -> list[dict]: url = f"{self._base_url}/{community_id}/groups" params = {"access_token": access_token, "limit": 100} if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) async with httpx.AsyncClient(timeout=30.0) as client: try: resp = await client.get(url, params=params) if resp.status_code == 200: data = resp.json() return data.get("data", []) logger.error("Failed to list groups: %d", resp.status_code) return [] except httpx.RequestError as exc: logger.error("Failed to list groups: %s", exc) return [] async def get_group( self, group_id: str, access_token: str, app_secret: str = "", ) -> dict | None: url = f"{self._base_url}/{group_id}" params = { "access_token": access_token, "fields": "id,name,description,privacy,cover,member_count,updated_time", } if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) async with httpx.AsyncClient(timeout=30.0) as client: try: resp = await client.get(url, params=params) if resp.status_code == 200: return resp.json() logger.error("Failed to get group %s: %d", group_id, resp.status_code) return None except httpx.RequestError as exc: logger.error("Failed to get group %s: %s", group_id, exc) return None async def list_group_members( self, group_id: str, access_token: str, limit: int = 100, app_secret: str = "", ) -> list[dict]: url = f"{self._base_url}/{group_id}/members" params = {"access_token": access_token, "limit": limit} if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) async with httpx.AsyncClient(timeout=30.0) as client: try: resp = await client.get(url, params=params) if resp.status_code == 200: data = resp.json() return data.get("data", []) logger.error("Failed to list group members: %d", resp.status_code) return [] except httpx.RequestError as exc: logger.error("Failed to list group members: %s", exc) return [] async def list_group_feed( self, group_id: str, access_token: str, limit: int = 25, app_secret: str = "", ) -> list[dict]: url = f"{self._base_url}/{group_id}/feed" params = { "access_token": access_token, "limit": limit, "fields": "id,message,created_time,from,comments.limit(10),reactions.limit(10)", } if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) async with httpx.AsyncClient(timeout=30.0) as client: try: resp = await client.get(url, params=params) if resp.status_code == 200: data = resp.json() return data.get("data", []) logger.error("Failed to list group feed: %d", resp.status_code) return [] except httpx.RequestError as exc: logger.error("Failed to list group feed: %s", exc) return [] async def create_group( self, community_id: str, name: str, access_token: str, *, description: str = "", privacy: str = "CLOSED", app_secret: str = "", ) -> dict: url = f"{self._base_url}/{community_id}/groups" params = {"access_token": access_token} if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) payload: dict = {"name": name, "privacy": privacy} if description: payload["description"] = description async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.post(url, json=payload, params=params) if resp.status_code == 200: data = resp.json() return {"success": True, "group_id": data.get("id", "")} return {"success": False, "error": resp.text[:200]} except httpx.RequestError as exc: return {"success": False, "error": str(exc)} async def get_post( self, post_id: str, access_token: str, app_secret: str = "", ) -> dict | None: url = f"{self._base_url}/{post_id}" params = { "access_token": access_token, "fields": "id,message,created_time,from,comments.limit(25),reactions.limit(25)", } if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) async with httpx.AsyncClient(timeout=30.0) as client: try: resp = await client.get(url, params=params) if resp.status_code == 200: return resp.json() logger.error("Failed to get post %s: %d", post_id, resp.status_code) return None except httpx.RequestError as exc: logger.error("Failed to get post %s: %s", post_id, exc) return None async def get_post_comments( self, post_id: str, access_token: str, limit: int = 25, app_secret: str = "", ) -> list[dict]: url = f"{self._base_url}/{post_id}/comments" params = { "access_token": access_token, "limit": limit, "fields": "id,message,created_time,from", } if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) async with httpx.AsyncClient(timeout=30.0) as client: try: resp = await client.get(url, params=params) if resp.status_code == 200: data = resp.json() return data.get("data", []) logger.error("Failed to get post comments: %d", resp.status_code) return [] except httpx.RequestError as exc: logger.error("Failed to get post comments: %s", exc) return [] async def get_post_reactions( self, post_id: str, access_token: str, limit: int = 25, app_secret: str = "", ) -> list[dict]: url = f"{self._base_url}/{post_id}/reactions" params = {"access_token": access_token, "limit": limit} if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) async with httpx.AsyncClient(timeout=30.0) as client: try: resp = await client.get(url, params=params) if resp.status_code == 200: data = resp.json() return data.get("data", []) logger.error("Failed to get post reactions: %d", resp.status_code) return [] except httpx.RequestError as exc: logger.error("Failed to get post reactions: %s", exc) return [] async def post_to_group( self, group_id: str, message: str, access_token: str, app_secret: str = "", ) -> dict: url = f"{self._base_url}/{group_id}/feed" params = {"access_token": access_token} if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) payload = {"message": message[:5000]} async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.post(url, json=payload, params=params) if resp.status_code == 200: data = resp.json() return {"success": True, "post_id": data.get("id", "")} return {"success": False, "error": resp.text[:200]} except httpx.RequestError as exc: return {"success": False, "error": str(exc)} async def reply_to_post( self, post_id: str, message: str, access_token: str, app_secret: str = "", ) -> dict: url = f"{self._base_url}/{post_id}/comments" params = {"access_token": access_token} if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) payload = {"message": message[:5000]} async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.post(url, json=payload, params=params) if resp.status_code == 200: data = resp.json() return {"success": True, "comment_id": data.get("id", "")} return {"success": False, "error": resp.text[:200]} except httpx.RequestError as exc: return {"success": False, "error": str(exc)}