""" WhatsApp sender – Meta Cloud API v18. Config non sensibile (config): { "phone_number_id": "123456789", # ID numero mittente Meta Business "to_phone": "+393331234567" # numero destinatario con prefisso } Config sensibile (config_enc → config_secret): { "access_token": "EAABs..." } # Meta Graph API token API endpoint: POST https://graph.facebook.com/v18.0/{phone_number_id}/messages Nota: richiede un account Meta Business verificato con WhatsApp Business API. """ import httpx META_GRAPH_API_URL = "https://graph.facebook.com/v18.0" DEFAULT_TIMEOUT = 10.0 class WhatsAppError(Exception): """Errore durante l'invio di un messaggio WhatsApp.""" def __init__(self, message: str, http_status: int | None = None, api_code: int | None = None): super().__init__(message) self.http_status = http_status self.api_code = api_code async def send_whatsapp_message( phone_number_id: str, to_phone: str, text: str, access_token: str, timeout: float = DEFAULT_TIMEOUT, ) -> dict: """ Invia un messaggio di testo WhatsApp via Meta Cloud API. Args: phone_number_id: ID del numero WhatsApp Business mittente to_phone: numero destinatario (formato E.164, es. +393331234567) text: testo del messaggio access_token: Meta Graph API Bearer token timeout: timeout HTTP in secondi Returns: dict con message_id dalla risposta API Raises: WhatsAppError: in caso di errore HTTP o risposta API non-ok """ url = f"{META_GRAPH_API_URL}/{phone_number_id}/messages" payload = { "messaging_product": "whatsapp", "to": to_phone.replace(" ", "").replace("-", ""), "type": "text", "text": {"body": text}, } headers = { "Authorization": f"Bearer {access_token}", "Content-Type": "application/json", } async with httpx.AsyncClient(timeout=timeout) as client: try: response = await client.post(url, json=payload, headers=headers) except httpx.TimeoutException as exc: raise WhatsAppError( f"Timeout WhatsApp API dopo {timeout}s" ) from exc except httpx.RequestError as exc: raise WhatsAppError(f"Errore di rete WhatsApp: {exc}") from exc if response.status_code == 401: raise WhatsAppError( "Token Meta non valido o scaduto", http_status=401, ) if response.status_code >= 400: try: err_data = response.json() err_msg = err_data.get("error", {}).get("message", response.text[:200]) err_code = err_data.get("error", {}).get("code") except Exception: err_msg = response.text[:200] err_code = None raise WhatsAppError( f"Meta API errore HTTP {response.status_code}: {err_msg}", http_status=response.status_code, api_code=err_code, ) data = response.json() messages = data.get("messages", []) message_id = messages[0].get("id") if messages else None return {"message_id": message_id, "http_status": response.status_code} async def send_test_whatsapp( phone_number_id: str, to_phone: str, access_token: str, channel_name: str = "PEChub", ) -> dict: """Invia un messaggio WhatsApp di test per verificare la configurazione.""" from datetime import datetime ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") text = ( f"*PEChub – Test canale WhatsApp*\n\n" f"Il canale _{channel_name}_ e' configurato correttamente.\n\n" f"Data/ora: {ts}" ) return await send_whatsapp_message( phone_number_id=phone_number_id, to_phone=to_phone, text=text, access_token=access_token, )