import os, json from fastapi import FastAPI, Request, HTTPException, Query from fastapi.responses import PlainTextResponse, JSONResponse import httpx import base64 app = FastAPI() VERIFY_TOKEN = os.getenv("VERIFY_TOKEN", "") # set in HF Secrets PHONE_NUMBER_ID = os.getenv("PHONE_NUMBER_ID", "") WHATSAPP_API_TOKEN = os.getenv("WHATSAPP_API_TOKEN", "") @app.get("/webhook") # verification def verify( hub_mode: str = Query(None, alias="hub.mode"), hub_token: str = Query(None, alias="hub.verify_token"), hub_challenge: str = Query(None, alias="hub.challenge") ): if hub_mode == "subscribe" and hub_token == VERIFY_TOKEN: return PlainTextResponse(hub_challenge) raise HTTPException(status_code=403, detail="Invalid token") def send_whatsapp_message(wa_id, reply): url = f"https://graph.facebook.com/v23.0/{PHONE_NUMBER_ID}/messages" headers = { "Authorization": f"Bearer {WHATSAPP_API_TOKEN}", "Content-Type": "application/json" } payload = { "messaging_product": "whatsapp", "to": wa_id, "type": "text", "text": {"body": reply} } httpx.post(url, headers=headers, json=payload) @app.post("/webhook") # incoming WhatsApp updates async def receive_update(req: Request): data = await req.json() try: change = data["entry"][0]["changes"][0]["value"] contacts = change.get("contacts") messages = change.get("messages") if not contacts or not messages: # Missing expected payload—ignore print("Ignoring update, no contacts or messages field") return JSONResponse({"status": "ignored", "reason": "no contacts/messages"}) wa_id = contacts[0]["wa_id"] msg = messages[0] except (KeyError, IndexError, TypeError) as e: print("Malformed webhook payload:", e) return JSONResponse({"status": "ignored", "reason": "malformed payload"}) # IMAGE: if user sent an image, convert to Base64 preview if msg["type"] == "image": media_id = msg["image"]["id"] # 1) get media URL media_resp = httpx.get( f"https://graph.facebook.com/v23.0/{media_id}", headers={"Authorization": f"Bearer {WHATSAPP_API_TOKEN}"} ) media_resp.raise_for_status() media_url = media_resp.json()["url"] # 2) download bytes with auth media_bytes = httpx.get( media_url, headers={"Authorization": f"Bearer {WHATSAPP_API_TOKEN}"} ).content # 3) encode & preview preview = base64.b64encode(media_bytes).decode("utf-8")[:10] # send back preview send_whatsapp_message(wa_id, f"Base64: {preview}") return JSONResponse({"status": "image_preview_sent", "preview": preview}) # TEXT: simple echo if msg["type"] == "text": incoming = msg["text"]["body"] reply = f"You said: {incoming}" send_whatsapp_message(wa_id, reply) return JSONResponse({"status": "replied"}) # OTHER: ignore return JSONResponse({"status": "ignored", "type": msg["type"]}) # health-check endpoint for token @app.post("/send") async def check_token(): token = os.getenv("WHATSAPP_API_TOKEN", "") if token: return {"token_loaded": True} raise HTTPException(status_code=400, detail="Token not found") @app.get("/ping") # health check def ping(): return {"pong": True}