|
|
|
from fastapi import APIRouter, Request, HTTPException, status |
|
from fastapi.responses import JSONResponse |
|
import logging |
|
import json |
|
|
|
|
|
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() |
|
|
|
|
|
@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: |
|
|
|
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 |
|
|
|
|
|
entries = incoming_message.get('entry', []) |
|
for entry in entries: |
|
changes = entry.get('changes', []) |
|
for change in changes: |
|
|
|
if change.get('field') == 'messages': |
|
value = change.get('value', {}) |
|
messages_list = value.get('messages', []) |
|
|
|
for msg in messages_list: |
|
|
|
if msg.get('type') == 'text': |
|
from_number = msg.get('from') |
|
message_text = msg.get('text', {}).get('body') |
|
break |
|
if from_number and message_text: |
|
break |
|
if from_number and message_text: |
|
break |
|
|
|
|
|
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}") |
|
|
|
|
|
if message_text.lower().strip() == "digest": |
|
logging.info(f"User {from_number} requested daily digest.") |
|
|
|
|
|
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_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"}) |
|
|
|
|
|
|
|
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)}) |
|
|
|
|
|
@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}") |
|
|
|
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") |