114 lines
4.0 KiB
Python
114 lines
4.0 KiB
Python
# 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")
|