# pxy_messenger/views.py import hashlib, hmac, json, logging, os from django.conf import settings from django.http import HttpResponse, HttpResponseForbidden from django.views.decorators.csrf import csrf_exempt # Optional: if you created a model to dedupe events (you have one in admin!) from .models import MessengerEvent # keep if present import requests log = logging.getLogger(__name__) GRAPH_API = os.getenv("META_GRAPH_API", "https://graph.facebook.com/v18.0") def _verify_get(request): mode = request.GET.get("hub.mode") token = request.GET.get("hub.verify_token") chal = request.GET.get("hub.challenge", "") if mode == "subscribe" and token == getattr(settings, "MESSENGER_VERIFY_TOKEN", ""): return HttpResponse(chal) return HttpResponseForbidden("Bad verify token") def _verify_signature(request): app_secret = getattr(settings, "FACEBOOK_APP_SECRET", "") if not app_secret: return True # dev mode (no signature check) provided = request.headers.get("X-Hub-Signature-256", "") body = request.body # keep a copy for troubleshooting try: with open("/tmp/m_sig_body.bin", "wb") as f: f.write(body) except Exception: pass calc_hex = hmac.new(app_secret.encode("utf-8"), body, hashlib.sha256).hexdigest() expected = "sha256=" + calc_hex log.info("[messenger] sig check: provided=%r expected=%r body_len=%d", provided, expected, len(body)) return hmac.compare_digest(provided, expected) def _send_text(psid: str, text: str) -> bool: token = getattr(settings, "PAGE_ACCESS_TOKEN", "") if not token: log.warning("[messenger] no PAGE_ACCESS_TOKEN; would send to %s: %s", psid, text) return False url = f"{GRAPH_API}/me/messages" params = {"access_token": token} payload = { "messaging_type": "RESPONSE", "recipient": {"id": psid}, "message": {"text": text}, } try: r = requests.post(url, params=params, json=payload, timeout=10) if r.status_code != 200: log.error("[messenger] Send API error %s: %s", r.status_code, r.text) return False log.info("[messenger] sent to %s ok: %s", psid, r.text) return True except Exception as e: log.exception("[messenger] Send API exception: %s", e) return False @csrf_exempt def webhook(request): if request.method == "GET": return _verify_get(request) if request.method != "POST": return HttpResponse(status=405) if not _verify_signature(request): return HttpResponseForbidden("Invalid signature") try: payload = json.loads(request.body.decode("utf-8") or "{}") except Exception: payload = {} log.info("[messenger] payload: %s", json.dumps(payload)[:2000]) # Iterate entries and messaging events for entry in payload.get("entry", []): for m in entry.get("messaging", []): sender = (m.get("sender") or {}).get("id") recipient = (m.get("recipient") or {}).get("id") # dedupe by message mid when present mid = (m.get("message") or {}).get("mid") \ or (m.get("delivery") or {}).get("mids", [None])[0] if mid: # store if not seen (your admin shows MessengerEvent already) obj, created = MessengerEvent.objects.get_or_create( mid=mid, defaults={ "sender_id": sender or "", "page_id": recipient or "", }, ) if not created: log.info("[messenger] duplicate mid %s — skipping", mid) continue # Basic text echo if "message" in m and "text" in m["message"] and sender: txt = m["message"]["text"].strip() reply = f"You said: {txt}" _send_text(sender, reply) # must 200 quickly return HttpResponse("OK")