|
|
|
from fastapi import APIRouter, Request |
|
import os, requests, asyncio |
|
from dotenv import load_dotenv |
|
from fastapi.responses import PlainTextResponse, JSONResponse |
|
|
|
|
|
from routes.question import ask_question, QuestionInput |
|
|
|
load_dotenv() |
|
|
|
wa_router = APIRouter() |
|
|
|
VERIFY_TOKEN = os.getenv("VERIFY_TOKEN") |
|
ACCESS_TOKEN = os.getenv("WHATSAPP_ACCESS_TOKEN") |
|
PHONE_NUMBER_ID = os.getenv("WHATSAPP_PHONE_NUMBER_ID") |
|
|
|
def send_whatsapp_text(to: str, text: str) -> dict: |
|
""" |
|
Send a plain text message (max 4096 chars per WhatsApp limits). |
|
Returns {'status': 'sent' | 'failed', ... } for easy logging. |
|
""" |
|
url = f"https://graph.facebook.com/v17.0/{PHONE_NUMBER_ID}/messages" |
|
headers = { |
|
"Authorization": f"Bearer {ACCESS_TOKEN}", |
|
"Content-Type": "application/json", |
|
} |
|
payload = { |
|
"messaging_product": "whatsapp", |
|
"to": to, |
|
"type": "text", |
|
"text": {"body": text[:4096]}, |
|
} |
|
|
|
try: |
|
r = requests.post(url, headers=headers, json=payload) |
|
r.raise_for_status() |
|
return {"status": "sent", "response": r.json()} |
|
except Exception as e: |
|
return {"status": "failed", "error": str(e)} |
|
|
|
async def nuse_interact(to: str, name: str, prompt: str) -> dict: |
|
""" |
|
1. Call ask_question() directly (no HTTP round-trip); |
|
2. Extract the answer text (+sources or headlines); |
|
3. Push it back to the user via WhatsApp API using link previews. |
|
""" |
|
qa_result = await ask_question(QuestionInput(question=prompt)) |
|
answer_text = qa_result["answer"] |
|
|
|
|
|
if "headlines" in qa_result: |
|
headlines = qa_result["headlines"] |
|
if not headlines: |
|
return send_whatsapp_text(to, "Sorry, no headlines found today.") |
|
|
|
|
|
preview_msgs = [] |
|
for h in headlines: |
|
preview_msgs.append( |
|
f"ποΈ *{h['title']}* ({h.get('category', '').title()})\n{h['summary']}\nπ {h['url']}" |
|
) |
|
|
|
message = f"{answer_text}\n\n" + "\n\n".join(preview_msgs[:10]) |
|
return send_whatsapp_text(to, message) |
|
|
|
|
|
if qa_result.get("sources"): |
|
bullet_list = "\n".join(f"β’ {s['title']} β {s['url']}" for s in qa_result["sources"]) |
|
answer_text = f"{answer_text}\n\nSources:\n{bullet_list}" |
|
|
|
return send_whatsapp_text(to, answer_text) |
|
|
|
@wa_router.get("/webhook") |
|
async def verify_webhook(request: Request): |
|
params = request.query_params |
|
if params.get("hub.mode") == "subscribe" and params.get("hub.verify_token") == VERIFY_TOKEN: |
|
return PlainTextResponse(content=params.get("hub.challenge")) |
|
return JSONResponse(status_code=403, content={"error": "Verification failed"}) |
|
|
|
@wa_router.post("/webhook") |
|
async def receive_whatsapp_event(request: Request): |
|
body = await request.json() |
|
print("[WEBHOOK] Incoming:", body) |
|
|
|
try: |
|
entry = body["entry"][0] |
|
changes = entry["changes"][0] |
|
value = changes["value"] |
|
messages = value.get("messages") |
|
contacts = value.get("contacts") |
|
|
|
if messages and contacts: |
|
msg = messages[0] |
|
contact = contacts[0] |
|
|
|
from_number = msg["from"] |
|
sender_name = contact["profile"]["name"] |
|
incoming_txt = msg["text"]["body"] |
|
|
|
print(f"[INFO] {sender_name} ({from_number}): {incoming_txt}") |
|
|
|
|
|
result = await nuse_interact(from_number, sender_name, incoming_txt) |
|
print("[INFO] Reply status:", result) |
|
|
|
except Exception as e: |
|
print("[ERROR] Webhook processing failed:", str(e)) |
|
|
|
return JSONResponse(content={"status": "received"}) |
|
|