Files
PecHub/backend/app/notifications/whatsapp.py
T
2026-03-27 15:13:14 +01:00

126 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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,
)