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 WorkplaceThreading: @staticmethod async def create_thread( user_ids: list[str], first_message: str, access_token: str, api_version: str = "v24.0", app_secret: str = "", ) -> dict: url = f"https://graph.facebook.com/{api_version}/me/messages" params = {"access_token": access_token} if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) payload = { "recipient": {"ids": user_ids}, "message": {"text": first_message[:2000]}, } async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.post(url, json=payload, params=params) data = resp.json() if resp.status_code == 200: return {"success": True, "thread_id": data.get("thread_id", ""), "data": data} return {"success": False, "error": data.get("error", {}).get("message", "Unknown error")} except httpx.RequestError as exc: return {"success": False, "error": str(exc)} @staticmethod async def rename_thread( thread_id: str, name: str, access_token: str, api_version: str = "v24.0", app_secret: str = "", ) -> dict: url = f"https://graph.facebook.com/{api_version}/t_{thread_id}/threadname" params = {"access_token": access_token} if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) payload = {"name": name} async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.post(url, json=payload, params=params) return {"success": resp.status_code == 200, "status_code": resp.status_code} except httpx.RequestError as exc: return {"success": False, "error": str(exc)} @staticmethod async def add_participants( thread_id: str, user_ids: list[str], access_token: str, api_version: str = "v24.0", app_secret: str = "", ) -> dict: url = f"https://graph.facebook.com/{api_version}/t_{thread_id}/participants" params = {"access_token": access_token} if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) payload = {"to": user_ids} async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.post(url, json=payload, params=params) return {"success": resp.status_code == 200, "status_code": resp.status_code} except httpx.RequestError as exc: return {"success": False, "error": str(exc)} @staticmethod async def remove_participants( thread_id: str, user_ids: list[str], access_token: str, api_version: str = "v24.0", app_secret: str = "", ) -> dict: url = f"https://graph.facebook.com/{api_version}/t_{thread_id}/participants" params = {"access_token": access_token} if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) payload = {"to": user_ids} async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.delete(url, json=payload, params=params) return {"success": resp.status_code == 200, "status_code": resp.status_code} except httpx.RequestError as exc: return {"success": False, "error": str(exc)} @staticmethod async def get_participants( thread_id: str, access_token: str, api_version: str = "v24.0", app_secret: str = "", ) -> dict: url = f"https://graph.facebook.com/{api_version}/t_{thread_id}" params = {"access_token": access_token, "fields": "participants"} if app_secret: params["appsecret_proof"] = _compute_appsecret_proof(access_token, app_secret) async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.get(url, params=params) if resp.status_code == 200: data = resp.json() return {"success": True, "participants": data.get("participants", {}).get("data", [])} return {"success": False, "error": resp.text[:200]} except httpx.RequestError as exc: return {"success": False, "error": str(exc)} @staticmethod def extract_thread_id(msg: object) -> str | None: raw = getattr(msg, "raw_payload", {}) if hasattr(msg, "raw_payload") else {} thread_id = raw.get("recipient", {}).get("thread_key") if thread_id: return thread_id if hasattr(msg, "group") and msg.group and msg.group.id: return msg.group.id return None