import os import json import logging import openai from telegram import Update, Bot from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from asgiref.sync import sync_to_async from .models import TelegramBot from pxy_langchain.services import LangchainAIService from .handlers import ( start, help_command, handle_location, next_truck, report_trash, private_pickup, green_balance, next_route, complete_stop, missed_stop, city_eco_score, available_jobs, accept_job, next_pickup, complete_pickup, private_eco_score ) logger = logging.getLogger(__name__) # Configura tu API Key de OpenAI openai.api_key = os.getenv("OPENAI_API_KEY") # ------------------------------- # 🛠 Modular local handlers inside views.py # ------------------------------- async def handle_location_message(update): if update.message.location: await handle_location(update) return True return False async def dispatch_citizen_commands(update, text): if text == "/start": await start(update) elif text == "/help": await help_command(update) elif text == "/next_truck": await next_truck(update) elif text == "/report_trash": await report_trash(update) elif text == "/private_pickup": await private_pickup(update) elif text == "/green_balance": await green_balance(update) else: return False return True async def dispatch_city_commands(update, text): if text == "/start": await start(update) elif text == "/help": await help_command(update) elif text == "/next_route": await next_route(update) elif text == "/complete_stop": await complete_stop(update) elif text == "/missed_stop": await missed_stop(update) elif text == "/my_eco_score": await city_eco_score(update) else: return False return True async def dispatch_private_commands(update, text): if text == "/start": await start(update) elif text == "/help": await help_command(update) elif text == "/available_jobs": await available_jobs(update) elif text.startswith("/accept_job"): await accept_job(update) elif text == "/next_pickup": await next_pickup(update) elif text == "/complete_pickup": await complete_pickup(update) elif text == "/my_eco_score": await private_eco_score(update) else: return False return True # ------------------------------- # 🛠 Voice transcription helper # ------------------------------- async def transcribe_with_whisper(update, bot): # 1. Descarga el archivo de voz desde Telegram tg_file = await sync_to_async(bot.get_file)(update.message.voice.file_id) download_path = f"/tmp/{update.message.voice.file_id}.ogg" await sync_to_async(tg_file.download)(download_path) # 2. Envía el audio a la API Whisper de OpenAI with open(download_path, "rb") as audio: transcript = openai.Audio.transcribe( model="whisper-1", file=audio, response_format="text", language="es" ) return transcript.strip() # ------------------------------- # 🌐 Main webhook # ------------------------------- @csrf_exempt async def telegram_webhook(request, bot_name): try: logger.info(f"Webhook called for bot: {bot_name}") # Carga la configuración del bot 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({"error": f"Bot '{bot_name}' not found."}, status=400) if not bot_instance.assistant: return JsonResponse({"error": "Assistant not configured."}, status=400) if request.method != "POST": return JsonResponse({"error": "Invalid request method"}, status=400) # Decodifica el payload de Telegram try: payload = json.loads(request.body.decode("utf-8")) update = Update.de_json(payload, Bot(token=bot_instance.token)) except json.JSONDecodeError as e: logger.error(f"Failed to decode JSON: {e}") return JsonResponse({"error": "Invalid JSON payload"}, status=400) if not update.message: return JsonResponse({"status": "no message"}) # 1) Geolocalización if await handle_location_message(update): return JsonResponse({"status": "ok"}) # 2) Voz: transcribir y llamar a report_trash if update.message.voice: bot = Bot(token=bot_instance.token) transcript = await transcribe_with_whisper(update, bot) if not transcript: await update.message.reply_text( "No pude entender tu mensaje de voz. Intenta de nuevo." ) return JsonResponse({"status": "ok"}) update.message.text = transcript await report_trash(update) return JsonResponse({"status": "ok"}) # 3) Comandos de texto por bot text = update.message.text or "" if bot_name == "PepeBasuritaCoinsBot" and await dispatch_citizen_commands(update, text): return JsonResponse({"status": "ok"}) if bot_name == "PepeCamioncitoBot" and await dispatch_city_commands(update, text): return JsonResponse({"status": "ok"}) if bot_name == "PepeMotitoBot" and await dispatch_private_commands(update, text): return JsonResponse({"status": "ok"}) # 4) Fallback a LLM assistant_instance = await sync_to_async(LangchainAIService)(bot_instance.assistant) bot_response = await sync_to_async(assistant_instance.generate_response)(text) await update.message.reply_text(bot_response) return JsonResponse({"status": "ok"}) except Exception as e: logger.error(f"Error in webhook: {e}") return JsonResponse({"error": f"Unexpected error: {str(e)}"}, status=500)