# /home/polisplexity/polisplexity/pxy_meta_pages/views.py from django.http import JsonResponse, HttpResponse from django.views.decorators.csrf import csrf_exempt from django.conf import settings import json, logging, hmac, hashlib from .webhook_handlers import ( verify_webhook_token, # keep using your app’s style if you already have it parse_webhook_payload, # your existing parser for Page feed "changes" handle_comment_event, # existing handle_share_event, # existing handle_message_event, # NEW (you’ll add it below) handle_postback_event, # NEW (you’ll add it below) ) logger = logging.getLogger(__name__) VERIFY_TOKEN = getattr(settings, "VERIFY_TOKEN", "") APP_SECRET = getattr(settings, "APP_SECRET", "") # add this in settings/.env VERIFY_SIG = getattr(settings, "VERIFY_SIGNATURE", True) # optional toggle (default True) def _valid_signature(raw_body: bytes, header: str) -> bool: """ Verify X-Hub-Signature-256 header using APP_SECRET (recommended by Meta). """ if not VERIFY_SIG: return True if not APP_SECRET: logger.warning("APP_SECRET not set; skipping signature verification.") return True if not header or not header.startswith("sha256="): logger.warning("Missing/invalid X-Hub-Signature-256 header.") return False received = header.split("=", 1)[1] expected = hmac.new(APP_SECRET.encode("utf-8"), raw_body, hashlib.sha256).hexdigest() ok = hmac.compare_digest(received, expected) if not ok: logger.warning("Signature mismatch on webhook payload.") return ok @csrf_exempt def facebook_webhook(request): """ One endpoint for: - GET: webhook verification (Messenger) - POST: Page feed changes (comment/share) and Messenger events (messages/postbacks) """ # --- GET VERIFICATION (fixes 405) --- if request.method == "GET": mode = request.GET.get("hub.mode") token = request.GET.get("hub.verify_token") challenge = request.GET.get("hub.challenge", "") # Use your helper if you prefer: verify_webhook_token(token) if mode == "subscribe" and token == VERIFY_TOKEN: return HttpResponse(challenge, status=200) return HttpResponse("Forbidden", status=403) # --- EVENTS --- if request.method == "POST": raw = request.body or b"" # Verify signature (recommended) sig = request.META.get("HTTP_X_HUB_SIGNATURE_256") if not _valid_signature(raw, sig): return JsonResponse({"error": "Invalid signature"}, status=403) try: payload = json.loads(raw.decode("utf-8") or "{}") entries = payload.get("entry", []) for entry in entries: page_id = entry.get("id") keys = list(entry.keys()) print(f"[WEBHOOK] entry0.keys={keys}", flush=True) # ---- Messenger events ---- if "messaging" in entry: for evt in entry.get("messaging", []): sender_id = (evt.get("sender") or {}).get("id") # Log similar to your current style print(f"[WEBHOOK] MESSENGER page={page_id} psid={sender_id}", flush=True) # Message (avoid echo loops) msg = evt.get("message") if msg and not msg.get("is_echo"): # Let your handler decide what to do (persist PSID, enqueue reply, etc.) resp = handle_message_event(page_id, sender_id, msg) if resp is not None: return resp # Postback (Get Started, buttons, persistent menu) postback = evt.get("postback") if postback: resp = handle_postback_event(page_id, sender_id, postback) if resp is not None: return resp # ---- Page feed changes (your existing flow) ---- if "changes" in entry: # Keep your existing parse + handlers untouched data = parse_webhook_payload(payload) sender = (data.get("from") or {}).get("id") item_type = data.get("item") # Optional: quick visibility print(f"[WEBHOOK] page_id={page_id} item_type={item_type!r}", flush=True) if item_type == "share": resp = handle_share_event(page_id, data) if resp is not None: return resp elif item_type == "comment": resp = handle_comment_event(page_id, sender, data) if resp is not None: return resp # Acknowledge within 5s return JsonResponse({"status": "ok"}, status=200) except json.JSONDecodeError: logger.exception("Invalid JSON payload") return JsonResponse({"error": "Invalid JSON payload"}, status=400) except Exception as e: logger.exception("Error processing webhook") return JsonResponse({"error": str(e)}, status=500) # Anything elses logger.warning(f"Received unsupported HTTP method: {request.method}") return HttpResponse("Method Not Allowed", status=405)