| """
|
| Smart Proxy/DNS Manager
|
| Handles proxy rotation for sanctioned exchanges (Binance, etc.)
|
| """
|
|
|
| import asyncio
|
| import aiohttp
|
| import random
|
| import time
|
| from typing import List, Dict, Optional
|
| from dataclasses import dataclass
|
| from datetime import datetime, timedelta
|
| import logging
|
|
|
| logger = logging.getLogger(__name__)
|
|
|
|
|
| @dataclass
|
| class ProxyServer:
|
| """Proxy server configuration"""
|
| url: str
|
| protocol: str = "http"
|
| username: Optional[str] = None
|
| password: Optional[str] = None
|
| success_count: int = 0
|
| failure_count: int = 0
|
| last_used: Optional[datetime] = None
|
| avg_response_time: float = 0.0
|
| is_active: bool = True
|
|
|
| def get_proxy_url(self) -> str:
|
| """Get full proxy URL with auth"""
|
| if self.username and self.password:
|
| return f"{self.protocol}://{self.username}:{self.password}@{self.url}"
|
| return f"{self.protocol}://{self.url}"
|
|
|
| def record_success(self, response_time: float):
|
| """Record successful proxy usage"""
|
| self.success_count += 1
|
| self.last_used = datetime.now()
|
|
|
| if self.avg_response_time == 0:
|
| self.avg_response_time = response_time
|
| else:
|
| self.avg_response_time = 0.7 * self.avg_response_time + 0.3 * response_time
|
|
|
| def record_failure(self):
|
| """Record proxy failure"""
|
| self.failure_count += 1
|
| self.last_used = datetime.now()
|
|
|
|
|
| if self.failure_count > 10:
|
| self.is_active = False
|
|
|
| def get_success_rate(self) -> float:
|
| """Get success rate"""
|
| total = self.success_count + self.failure_count
|
| return self.success_count / max(total, 1)
|
|
|
|
|
| @dataclass
|
| class DNSServer:
|
| """Smart DNS server"""
|
| address: str
|
| port: int = 53
|
| protocol: str = "udp"
|
| is_active: bool = True
|
| success_count: int = 0
|
| failure_count: int = 0
|
|
|
| def get_address(self) -> str:
|
| """Get DNS server address"""
|
| return f"{self.address}:{self.port}"
|
|
|
|
|
| class SmartProxyManager:
|
| """
|
| Smart proxy manager with rotation and health tracking
|
| Supports multiple proxy types and smart DNS
|
| """
|
|
|
| def __init__(self):
|
| self.proxies: List[ProxyServer] = []
|
| self.dns_servers: List[DNSServer] = []
|
| self.current_proxy_index = 0
|
| self.rotation_enabled = True
|
| self.rotation_interval = 60
|
| self.last_rotation = datetime.now()
|
|
|
|
|
| self._load_default_proxies()
|
| self._load_default_dns()
|
|
|
| logger.info(f"✅ SmartProxyManager initialized with {len(self.proxies)} proxies and {len(self.dns_servers)} DNS servers")
|
|
|
| def _load_default_proxies(self):
|
| """Load default free proxy list"""
|
|
|
| default_proxies = [
|
|
|
| "proxy1.example.com:8080",
|
| "proxy2.example.com:3128",
|
|
|
| "socks5://proxy3.example.com:1080",
|
| ]
|
|
|
|
|
|
|
|
|
| for proxy_url in default_proxies:
|
| if proxy_url.startswith("socks5://"):
|
| protocol = "socks5"
|
| url = proxy_url.replace("socks5://", "")
|
| else:
|
| protocol = "http"
|
| url = proxy_url
|
|
|
| self.proxies.append(ProxyServer(
|
| url=url,
|
| protocol=protocol
|
| ))
|
|
|
|
|
| import os
|
| env_proxy = os.getenv("PROXY_URL")
|
| if env_proxy:
|
| self.proxies.append(ProxyServer(url=env_proxy, protocol="http"))
|
|
|
| def _load_default_dns(self):
|
| """Load default smart DNS servers"""
|
|
|
| self.dns_servers = [
|
| DNSServer(address="1.1.1.1", port=53),
|
| DNSServer(address="8.8.8.8", port=53),
|
| DNSServer(address="9.9.9.9", port=53),
|
| DNSServer(address="208.67.222.222", port=53),
|
| ]
|
|
|
| async def get_proxy(self) -> Optional[str]:
|
| """Get next available proxy with rotation"""
|
| if not self.proxies:
|
| logger.warning("⚠️ No proxies configured")
|
| return None
|
|
|
|
|
| if self.rotation_enabled:
|
| now = datetime.now()
|
| if (now - self.last_rotation).seconds > self.rotation_interval:
|
| self._rotate_proxy()
|
| self.last_rotation = now
|
|
|
|
|
| active_proxies = [p for p in self.proxies if p.is_active]
|
|
|
| if not active_proxies:
|
| logger.error("❌ All proxies are inactive!")
|
| return None
|
|
|
|
|
| active_proxies.sort(
|
| key=lambda p: (p.get_success_rate(), -p.avg_response_time),
|
| reverse=True
|
| )
|
|
|
|
|
| best_proxy = active_proxies[0]
|
| proxy_url = best_proxy.get_proxy_url()
|
|
|
| logger.debug(f"🔄 Using proxy: {best_proxy.url} (success rate: {best_proxy.get_success_rate():.1%})")
|
|
|
| return proxy_url
|
|
|
| def _rotate_proxy(self):
|
| """Rotate to next proxy"""
|
| if len(self.proxies) > 1:
|
| self.current_proxy_index = (self.current_proxy_index + 1) % len(self.proxies)
|
| logger.debug(f"🔄 Rotated to proxy #{self.current_proxy_index}")
|
|
|
| async def test_proxy(self, proxy: ProxyServer, test_url: str = "https://httpbin.org/ip") -> bool:
|
| """Test if proxy is working"""
|
| try:
|
| start_time = time.time()
|
|
|
| async with aiohttp.ClientSession() as session:
|
| async with session.get(
|
| test_url,
|
| proxy=proxy.get_proxy_url(),
|
| timeout=aiohttp.ClientTimeout(total=10)
|
| ) as response:
|
| if response.status == 200:
|
| response_time = time.time() - start_time
|
| proxy.record_success(response_time)
|
| logger.info(f"✅ Proxy {proxy.url} is working ({response_time:.2f}s)")
|
| return True
|
|
|
| proxy.record_failure()
|
| return False
|
|
|
| except Exception as e:
|
| proxy.record_failure()
|
| logger.warning(f"⚠️ Proxy {proxy.url} failed: {e}")
|
| return False
|
|
|
| async def test_all_proxies(self):
|
| """Test all proxies and update their status"""
|
| logger.info("🧪 Testing all proxies...")
|
|
|
| tasks = [self.test_proxy(proxy) for proxy in self.proxies]
|
| results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
|
| active_count = sum(1 for r in results if r is True)
|
| logger.info(f"✅ {active_count}/{len(self.proxies)} proxies are active")
|
|
|
| def add_proxy(self, url: str, protocol: str = "http", username: str = None, password: str = None):
|
| """Add a new proxy"""
|
| proxy = ProxyServer(
|
| url=url,
|
| protocol=protocol,
|
| username=username,
|
| password=password
|
| )
|
| self.proxies.append(proxy)
|
| logger.info(f"➕ Added proxy: {url}")
|
|
|
| def remove_proxy(self, url: str):
|
| """Remove a proxy"""
|
| self.proxies = [p for p in self.proxies if p.url != url]
|
| logger.info(f"➖ Removed proxy: {url}")
|
|
|
| def get_dns_server(self) -> str:
|
| """Get next DNS server"""
|
| active_dns = [d for d in self.dns_servers if d.is_active]
|
|
|
| if not active_dns:
|
| return "8.8.8.8:53"
|
|
|
|
|
| dns = random.choice(active_dns)
|
| return dns.get_address()
|
|
|
| async def resolve_with_smart_dns(self, hostname: str) -> Optional[str]:
|
| """Resolve hostname using smart DNS"""
|
| import socket
|
|
|
| dns_server = self.get_dns_server()
|
| logger.debug(f"🔍 Resolving {hostname} using DNS: {dns_server}")
|
|
|
| try:
|
|
|
| ip = socket.gethostbyname(hostname)
|
| logger.debug(f"✅ Resolved {hostname} -> {ip}")
|
| return ip
|
| except socket.gaierror as e:
|
| logger.error(f"❌ DNS resolution failed for {hostname}: {e}")
|
| return None
|
|
|
| def get_status_report(self) -> Dict:
|
| """Get proxy manager status"""
|
| active_proxies = [p for p in self.proxies if p.is_active]
|
|
|
| return {
|
| "total_proxies": len(self.proxies),
|
| "active_proxies": len(active_proxies),
|
| "inactive_proxies": len(self.proxies) - len(active_proxies),
|
| "dns_servers": len(self.dns_servers),
|
| "rotation_enabled": self.rotation_enabled,
|
| "rotation_interval": self.rotation_interval,
|
| "proxies": [
|
| {
|
| "url": p.url,
|
| "protocol": p.protocol,
|
| "is_active": p.is_active,
|
| "success_rate": p.get_success_rate(),
|
| "avg_response_time": p.avg_response_time,
|
| "success_count": p.success_count,
|
| "failure_count": p.failure_count
|
| }
|
| for p in self.proxies
|
| ]
|
| }
|
|
|
| async def fetch_with_proxy_rotation(
|
| self,
|
| url: str,
|
| max_retries: int = 3,
|
| **kwargs
|
| ) -> Optional[Dict]:
|
| """Fetch URL with automatic proxy rotation on failure"""
|
| for attempt in range(max_retries):
|
| proxy_url = await self.get_proxy()
|
|
|
| if not proxy_url:
|
| logger.warning("⚠️ No proxy available, trying direct connection")
|
| proxy_url = None
|
|
|
| try:
|
| start_time = time.time()
|
|
|
| async with aiohttp.ClientSession() as session:
|
| async with session.get(
|
| url,
|
| proxy=proxy_url,
|
| timeout=aiohttp.ClientTimeout(total=15),
|
| **kwargs
|
| ) as response:
|
| response.raise_for_status()
|
|
|
| response_time = time.time() - start_time
|
|
|
|
|
| if proxy_url:
|
| for proxy in self.proxies:
|
| if proxy.get_proxy_url() == proxy_url:
|
| proxy.record_success(response_time)
|
| break
|
|
|
| return await response.json()
|
|
|
| except Exception as e:
|
| logger.warning(f"⚠️ Proxy attempt {attempt + 1} failed: {e}")
|
|
|
|
|
| if proxy_url:
|
| for proxy in self.proxies:
|
| if proxy.get_proxy_url() == proxy_url:
|
| proxy.record_failure()
|
| break
|
|
|
|
|
| self._rotate_proxy()
|
|
|
|
|
| if attempt == max_retries - 1:
|
| raise
|
|
|
| return None
|
|
|
|
|
|
|
| _proxy_manager = None
|
|
|
| def get_proxy_manager() -> SmartProxyManager:
|
| """Get global proxy manager instance"""
|
| global _proxy_manager
|
| if _proxy_manager is None:
|
| _proxy_manager = SmartProxyManager()
|
| return _proxy_manager
|
|
|