melodix-api / app /utils /telegram_storage.py
GitHub Action
deploy from github actions
440bac0
Raw
History Blame Contribute Delete
5.63 kB
# ==========================================
# MELODIX - Telegram Storage Utility
# Gestiona subida/descarga de stems al canal de Telegram desde el backend.
# El Bot Token NUNCA se expone al cliente móvil.
# ==========================================
import os
import logging
import requests
from dotenv import load_dotenv
load_dotenv()
logger = logging.getLogger(__name__)
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "")
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "")
TELEGRAM_API = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}"
TELEGRAM_FILE_API = f"https://api.telegram.org/file/bot{TELEGRAM_BOT_TOKEN}"
def _check_config():
if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
raise RuntimeError(
"TELEGRAM_BOT_TOKEN y TELEGRAM_CHAT_ID deben estar configurados en .env"
)
def upload_stem_to_telegram(file_path: str, stem_name: str, task_id: str) -> dict:
"""
Sube un archivo FLAC al canal de Telegram con reintentos y tolerancia a límite de peticiones (429).
Returns:
{"file_id": str, "message_id": int}
Raises:
RuntimeError si falla la subida después de todos los reintentos.
"""
import time as _time
_check_config()
if not os.path.exists(file_path):
raise FileNotFoundError(f"Stem no encontrado para subir: {file_path}")
file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
logger.info(f"[Telegram] Subiendo stem '{stem_name}' de tarea '{task_id}' ({file_size_mb:.1f} MB)...")
caption = f"🎵 task:{task_id} | stem:{stem_name}"
max_retries = 3
for attempt in range(1, max_retries + 1):
try:
with open(file_path, "rb") as f:
response = requests.post(
f"{TELEGRAM_API}/sendDocument",
data={
"chat_id": TELEGRAM_CHAT_ID,
"caption": caption,
},
files={
"document": (f"{task_id}_{stem_name}.flac", f, "audio/flac"),
},
timeout=300, # 5 minutos por archivo
)
# Manejar límite de peticiones de Telegram
if response.status_code == 429:
retry_after = 5
try:
retry_after = int(response.headers.get("Retry-After", 5))
except:
pass
logger.warning(f"[Telegram] Límite de peticiones (429) alcanzado para '{stem_name}'. Reintentando en {retry_after}s... (Intento {attempt}/{max_retries})")
_time.sleep(retry_after)
continue
if not response.ok:
raise RuntimeError(f"HTTP {response.status_code}: {response.text}")
data = response.json()
if not data.get("ok"):
raise RuntimeError(f"Error API: {data.get('description', 'desconocido')}")
document = data["result"].get("document") or data["result"].get("audio")
if not document:
raise RuntimeError("La respuesta no contiene 'document'")
file_id = document["file_id"]
message_id = data["result"]["message_id"]
logger.info(f"[Telegram] ✅ '{stem_name}' subido con éxito en el intento {attempt}. message_id={message_id}")
return {"file_id": file_id, "message_id": message_id}
except Exception as e:
logger.warning(f"[Telegram] Error en intento {attempt}/{max_retries} para '{stem_name}': {e}")
if attempt == max_retries:
raise RuntimeError(f"No se pudo subir a Telegram después de {max_retries} intentos. Error: {e}")
_time.sleep(3) # Espera corta antes de reintentar
def get_telegram_file_url(file_id: str) -> str:
"""
Obtiene la URL de descarga directa (temporal ~1 hora) de un archivo en Telegram.
Returns:
URL de descarga directa (str)
Raises:
RuntimeError si falla.
"""
_check_config()
response = requests.get(
f"{TELEGRAM_API}/getFile",
params={"file_id": file_id},
timeout=30,
)
if not response.ok:
try:
err_desc = response.json().get("description", response.text)
except Exception:
err_desc = response.text
raise RuntimeError(f"[Telegram] getFile HTTP {response.status_code}: {err_desc}")
data = response.json()
if not data.get("ok"):
raise RuntimeError(f"[Telegram] getFile error: {data.get('description')}")
file_path = data["result"]["file_path"]
return f"{TELEGRAM_FILE_API}/{file_path}"
def delete_telegram_message(message_id: int) -> bool:
"""
Elimina un mensaje (y por ende su archivo) del canal de Telegram.
Llamar cuando el usuario borra una pista desde la app.
Returns:
True si se eliminó, False si hubo error no crítico.
"""
_check_config()
try:
response = requests.post(
f"{TELEGRAM_API}/deleteMessage",
json={"chat_id": TELEGRAM_CHAT_ID, "message_id": message_id},
timeout=15,
)
data = response.json()
if data.get("ok"):
logger.info(f"[Telegram] Mensaje {message_id} eliminado del canal.")
return True
else:
logger.warning(f"[Telegram] No se pudo eliminar mensaje {message_id}: {data.get('description')}")
return False
except Exception as e:
logger.warning(f"[Telegram] Error eliminando mensaje {message_id}: {e}")
return False