# ========================================== # 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