from typing import Callable, Any, Optional from yt_dlp import YoutubeDL from database import get_all_active_proxies, format_proxy_url, mark_proxy_success, mark_proxy_failure class ProxyError(Exception): pass def is_proxy_error(error_msg: str) -> bool: proxy_error_keywords = [ 'proxy', 'connection', 'timeout', 'timed out', 'refused', 'unreachable', 'unable to connect', 'network', 'failed to connect', 'connection reset', 'connection aborted', 'read timed out', 'http error 407', # Proxy authentication required 'tunnel connection failed', 'connect to host', 'bot', # YouTube bloqueando proxy como bot 'sign in to confirm', # YouTube pedindo verificação 'http error 429', # Too many requests 'failed to extract', # Falha ao extrair dados (pode ser proxy ruim) 'player response', # Problemas com resposta do player 'http error 403', # Forbidden (pode ser proxy bloqueado) 'http error 503', # Service unavailable (pode ser proxy) 'ssl', # Erros SSL causados por proxy 'certificate', # Erros de certificado causados por proxy 'certificate_verify_failed', # Verificação de certificado SSL falhou 'failed to parse json', # Erros de parsing JSON (proxy retornando HTML) 'jsondecode', # Erros de decodificação JSON 'remote end closed', # Conexão fechada pelo proxy 'remotedisconnected', # Desconexão remota 'connection/parsing error', # Erros de conexão/parsing do download ] non_proxy_error_keywords = [ 'requested format is not available', 'format not available', 'no video formats', 'video unavailable', 'private video', 'age restricted', ] error_lower = error_msg.lower() if any(keyword in error_lower for keyword in non_proxy_error_keywords): return False return any(keyword in error_lower for keyword in proxy_error_keywords) def execute_with_proxy_retry( ydl_opts: dict, operation: Callable[[YoutubeDL], Any], retry_per_proxy: int = 2, max_proxies_to_try: int = None ) -> Any: """ Tenta executar operação com proxies disponíveis. Cada proxy é tentado N vezes antes de passar para o próximo. Proxies NÃO são removidos do banco, apenas pulados. Args: ydl_opts: Opções do YoutubeDL operation: Função a ser executada retry_per_proxy: Número de tentativas por proxy antes de pular para o próximo max_proxies_to_try: Número máximo de proxies a tentar (None = todos) """ # Busca TODOS os proxies ativos all_proxies = get_all_active_proxies() # Limita número de proxies se especificado if max_proxies_to_try is not None and max_proxies_to_try > 0: all_proxies = all_proxies[:max_proxies_to_try] total_proxies = len(all_proxies) last_error = None # Tenta cada proxy da lista for proxy_index, proxy_data in enumerate(all_proxies, 1): proxy_url = format_proxy_url(proxy_data) # Proxy desativado temporariamente - Para reativar, descomente a linha abaixo: # ydl_opts_with_proxy = {**ydl_opts, 'proxy': proxy_url} ydl_opts_with_proxy = {**ydl_opts} # Sem proxy por enquanto proxy_failed = False # Tenta N vezes com o MESMO proxy for attempt in range(1, retry_per_proxy + 1): try: with YoutubeDL(ydl_opts_with_proxy) as ydl: result = operation(ydl) mark_proxy_success(proxy_data['id']) return result except Exception as e: error_msg = str(e) last_error = e # Se não for erro de proxy, lança imediatamente if not is_proxy_error(error_msg): raise e proxy_failed = True # Se chegou aqui, falhou todas as tentativas com este proxy if proxy_failed: mark_proxy_failure(proxy_data['id']) # Se chegou aqui, todos os proxies falharam, tenta SEM proxy try: with YoutubeDL(ydl_opts) as ydl: result = operation(ydl) return result except Exception as e: last_error = e raise ProxyError( f"Falha após tentar {total_proxies} proxies + tentativa sem proxy. Último erro: {last_error}" )