|
|
"""
|
|
|
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
|
|
|
|