194 lines
6.5 KiB
Python
194 lines
6.5 KiB
Python
import os
|
|
import json
|
|
import requests
|
|
import logging
|
|
from django.http import JsonResponse, HttpResponse
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from django.views.decorators.http import require_http_methods
|
|
from django.shortcuts import get_object_or_404
|
|
from pxy_openai.assistants import OpenAIAssistant
|
|
from .models import WhatsAppBot
|
|
from .models import WhatsAppBot, Conversation, Message
|
|
from django.utils import timezone
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Utility Functions
|
|
def send_whatsapp_message(phone_number_id, sender_number, bot_response, graph_api_token):
|
|
"""
|
|
Sends a message back to the user via WhatsApp.
|
|
"""
|
|
try:
|
|
response = requests.post(
|
|
f"https://graph.facebook.com/v18.0/{phone_number_id}/messages",
|
|
headers={"Authorization": f"Bearer {graph_api_token}"},
|
|
json={
|
|
"messaging_product": "whatsapp",
|
|
"to": sender_number,
|
|
"text": {"body": bot_response},
|
|
},
|
|
)
|
|
response.raise_for_status()
|
|
except Exception as e:
|
|
logger.error(f"Error sending message via WhatsApp: {e}")
|
|
|
|
|
|
def verify_webhook_token(mode, token, challenge, verify_token):
|
|
"""
|
|
Verifies the webhook token and mode.
|
|
"""
|
|
if mode == "subscribe" and token == verify_token:
|
|
return HttpResponse(challenge, status=200)
|
|
return HttpResponse("Forbidden", status=403)
|
|
|
|
|
|
def parse_webhook_payload(payload):
|
|
"""
|
|
Parses the webhook payload and extracts relevant data.
|
|
"""
|
|
entry = payload.get("entry", [{}])[0]
|
|
changes = entry.get("changes", [{}])[0]
|
|
value = changes.get("value", {})
|
|
message = value.get("messages", [{}])[0]
|
|
return value, message
|
|
|
|
|
|
# Webhook Endpoint
|
|
@csrf_exempt
|
|
def webhook(request):
|
|
"""
|
|
Handles incoming webhook requests from WhatsApp.
|
|
"""
|
|
if request.method == "GET":
|
|
# Webhook verification
|
|
mode = request.GET.get("hub.mode")
|
|
token = request.GET.get("hub.verify_token")
|
|
challenge = request.GET.get("hub.challenge")
|
|
|
|
# Use the first active bot for verification
|
|
bot = WhatsAppBot.objects.filter(is_active=True).first()
|
|
if bot:
|
|
return verify_webhook_token(mode, token, challenge, bot.webhook_verify_token)
|
|
return HttpResponse("No active bots configured", status=500)
|
|
|
|
elif request.method == "POST":
|
|
try:
|
|
# Parse the incoming payload
|
|
payload = json.loads(request.body)
|
|
value, message = parse_webhook_payload(payload)
|
|
|
|
if message.get("type") == "text":
|
|
user_message = message["text"]["body"]
|
|
phone_number_id = value.get("metadata", {}).get("phone_number_id")
|
|
sender_number = message["from"]
|
|
|
|
logger.info(f"Received phone_number_id from webhook payload: {phone_number_id}")
|
|
|
|
# 1) Fetch the active bot
|
|
bot = get_object_or_404(
|
|
WhatsAppBot,
|
|
phone_number_id=phone_number_id,
|
|
is_active=True
|
|
)
|
|
|
|
# 2) Get or create Conversation
|
|
conv, _ = Conversation.objects.get_or_create(
|
|
bot=bot,
|
|
user_number=sender_number,
|
|
defaults={'started_at': timezone.now()}
|
|
)
|
|
|
|
# 3) Save inbound message
|
|
Message.objects.create(
|
|
conversation=conv,
|
|
direction="in",
|
|
content=user_message
|
|
)
|
|
|
|
# 4) Generate assistant response and measure time
|
|
assistant = OpenAIAssistant(name=bot.assistant.name)
|
|
start = timezone.now()
|
|
try:
|
|
bot_response = assistant.handle_message(user_message)
|
|
except Exception as e:
|
|
bot_response = f"Assistant error: {e}"
|
|
logger.error(bot_response)
|
|
end = timezone.now()
|
|
|
|
# 5) Send the response back to the user
|
|
send_whatsapp_message(
|
|
phone_number_id,
|
|
sender_number,
|
|
bot_response,
|
|
bot.graph_api_token
|
|
)
|
|
|
|
# 6) Save outbound message with response time
|
|
resp_ms = int((end - start).total_seconds() * 1000)
|
|
Message.objects.create(
|
|
conversation=conv,
|
|
direction="out",
|
|
content=bot_response,
|
|
response_time_ms=resp_ms
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing webhook: {e}")
|
|
|
|
return JsonResponse({}, status=200)
|
|
|
|
return HttpResponse("Method Not Allowed", status=405)
|
|
|
|
|
|
|
|
# Webhook Verification Endpoint
|
|
@require_http_methods(["GET"])
|
|
def webhook_verification(request):
|
|
"""
|
|
Verifies the webhook token from WhatsApp.
|
|
"""
|
|
mode = request.GET.get("hub.mode")
|
|
token = request.GET.get("hub.verify_token")
|
|
challenge = request.GET.get("hub.challenge")
|
|
|
|
# Use the first active bot for verification
|
|
bot = WhatsAppBot.objects.filter(is_active=True).first()
|
|
if bot:
|
|
return verify_webhook_token(mode, token, challenge, bot.webhook_verify_token)
|
|
return HttpResponse("No active bots configured", status=500)
|
|
|
|
|
|
# Root Endpoint
|
|
def root(request):
|
|
"""
|
|
A root endpoint for basic connectivity testing.
|
|
"""
|
|
return HttpResponse("<pre>Nothing to see here.\nCheckout README.md to start.</pre>", content_type="text/html")
|
|
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.http import JsonResponse
|
|
from django.db.models import Avg
|
|
from .models import Conversation, Message
|
|
|
|
@login_required
|
|
def whatsapp_stats(request):
|
|
# Total de conversaciones registradas
|
|
total_conversations = Conversation.objects.count()
|
|
|
|
# Total de mensajes entrantes y salientes de todo el tiempo
|
|
messages_in = Message.objects.filter(direction="in").count()
|
|
messages_out = Message.objects.filter(direction="out").count()
|
|
|
|
# Tiempo de respuesta promedio (solo salientes con tiempo registrado)
|
|
avg_rt_qs = Message.objects.filter(direction="out", response_time_ms__isnull=False)
|
|
avg_rt = avg_rt_qs.aggregate(Avg("response_time_ms"))["response_time_ms__avg"] or 0
|
|
|
|
return JsonResponse({
|
|
"total_conversations": total_conversations,
|
|
"messages_in": messages_in,
|
|
"messages_out": messages_out,
|
|
"avg_response_time": int(avg_rt),
|
|
})
|
|
|