""" Mannage.py Contiene los endpoints utilziados para adminitración o manejo de grupos o sesiones de telegram. """ from time import time from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel from integrations.chats import TelegramChatSingleton from dotenv import load_dotenv import os from typing import Dict, Optional import requests import asyncio from integrations.api_implementations import post_api_sync, create_telegram_group from telethon import TelegramClient from telethon.sessions import StringSession from telethon.errors import SessionPasswordNeededError import httpx import uuid from auth import get_current_user #Almacenes temporales login_flows = {} # flow_id -> {"client": TelegramClient, "phone": str} user_sessions = {} # token -> session_string class VerifyCodeRequest(BaseModel): flow_id: str code: str password: Optional[str] = None class VerifyCodeResponse(BaseModel): token: str message: str router = APIRouter() load_dotenv() API_BASE_URL = os.getenv('API_URL') HEADERS = {"Content-Type": "application/json"} TIMEOUT = 5.0 cache_status = {"connected": None, "last_check": 0} TTL = 10 # segundos router = APIRouter() load_dotenv() @router.post("/manage/", tags=['Manage']) def add_pending_chat(channel: int, current_user: str = Depends(get_current_user)): """ Agrega un canal/grupo viendo si existe en telegram anteriormente. Primero valida que el ID sea accesible en Telegram, luego lo guarda con los datos reales (nombre, tipo). """ # 1. Verificar que el cliente de Telegram está disponible try: scraper = TelegramChatSingleton.get_scraper() except Exception as e: raise HTTPException( status_code=503, detail=f"El cliente de Telegram no está disponible: {str(e)}" ) # 2. Validar el canal contra Telegram try: validation = scraper.validate_chat(channel) except RuntimeError as e: raise HTTPException(status_code=503, detail=str(e)) except Exception as e: raise HTTPException( status_code=503, detail=f"Error al validar en Telegram: {str(e)}" ) if not validation["valid"]: raise HTTPException( status_code=400, detail=f"Canal no válido o inaccesible: {validation.get('error', 'desconocido')}" ) info = validation["info"] # 3. Guardar con datos reales de Telegram — sin pasar por estado pendiente from integrations.api_implementations import refresh_telegram_group tipo_interno = info["type"] nombre = info["username"] if info["username"] != str(info["id_telegram"]) else info["title"] or str(info["id_telegram"]) # Intentar crear primero (caso nuevo) new_group = { "id_telegram": info["id_telegram"], "name": nombre, "description": info["description"] or info["title"] or "", "type": tipo_interno, "message_position": 0, } created_group = create_telegram_group(new_group) if created_group: return { "status": "success", "group": created_group, "message": "Grupo validado y agregado correctamente", "details": { "title": info["title"], "type": info["type"], "id_telegram": info["id_telegram"], } } # Si ya existía, actualizar sus datos con los de Telegram update_data = { "name": nombre, "description": info["description"] or info["title"] or "", "type": tipo_interno, } updated_group = refresh_telegram_group(info["id_telegram"], update_data) if updated_group: return { "status": "updated", "group": updated_group, "message": "El grupo ya existía y fue actualizado con los datos de Telegram", "details": { "title": info["title"], "type": info["type"], "id_telegram": info["id_telegram"], } } return { "status": "exists", "message": f"El grupo con ID {info['id_telegram']} ya existe en el sistema" } @router.get("/manage/connection-status", tags=['Manage']) async def telegram_status(): ahora = time() if ahora - cache_status["last_check"] < TTL and cache_status["connected"] is not None: return {"connected": cache_status["connected"], "cached": True} # Realizar la verificación real try: response = requests.get("https://api.telegram.org", timeout=2) cache_status["connected"] = response.status_code == 200 or 302 except: cache_status["connected"] = False cache_status["last_check"] = ahora return {"connected": cache_status["connected"], "cached": False} @router.get("/manage/init-session", tags=['Manage']) async def init_session(current_user: str = Depends(get_current_user)): flow_id = str(uuid.uuid4()) session_dir = os.path.join(os.getcwd(), "telegram_sessions") os.makedirs(session_dir, exist_ok=True) # --- Limpiar sesiones anteriores de forma segura --- active_session_files = { f"{data['client'].session.filename}" for data in login_flows.values() if "client" in data } for filename in os.listdir(session_dir): if not filename.endswith(".session"): continue filepath = os.path.join(session_dir, filename) # Verificar que es un archivo real y no está en uso if not os.path.isfile(filepath): continue if filepath in active_session_files: print(f"[INFO] Sesión en uso, se omite: {filename}") continue try: os.remove(filepath) print(f"[INFO] Sesión eliminada: {filename}") except Exception as e: print(f"[WARN] No se pudo eliminar {filename}: {e}") # Usar el flow_id como nombre de sesión para evitar conflictos entre flujos session_file = os.path.join(session_dir, f"session_{flow_id}") client = TelegramClient( session_file, int(os.getenv('TELEGRAM_API_ID')), # debe ser int os.getenv('TELEGRAM_API_HASH') ) # ✅ await directamente, nunca asyncio.run() dentro de async def await client.connect() if not await client.is_user_authorized(): try: await client.send_code_request(phone=os.getenv('TELEGRAM_TELEPHONE')) except Exception as e: await client.disconnect() raise HTTPException(status_code=400, detail=f"Error enviando código: {str(e)}") login_flows[flow_id] = { "client": client, "phone": os.getenv('TELEGRAM_TELEPHONE') } return { "flow_id": flow_id, "message": "Código enviado. Usa /verify-code para completar la autenticación." } @router.post("/manage/verify-code", tags=['Manage']) async def verify_code(request: VerifyCodeRequest, current_user: str = Depends(get_current_user)): flow = login_flows.get(request.flow_id) if not flow: raise HTTPException(status_code=404, detail="Flow no encontrado o expirado") client: TelegramClient = flow["client"] phone = flow["phone"] # Reconectar si el cliente se desconectó entre requests if not client.is_connected(): await client.connect() try: await client.sign_in(phone=phone, code=request.code) except SessionPasswordNeededError: if not request.password: raise HTTPException( status_code=428, detail="Se requiere contraseña de dos factores" ) try: await client.sign_in(password=request.password) except Exception as e: await client.disconnect() del login_flows[request.flow_id] raise HTTPException(status_code=400, detail=f"Error en contraseña 2FA: {str(e)}") except Exception as e: await client.disconnect() del login_flows[request.flow_id] raise HTTPException(status_code=400, detail=f"Error verificando código: {str(e)}") # Éxito: guardar sesión y limpiar session_string = client.session.save() token = str(uuid.uuid4()) user_sessions[token] = session_string await asyncio.sleep(1) await client.disconnect() del login_flows[request.flow_id] # Limpiar la instancia singleton para que tome la nueva sesión TelegramChatSingleton.cleanup() #Inicializa scraper. TelegramChatSingleton.get_scraper() return {"token": token, "message": "Autenticación exitosa"}