Spaces:
Running
Running
File size: 7,611 Bytes
2dc7238 bbbe047 3b1b98d 2dc7238 bbbe047 3b1b98d 5bea81c 3b1b98d 2dc7238 3b1b98d 2dc7238 3b1b98d 2dc7238 5bea81c 2dc7238 3b1b98d 2dc7238 3b1b98d 2dc7238 3b1b98d 2dc7238 bbbe047 3b1b98d 2dc7238 bbbe047 2dc7238 3b1b98d 5bea81c 3b1b98d 5bea81c 2dc7238 bbbe047 3b1b98d bbbe047 3b1b98d bbbe047 3b1b98d bbbe047 3b1b98d bbbe047 3b1b98d bbbe047 3b1b98d bbbe047 3b1b98d bbbe047 2dc7238 bbbe047 2dc7238 bbbe047 2dc7238 bbbe047 2dc7238 3b1b98d 2dc7238 5bea81c 2dc7238 5bea81c 3b1b98d 2dc7238 5bea81c 2dc7238 bbbe047 2dc7238 5bea81c 2dc7238 bbbe047 2dc7238 bbbe047 2dc7238 bbbe047 3b1b98d bbbe047 3b1b98d bbbe047 2dc7238 bbbe047 2dc7238 bbbe047 2dc7238 bbbe047 2dc7238 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
"""
Proxy Manager for Browser Portals
---------------------------------
Supports custom IP proxy configuration with optional authentication.
"""
import asyncio
import aiohttp
import logging
from typing import Optional, Dict
from dataclasses import dataclass, field
from datetime import datetime
from urllib.parse import urlparse
logger = logging.getLogger("kai_api.proxy_manager")
@dataclass
class Proxy:
"""Represents a proxy server with optional authentication."""
ip: str
port: int
protocol: str = "http"
username: Optional[str] = None
password: Optional[str] = None
country: str = "Custom"
last_tested: Optional[datetime] = None
is_working: bool = False
response_time: float = 999.0
def __str__(self):
"""Return proxy URL string."""
if self.username and self.password:
return f"{self.protocol}://{self.username}:{self.password}@{self.ip}:{self.port}"
return f"{self.protocol}://{self.ip}:{self.port}"
def to_display_string(self) -> str:
"""Return proxy URL without credentials for display."""
return f"{self.protocol}://{self.ip}:{self.port}"
def to_playwright_format(self) -> Dict:
"""Convert to Playwright proxy format."""
proxy_dict = {
"server": f"{self.protocol}://{self.ip}:{self.port}",
}
# Add authentication if present
if self.username:
proxy_dict["username"] = self.username
if self.password:
proxy_dict["password"] = self.password
return proxy_dict
class ProxyManager:
"""Manages proxy configuration with optional authentication."""
def __init__(self):
self.custom_proxy: Optional[Proxy] = None
self._proxy_str: Optional[str] = None
def set_custom_proxy(self, proxy_str: str, username: Optional[str] = None, password: Optional[str] = None) -> bool:
"""
Set a custom proxy from string format.
Supports formats:
- ip:port
- protocol://ip:port
- protocol://username:password@ip:port
- ip:port (with separate username/password params)
Examples:
- 192.168.1.1:8080
- http://proxy.example.com:3128
- http://user:pass@proxy.example.com:3128
"""
try:
proxy_str = proxy_str.strip()
# Check if credentials are embedded in URL
parsed = urlparse(proxy_str)
# Parse protocol
protocol = parsed.scheme or "http"
# Get host and port
host = parsed.hostname
port = parsed.port
# If no host parsed, try simple ip:port format
if not host:
if ":" not in proxy_str:
raise ValueError("Proxy must include port (e.g., ip:port)")
# Remove protocol if present
if "://" in proxy_str:
protocol, proxy_str = proxy_str.split("://", 1)
parts = proxy_str.rsplit(":", 1)
host = parts[0]
port = int(parts[1])
# Extract credentials from URL if present
url_username = parsed.username
url_password = parsed.password
# Use provided credentials or extracted ones
final_username = username or url_username
final_password = password or url_password
self.custom_proxy = Proxy(
ip=host,
port=port,
protocol=protocol,
username=final_username,
password=final_password,
is_working=True, # Assume working until tested
last_tested=datetime.now()
)
self._proxy_str = str(self.custom_proxy)
auth_info = f" with auth" if final_username else ""
logger.info(f"✅ Custom proxy set: {self.custom_proxy.to_display_string()}{auth_info}")
return True
except Exception as e:
logger.error(f"❌ Failed to set custom proxy: {e}")
return False
def clear_proxy(self):
"""Clear the custom proxy."""
self.custom_proxy = None
self._proxy_str = None
logger.info("🗑️ Custom proxy cleared")
def get_current_proxy(self) -> Optional[Proxy]:
"""Get the current custom proxy."""
return self.custom_proxy
def get_proxy_string(self) -> Optional[str]:
"""Get the proxy string for environment variables."""
return self._proxy_str
async def test_proxy(self, proxy: Optional[Proxy] = None) -> bool:
"""Test if a proxy is working."""
test_proxy = proxy or self.custom_proxy
if not test_proxy:
return False
try:
timeout = aiohttp.ClientTimeout(total=10)
# Build proxy URL with auth if present
if test_proxy.username and test_proxy.password:
proxy_url = f"{test_proxy.protocol}://{test_proxy.username}:{test_proxy.password}@{test_proxy.ip}:{test_proxy.port}"
else:
proxy_url = f"{test_proxy.protocol}://{test_proxy.ip}:{test_proxy.port}"
async with aiohttp.ClientSession(timeout=timeout) as session:
start = asyncio.get_event_loop().time()
async with session.get(
"http://httpbin.org/ip",
proxy=proxy_url,
ssl=False
) as response:
elapsed = asyncio.get_event_loop().time() - start
if response.status == 200:
test_proxy.is_working = True
test_proxy.response_time = elapsed
test_proxy.last_tested = datetime.now()
logger.info(f"✅ Proxy test passed: {elapsed:.2f}s")
return True
return False
except Exception as e:
logger.warning(f"❌ Proxy test failed: {e}")
test_proxy.is_working = False
return False
def get_status(self) -> Dict:
"""Get proxy status."""
if not self.custom_proxy:
return {
"enabled": False,
"proxy": None,
"message": "No custom proxy configured"
}
return {
"enabled": True,
"proxy": self.custom_proxy.to_display_string(),
"full_url": str(self.custom_proxy),
"protocol": self.custom_proxy.protocol,
"ip": self.custom_proxy.ip,
"port": self.custom_proxy.port,
"has_auth": bool(self.custom_proxy.username),
"username": self.custom_proxy.username,
"is_working": self.custom_proxy.is_working,
"response_time": f"{self.custom_proxy.response_time:.2f}s",
"last_tested": self.custom_proxy.last_tested.isoformat() if self.custom_proxy.last_tested else None
}
# Global proxy manager instance
_proxy_manager: Optional[ProxyManager] = None
def get_proxy_manager() -> ProxyManager:
"""Get the global proxy manager instance."""
global _proxy_manager
if _proxy_manager is None:
_proxy_manager = ProxyManager()
return _proxy_manager
|