127 lines
5.6 KiB
Python
127 lines
5.6 KiB
Python
# /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 else
|
||
logger.warning(f"Received unsupported HTTP method: {request.method}")
|
||
return HttpResponse("Method Not Allowed", status=405)
|