import json import logging from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from asgiref.sync import sync_to_async from telegram import Update, Bot from django.utils import timezone from django.conf import settings from .models import TelegramBot, TelegramConversation, TelegramMessage from pxy_langchain.services import LangchainAIService from .handlers import dream_city_command, start, help_command, handle_location logger = logging.getLogger(__name__) @csrf_exempt async def telegram_webhook(request, bot_name): """ Telegram webhook handler that logs inbound/outbound messages and responds with AI or commands, resilient to Neo4j or send failures. Always returns 200 OK to prevent Telegram retries. """ try: logger.info(f"Webhook called for bot: {bot_name}") # 1) Load bot configuration try: bot_instance = await sync_to_async(TelegramBot.objects.get)( name=bot_name, is_active=True ) except TelegramBot.DoesNotExist: logger.error(f"Bot '{bot_name}' not found or inactive.") return JsonResponse({"status": "ok", "error": f"Bot '{bot_name}' not found."}, status=200) # 2) Ensure POST if request.method != "POST": logger.warning("Received non-POST request to Telegram webhook.") return JsonResponse({"status": "ok"}, status=200) # 3) Parse the update JSON try: payload = json.loads(request.body.decode("utf-8")) update = Update.de_json(payload, Bot(token=bot_instance.token)) logger.info(f"Parsed update: {update}") except Exception as e: logger.error(f"Failed to parse update JSON: {e}") return JsonResponse({"status": "ok", "error": "Invalid JSON payload"}, status=200) # 4) Log inbound message user_id = str(update.effective_user.id) conv, _ = await sync_to_async(TelegramConversation.objects.get_or_create)( bot=bot_instance, user_id=user_id, defaults={'started_at': timezone.now()} ) incoming_text = update.message.text or "" try: await sync_to_async(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}") # 5) Handle commands or AI response if update.message: try: text = update.message.text or "" if text == "/start": await start(update) elif text == "/help": await help_command(update) elif text == "/dream_city": await dream_city_command(update) elif update.message.location: await handle_location(update) else: # AI fallback with resilience try: assistant = await sync_to_async(LangchainAIService)(bot_instance.assistant) start_time = timezone.now() bot_response = await sync_to_async(assistant.generate_response)(text) response_time = int((timezone.now() - start_time).total_seconds() * 1000) except Exception as ai_err: logger.error(f"AI service error: {ai_err}") bot_response = "Lo siento, el servicio de IA no está disponible." response_time = 0 # Send reply (skipped in DEBUG) if not settings.DEBUG: try: await update.message.reply_text(bot_response) except Exception as send_err: logger.error(f"Error sending message to Telegram: {send_err}") # Log outbound message try: await sync_to_async(TelegramMessage.objects.create)( conversation=conv, direction=TelegramMessage.OUT, content=bot_response, 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) Always return 200 OK return JsonResponse({"status": "ok"}, status=200) except Exception as e: logger.error(f"Unexpected error in telegram_webhook: {e}") return JsonResponse({"status": "ok", "error": str(e)}, status=200)