diff --git a/pxy_bots/views.py b/pxy_bots/views.py index 39506df..17186da 100644 --- a/pxy_bots/views.py +++ b/pxy_bots/views.py @@ -1,119 +1,69 @@ import json -import logging - +from telegram import Update, Bot from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt -from telegram import Update, Bot -from django.utils import timezone - -from .models import TelegramBot, TelegramConversation, TelegramMessage +from asgiref.sync import sync_to_async +from .models import TelegramBot from pxy_langchain.services import LangchainAIService from .handlers import dream_city_command, start, help_command, handle_location +import logging logger = logging.getLogger(__name__) @csrf_exempt -def telegram_webhook(request, bot_name): +async def telegram_webhook(request, bot_name): """ - Webhook view for Telegram that logs inbound/outbound messages, - handles commands and AI fallback, and always returns 200 OK. + Webhook view that handles Telegram updates asynchronously and only uses LangChain. """ try: - # 1) Sólo POST - if request.method != "POST": - logger.warning("Received non-POST request to Telegram webhook.") + logger.info(f"Webhook called for bot: {bot_name}") + + # Step 1: Fetch the bot instance asynchronously + try: + bot_instance = await sync_to_async(TelegramBot.objects.get)(name=bot_name, is_active=True) + logger.info(f"Loaded bot configuration: {bot_instance}") + except TelegramBot.DoesNotExist: + logger.error(f"Bot '{bot_name}' not found or inactive.") + return JsonResponse({"error": f"Bot '{bot_name}' not found."}, status=400) + + # Step 2: Ensure the bot has a LangChain assistant + if not bot_instance.assistant: + logger.error(f"No assistant configured for bot '{bot_name}'.") + return JsonResponse({"error": "Assistant not configured."}, status=400) + + # Step 3: Process POST request from Telegram + if request.method == "POST": + try: + request_body = json.loads(request.body.decode("utf-8")) + update = Update.de_json(request_body, Bot(token=bot_instance.token)) + logger.info(f"Update received: {update}") + except json.JSONDecodeError as e: + logger.error(f"Failed to decode JSON: {e}") + return JsonResponse({"error": "Invalid JSON payload"}, status=400) + + # Step 4: Route commands to the appropriate handlers + if update.message: + if update.message.text == "/start": + await start(update) + elif update.message.text == "/help": + await help_command(update) + elif update.message.text == "/dream_city": + await dream_city_command(update) + elif update.message.location: + await handle_location(update) + else: + # Step 5: Process AI-generated response using LangChain + assistant_instance = await sync_to_async(LangchainAIService)(bot_instance.assistant) + bot_response = await sync_to_async(assistant_instance.generate_response)(update.message.text) + + # Step 6: Send the response back to Telegram + await update.message.reply_text(bot_response) + return JsonResponse({"status": "ok"}) - # 2) Cargar configuración del bot - try: - bot_instance = TelegramBot.objects.get(name=bot_name, is_active=True) - except TelegramBot.DoesNotExist: - msg = f"Bot '{bot_name}' not found or inactive." - logger.error(msg) - return JsonResponse({"status": "ok", "error": msg}) - - # 3) Parsear el update - try: - payload = json.loads(request.body.decode("utf-8")) - update = Update.de_json(payload, Bot(token=bot_instance.token)) - except Exception as e: - logger.error(f"Failed to parse update JSON: {e}") - return JsonResponse({"status": "ok", "error": "Invalid JSON payload"}) - - # 4) Loggear mensaje entrante - if update.message and update.effective_user: - user_id = str(update.effective_user.id) - conv, _ = TelegramConversation.objects.get_or_create( - bot=bot_instance, - user_id=user_id, - defaults={"started_at": timezone.now()} - ) - incoming_text = update.message.text or "" - try: - TelegramMessage.objects.create( - conversation=conv, - direction=TelegramMessage.IN, - content=incoming_text - ) - except Exception as log_in_err: - logger.error(f"Error logging inbound message: {log_in_err}") - else: - logger.warning("Update has no message or user info.") - - # 5) Manejar comandos y fallback AI - reply = None - response_time = 0 - if update.message: - text = update.message.text or "" - try: - # Comandos predefinidos - if text == "/start": - start(update) - elif text == "/help": - help_command(update) - elif text == "/dream_city": - dream_city_command(update) - elif update.message.location: - handle_location(update) - else: - # Fallback AI - try: - assistant = LangchainAIService(bot_instance.assistant) - start_ts = timezone.now() - reply = assistant.generate_response(text) - response_time = int((timezone.now() - start_ts).total_seconds() * 1000) - except Exception as ai_err: - logger.error(f"AI service error: {ai_err}") - reply = "Lo siento, el servicio de IA no está disponible." - response_time = 0 - - # Enviar respuesta - chat_id = update.message.chat.id - try: - Bot(token=bot_instance.token).send_message( - chat_id=chat_id, - text=reply - ) - except Exception as send_err: - logger.error(f"Error sending message to Telegram (chat {chat_id}): {send_err}") - - # Loggear mensaje saliente - try: - TelegramMessage.objects.create( - conversation=conv, - direction=TelegramMessage.OUT, - content=reply, - response_time_ms=response_time - ) - except Exception as log_out_err: - logger.error(f"Error logging outbound message: {log_out_err}") - - except Exception as cmd_err: - logger.error(f"Error processing Telegram commands: {cmd_err}") - - # 6) Devolver siempre 200 OK - return JsonResponse({"status": "ok"}) + logger.warning("Received non-POST request") + return JsonResponse({"error": "Invalid request method"}, status=400) except Exception as e: - logger.error(f"Unexpected error in telegram_webhook: {e}") - return JsonResponse({"status": "ok", "error": str(e)}) + logger.error(f"Error in webhook: {e}") + return JsonResponse({"error": f"Unexpected error: {str(e)}"}, status=500)