from typing import Callable, Any, Optional from yt_dlp import YoutubeDL from database import get_all_active_proxies, format_proxy_url, mark_proxy_success 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 ] 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 ) -> Any: """ Tenta executar operação com todos os 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 """ # Busca TODOS os proxies ativos all_proxies = get_all_active_proxies() total_proxies = len(all_proxies) print(f"\n{'='*60}") print(f"Proxies disponíveis no banco: {total_proxies}") print(f"Tentativas por proxy: {retry_per_proxy}") print(f"{'='*60}\n") 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) ydl_opts_with_proxy = {**ydl_opts, 'proxy': proxy_url} print(f"\n[Proxy {proxy_index}/{total_proxies}] {proxy_url} (ID: {proxy_data['id']})") # Tenta N vezes com o MESMO proxy for attempt in range(1, retry_per_proxy + 1): try: print(f" → Tentativa {attempt}/{retry_per_proxy}...", end=" ") with YoutubeDL(ydl_opts_with_proxy) as ydl: result = operation(ydl) print(f"✓ SUCESSO!") mark_proxy_success(proxy_data['id']) return result except Exception as e: error_msg = str(e) last_error = e print(f"✗ Falhou") print(f" Erro: {error_msg[:80]}...") # Se não for erro de proxy, lança imediatamente if not is_proxy_error(error_msg): print(f" ⚠ Erro não relacionado a proxy, abortando") raise e # Se chegou aqui, falhou todas as tentativas com este proxy print(f" ⨯ Proxy falhou {retry_per_proxy} vezes, pulando para o próximo...") # Se chegou aqui, todos os proxies falharam, tenta SEM proxy print(f"\n{'='*60}") print(f"Todos os {total_proxies} proxies falharam") print(f"Tentando SEM proxy como último recurso...") print(f"{'='*60}\n") try: print(f" → Tentativa sem proxy...", end=" ") with YoutubeDL(ydl_opts) as ydl: result = operation(ydl) print(f"✓ SUCESSO!") return result except Exception as e: print(f"✗ Falhou") print(f" Erro: {str(e)[:80]}...") last_error = e raise ProxyError( f"Falha após tentar {total_proxies} proxies + tentativa sem proxy. Último erro: {last_error}" )