""" Publisher para Facebook Pages (Meta Graph API). """ from typing import Optional, List, Dict import httpx from app.core.config import settings from app.publishers.base import BasePublisher, PublishResult class FacebookPublisher(BasePublisher): """Publisher para Facebook Pages usando Meta Graph API.""" platform = "facebook" char_limit = 63206 # Límite real de Facebook base_url = "https://graph.facebook.com/v18.0" def __init__(self): self.access_token = settings.META_ACCESS_TOKEN self.page_id = settings.FACEBOOK_PAGE_ID def validate_content(self, content: str) -> bool: """Validar longitud del post.""" return len(content) <= self.char_limit async def publish( self, content: str, image_path: Optional[str] = None ) -> PublishResult: """Publicar en Facebook Page.""" if not self.access_token or not self.page_id: return PublishResult( success=False, error_message="Credenciales de Facebook no configuradas" ) try: async with httpx.AsyncClient() as client: if image_path: # Publicar con imagen url = f"{self.base_url}/{self.page_id}/photos" payload = { "caption": content, "url": image_path, # URL pública de la imagen "access_token": self.access_token } else: # Publicar solo texto url = f"{self.base_url}/{self.page_id}/feed" payload = { "message": content, "access_token": self.access_token } response = await client.post(url, data=payload) response.raise_for_status() post_id = response.json().get("id") return PublishResult( success=True, post_id=post_id, url=f"https://www.facebook.com/{post_id}" ) except httpx.HTTPError as e: return PublishResult( success=False, error_message=str(e) ) async def publish_thread( self, posts: List[str], images: Optional[List[str]] = None ) -> PublishResult: """Publicar como un solo post largo en Facebook.""" # Facebook no tiene threads, concatenamos el contenido combined_content = "\n\n".join(posts) # Usar la primera imagen si existe image = images[0] if images else None return await self.publish(combined_content, image) async def reply( self, post_id: str, content: str ) -> PublishResult: """Responder a un comentario en Facebook.""" if not self.access_token: return PublishResult( success=False, error_message="Token de acceso no configurado" ) try: async with httpx.AsyncClient() as client: url = f"{self.base_url}/{post_id}/comments" payload = { "message": content, "access_token": self.access_token } response = await client.post(url, data=payload) response.raise_for_status() comment_id = response.json().get("id") return PublishResult( success=True, post_id=comment_id ) except httpx.HTTPError as e: return PublishResult( success=False, error_message=str(e) ) async def like(self, post_id: str) -> bool: """Dar like a un post/comentario.""" if not self.access_token: return False try: async with httpx.AsyncClient() as client: url = f"{self.base_url}/{post_id}/likes" payload = {"access_token": self.access_token} response = await client.post(url, data=payload) response.raise_for_status() return response.json().get("success", False) except httpx.HTTPError: return False async def get_mentions(self, since_id: Optional[str] = None) -> List[Dict]: """Obtener menciones de la página.""" if not self.access_token or not self.page_id: return [] try: async with httpx.AsyncClient() as client: url = f"{self.base_url}/{self.page_id}/tagged" params = { "fields": "id,message,from,created_time,permalink_url", "access_token": self.access_token } response = await client.get(url, params=params) response.raise_for_status() data = response.json() return data.get("data", []) except httpx.HTTPError: return [] async def get_comments(self, post_id: str) -> List[Dict]: """Obtener comentarios de un post.""" if not self.access_token: return [] try: async with httpx.AsyncClient() as client: url = f"{self.base_url}/{post_id}/comments" params = { "fields": "id,message,from,created_time,like_count", "access_token": self.access_token } response = await client.get(url, params=params) response.raise_for_status() data = response.json() return data.get("data", []) except httpx.HTTPError: return [] async def get_page_messages(self) -> List[Dict]: """Obtener mensajes de la página (inbox).""" if not self.access_token or not self.page_id: return [] try: async with httpx.AsyncClient() as client: url = f"{self.base_url}/{self.page_id}/conversations" params = { "fields": "id,participants,messages{message,from,created_time}", "access_token": self.access_token } response = await client.get(url, params=params) response.raise_for_status() data = response.json() return data.get("data", []) except httpx.HTTPError: return [] async def send_message(self, recipient_id: str, message: str) -> PublishResult: """Enviar mensaje directo a un usuario.""" if not self.access_token or not self.page_id: return PublishResult( success=False, error_message="Credenciales no configuradas" ) try: async with httpx.AsyncClient() as client: url = f"{self.base_url}/{self.page_id}/messages" payload = { "recipient": {"id": recipient_id}, "message": {"text": message}, "access_token": self.access_token } response = await client.post(url, json=payload) response.raise_for_status() message_id = response.json().get("message_id") return PublishResult( success=True, post_id=message_id ) except httpx.HTTPError as e: return PublishResult( success=False, error_message=str(e) ) async def delete(self, post_id: str) -> bool: """Eliminar un post.""" if not self.access_token: return False try: async with httpx.AsyncClient() as client: url = f"{self.base_url}/{post_id}" params = {"access_token": self.access_token} response = await client.delete(url, params=params) response.raise_for_status() return response.json().get("success", False) except httpx.HTTPError: return False