Messenger AI bot responder
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Ekaropolus 2025-09-07 02:58:02 -06:00
parent d1149ff471
commit 170ec64cec
2 changed files with 171 additions and 22 deletions

View File

@ -1,44 +1,126 @@
# /home/polisplexity/polisplexity/pxy_meta_pages/views.py
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
import json
import logging
from django.conf import settings
from .webhook_handlers import verify_webhook_token, parse_webhook_payload, handle_comment_event, handle_share_event
import json, logging, hmac, hashlib
from .webhook_handlers import (
verify_webhook_token, # keep using your apps 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 (youll add it below)
handle_postback_event, # NEW (youll add it below)
)
# Configure logging
logger = logging.getLogger(__name__)
VERIFY_TOKEN = settings.VERIFY_TOKEN
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):
"""
Handles incoming webhook requests from Facebook.
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:
logger.info("Received POST request.")
payload = json.loads(request.body)
data = parse_webhook_payload(payload)
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)
sender_id = data.get("from", {}).get("id")
page_id = payload.get("entry", [{}])[0].get("id")
item_type = data.get("item")
# ---- 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)
if item_type == "share":
return handle_share_event(page_id, data)
elif item_type == "comment":
return handle_comment_event(page_id, sender_id, data)
# 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
except json.JSONDecodeError as e:
logger.error(f"JSON decoding error: {e}")
# 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.error(f"Error processing webhook: {e}")
logger.exception("Error processing webhook")
return JsonResponse({"error": str(e)}, status=500)
logger.info("POST request processed successfully.")
return JsonResponse({"status": "success"}, status=200)
# Anything else
logger.warning(f"Received unsupported HTTP method: {request.method}")
return HttpResponse("Method Not Allowed", status=405)

View File

@ -7,6 +7,10 @@ from django.conf import settings
from .services import FacebookService
from .models import FacebookPageAssistant, EventType, FacebookEvent
import requests
from pxy_openai.assistants import OpenAIAssistant as OpenAIService
# Configure logging
logger = logging.getLogger(__name__)
@ -113,3 +117,66 @@ def handle_share_event(page_id, data):
logger.error(f"Error posting comment on share via FacebookService: {e}")
return JsonResponse({"status": "share_logged"}, status=200)
def handle_message_event(page_id: str, sender_psid: str, message: dict):
"""
Handles incoming Messenger messages:
1) Log event to DB (like comments/shares)
2) Generate AI reply with your configured OpenAI assistant for this page
3) Send the reply via the Send API (using the page's access token)
"""
try:
# 0) ignore echoes to prevent loops
if not message or message.get("is_echo"):
return JsonResponse({"status": "ignored"}, status=200)
text = (message.get("text") or "").strip()
mid = message.get("mid") or ""
# 1) Persist event to DB (EventType 'message'; create if missing)
try:
page = FacebookPageAssistant.objects.get(page_id=page_id)
et, _ = EventType.objects.get_or_create(code="message", defaults={"label": "Message"})
FacebookEvent.objects.create(
page=page,
event_type=et,
sender_id=sender_psid,
object_id=mid,
message=text
)
except Exception as e:
logger.error(f"Error logging Messenger event: {e}")
# 2) Build prompt and get AI reply from the page's configured assistant
try:
page_assistant = FacebookPageAssistant.objects.get(page_id=page_id).assistant
prompt = text if text else "Say hello and ask how you can help."
openai_service = OpenAIService(name=page_assistant.name)
bot_reply = openai_service.handle_message(prompt)
except Exception as e:
logger.exception(f"AI reply failed; falling back. Reason: {e}")
bot_reply = "Gracias por tu mensaje 🙌. ¿En qué puedo ayudarte?"
# 3) Send the reply via Send API
try:
fb_service = FacebookService(PAGE_ACCESS_TOKEN) # reuse your token flow
page_token = fb_service._get_page_access_token(page_id) or PAGE_ACCESS_TOKEN
url = "https://graph.facebook.com/v22.0/me/messages"
payload = {
"recipient": {"id": sender_psid},
"messaging_type": "RESPONSE",
"message": {"text": bot_reply},
}
resp = requests.post(url, params={"access_token": page_token}, json=payload, timeout=5)
resp.raise_for_status()
logger.info(f"Sent Messenger reply to psid={sender_psid}")
return JsonResponse({"status": "message_replied"}, status=200)
except requests.RequestException as e:
logger.exception(f"Send API failed: {e}")
return JsonResponse({"status": "send_failed"}, status=200)
except Exception as e:
logger.exception(f"handle_message_event crashed: {e}")
return JsonResponse({"status": "error", "detail": str(e)}, status=200)