Bot & Automation

AI Assistants

/root/hermes-projects/AI Assistants

apps/whatsapp_control_api/app/services/meta_whatsapp.py text
from __future__ import annotations

import logging

import httpx

from ..core.config import get_settings

logger = logging.getLogger(__name__)


class MetaWhatsAppSender:
    @property
    def settings(self):
        return get_settings()

    @property
    def enabled(self) -> bool:
        return bool(self.settings.meta_phone_number_id and self.settings.meta_access_token)

    async def send_text(self, to_number: str, body: str) -> dict | None:
        if not self.enabled:
            logger.warning("Meta WhatsApp sender disabled because credentials are incomplete.")
            return None

        truncated_body = body[: self.settings.max_outbound_text_length]
        url = (
            f"https://graph.facebook.com/"
            f"{self.settings.meta_graph_api_version}/"
            f"{self.settings.meta_phone_number_id}/messages"
        )
        headers = {
            "Authorization": f"Bearer {self.settings.meta_access_token}",
            "Content-Type": "application/json",
        }
        payload = {
            "messaging_product": "whatsapp",
            "recipient_type": "individual",
            "to": to_number,
            "type": "text",
            "text": {
                "preview_url": False,
                "body": truncated_body,
            },
        }
        async with httpx.AsyncClient(timeout=15.0) as client:
            try:
                response = await client.post(url, headers=headers, json=payload)
                response.raise_for_status()
            except httpx.HTTPStatusError as exc:
                status_code = exc.response.status_code
                response_text = exc.response.text[:1000]
                logger.error(
                    "Meta WhatsApp send failed: status=%s phone_number_id=%s response=%s",
                    status_code,
                    self.settings.meta_phone_number_id,
                    response_text,
                )
                return None
            except httpx.HTTPError as exc:
                logger.error(
                    "Meta WhatsApp send failed: phone_number_id=%s error=%s",
                    self.settings.meta_phone_number_id,
                    exc,
                )
                return None
            return response.json()

    async def mark_read(self, message_id: str, *, typing: bool = False) -> dict | None:
        if not self.enabled:
            logger.warning("Meta WhatsApp sender disabled because credentials are incomplete.")
            return None

        url = (
            f"https://graph.facebook.com/"
            f"{self.settings.meta_graph_api_version}/"
            f"{self.settings.meta_phone_number_id}/messages"
        )
        headers = {
            "Authorization": f"Bearer {self.settings.meta_access_token}",
            "Content-Type": "application/json",
        }
        payload: dict = {
            "messaging_product": "whatsapp",
            "status": "read",
            "message_id": message_id,
        }
        if typing:
            payload["typing_indicator"] = {"type": "text"}

        async with httpx.AsyncClient(timeout=15.0) as client:
            try:
                response = await client.post(url, headers=headers, json=payload)
                response.raise_for_status()
            except httpx.HTTPStatusError as exc:
                logger.error(
                    "Meta WhatsApp mark-read failed: status=%s phone_number_id=%s response=%s",
                    exc.response.status_code,
                    self.settings.meta_phone_number_id,
                    exc.response.text[:1000],
                )
                return None
            except httpx.HTTPError as exc:
                logger.error(
                    "Meta WhatsApp mark-read failed: phone_number_id=%s error=%s",
                    self.settings.meta_phone_number_id,
                    exc,
                )
                return None
            return response.json()