# 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")