fastAPIv2 / routes /api /whatsapp_webhook.py
ragV98's picture
fix 113
f1e54f5
# routes/api/whatsapp_webhook.py
from fastapi import APIRouter, Request, HTTPException, status
from fastapi.responses import JSONResponse
import logging
import json
# Import your function to send messages back
from components.gateways.headlines_to_wa import fetch_cached_headlines, send_to_whatsapp
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
router = APIRouter()
# WhatsApp/Gupshup webhook endpoint
@router.post("/message-received")
async def whatsapp_webhook_receiver(request: Request):
"""
Receives incoming messages from Gupshup WhatsApp webhook.
Sends a daily news digest if the user sends a specific command.
"""
try:
# <<< FIX 1: Assume JSON body directly for incoming_message >>>
body_bytes = await request.body()
body_str = body_bytes.decode("utf-8")
logging.info(f"Raw webhook body received: {body_str}")
try:
incoming_message = json.loads(body_str)
except json.JSONDecodeError:
logging.error("❌ Failed to decode webhook body as JSON")
return JSONResponse(status_code=400, content={"error": "Invalid JSON format"})
from_number = None
message_text = None
# <<< FIX 2: Robustly extract data from the nested structure >>>
entries = incoming_message.get('entry', [])
for entry in entries:
changes = entry.get('changes', [])
for change in changes:
# We are interested in changes related to 'messages'
if change.get('field') == 'messages':
value = change.get('value', {})
messages_list = value.get('messages', [])
for msg in messages_list:
# Check if it's a text message and extract sender/body
if msg.get('type') == 'text':
from_number = msg.get('from')
message_text = msg.get('text', {}).get('body')
break # Found text message, exit inner loop
if from_number and message_text:
break # Found message, exit middle loop
if from_number and message_text:
break # Found message, exit outer loop
# Check if sender and message text were successfully extracted
if not from_number or not message_text:
logging.warning("Received webhook without valid sender or message text in expected structure.")
return JSONResponse(status_code=200, content={"status": "ignored", "message": "Missing sender or text"})
logging.info(f"Message from {from_number}: {message_text}")
# Check for specific commands to send the digest
if message_text.lower().strip() == "digest":
logging.info(f"User {from_number} requested daily digest.")
# Fetch the digest headlines
full_message_text = fetch_cached_headlines()
if full_message_text.startswith("❌") or full_message_text.startswith("⚠️"):
logging.error(f"Failed to fetch digest for {from_number}: {full_message_text}")
# Send an error message back to the user
send_to_whatsapp(f"Sorry, I couldn't fetch the news digest today. {full_message_text}", destination_number=from_number)
return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to fetch digest"})
# Send the digest back to the user who requested it
# <<< FIX 3: Pass from_number as destination_number to send_to_whatsapp >>>
result = send_to_whatsapp(full_message_text, destination_number=from_number)
if result.get("status") == "success":
logging.info(f"βœ… Successfully sent digest to {from_number}.")
return JSONResponse(status_code=200, content={"status": "success", "message": "Digest sent"})
else:
logging.error(f"❌ Failed to send digest to {from_number}: {result.get('error')}")
send_to_whatsapp(f"Sorry, I couldn't send the news digest to you. Error: {result.get('error', 'unknown')}", destination_number=from_number)
return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to send digest"})
else:
logging.info(f"Received unhandled message from {from_number}: '{message_text}'")
return JSONResponse(status_code=200, content={"status": "ignored", "message": "No action taken for this command"})
except Exception as e:
logging.error(f"Error processing webhook: {e}", exc_info=True)
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
# Gupshup webhook verification endpoint (GET request with 'hub.mode' and 'hub.challenge')
@router.get("/message-received")
async def whatsapp_webhook_verify(request: Request):
"""
Endpoint for Gupshup webhook verification.
"""
mode = request.query_params.get("hub.mode")
challenge = request.query_params.get("hub.challenge")
if mode == "subscribe" and challenge:
logging.info(f"Webhook verification successful. Challenge: {challenge}")
# Challenge needs to be returned as an integer, not string
return JSONResponse(status_code=200, content=int(challenge))
else:
logging.warning(f"Webhook verification failed. Mode: {mode}, Challenge: {challenge}")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Verification failed")