Spaces:
Running
Running
| # ========================================== | |
| # 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 | |