First commit
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
"""
|
||||
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"}
|
||||
|
||||
Reference in New Issue
Block a user