Spaces:
Sleeping
Add free proxy rotation system for ChatGPT and Copilot
Browse filesFeatures:
- FreeProxyManager class that fetches proxies from multiple sources
- Proxy testing to ensure IPs are active and working
- Proxy rotation with one-click IP switching
- Endpoints:
* POST /qaz/proxy/fetch - Fetch and test new proxies
* POST /qaz/proxy/rotate - Rotate to new working proxy
* GET /qaz/proxy/status - Check proxy status
* POST /qaz/proxy/test - Test current proxy
* POST /qaz/portal/{provider}/restart-with-proxy - Restart portal with proxy
- UI controls in portal interface:
* π Fetch New IPs - Get fresh proxies
* π² Rotate IP - Switch to different proxy
* β
Test Current IP - Verify proxy is working
* π Restart with Proxy - Apply proxy to portal
- Shows proxy country and response time
- Works specifically for ChatGPT and Copilot to bypass IP restrictions
This helps avoid blocks by rotating through free proxies from different countries!
- admin_router.py +132 -0
- browser_portal.py +16 -6
- proxy_manager.py +217 -0
- static/qaz.html +158 -0
|
@@ -662,3 +662,135 @@ async def get_all_portal_status():
|
|
| 662 |
}
|
| 663 |
except Exception as e:
|
| 664 |
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 662 |
}
|
| 663 |
except Exception as e:
|
| 664 |
raise HTTPException(status_code=500, detail=str(e))
|
| 665 |
+
|
| 666 |
+
|
| 667 |
+
# --- Proxy Management for Browser Portals ---
|
| 668 |
+
|
| 669 |
+
@router.post("/proxy/fetch")
|
| 670 |
+
async def fetch_new_proxies():
|
| 671 |
+
"""Fetch new free proxies and test them."""
|
| 672 |
+
try:
|
| 673 |
+
from proxy_manager import get_proxy_manager
|
| 674 |
+
|
| 675 |
+
proxy_mgr = get_proxy_manager()
|
| 676 |
+
|
| 677 |
+
# Fetch new proxies
|
| 678 |
+
proxies = await proxy_mgr.fetch_proxies(limit=30)
|
| 679 |
+
|
| 680 |
+
# Test first few to find a working one
|
| 681 |
+
working_proxy = await proxy_mgr.get_working_proxy(max_attempts=5)
|
| 682 |
+
|
| 683 |
+
stats = proxy_mgr.get_proxy_stats()
|
| 684 |
+
|
| 685 |
+
return {
|
| 686 |
+
"status": "success",
|
| 687 |
+
"message": f"Fetched {len(proxies)} proxies",
|
| 688 |
+
"working_proxy": str(working_proxy) if working_proxy else None,
|
| 689 |
+
"stats": stats
|
| 690 |
+
}
|
| 691 |
+
except Exception as e:
|
| 692 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 693 |
+
|
| 694 |
+
@router.post("/proxy/rotate")
|
| 695 |
+
async def rotate_proxy():
|
| 696 |
+
"""Rotate to a new working proxy."""
|
| 697 |
+
try:
|
| 698 |
+
from proxy_manager import get_proxy_manager
|
| 699 |
+
|
| 700 |
+
proxy_mgr = get_proxy_manager()
|
| 701 |
+
|
| 702 |
+
# Rotate to new proxy
|
| 703 |
+
new_proxy = await proxy_mgr.rotate_proxy()
|
| 704 |
+
|
| 705 |
+
if new_proxy:
|
| 706 |
+
return {
|
| 707 |
+
"status": "success",
|
| 708 |
+
"proxy": str(new_proxy),
|
| 709 |
+
"country": new_proxy.country,
|
| 710 |
+
"response_time": f"{new_proxy.response_time:.2f}s"
|
| 711 |
+
}
|
| 712 |
+
else:
|
| 713 |
+
raise HTTPException(status_code=503, detail="No working proxy available")
|
| 714 |
+
except HTTPException:
|
| 715 |
+
raise
|
| 716 |
+
except Exception as e:
|
| 717 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 718 |
+
|
| 719 |
+
@router.get("/proxy/status")
|
| 720 |
+
async def get_proxy_status():
|
| 721 |
+
"""Get current proxy status."""
|
| 722 |
+
try:
|
| 723 |
+
from proxy_manager import get_proxy_manager
|
| 724 |
+
|
| 725 |
+
proxy_mgr = get_proxy_manager()
|
| 726 |
+
|
| 727 |
+
stats = proxy_mgr.get_proxy_stats()
|
| 728 |
+
current = proxy_mgr.get_current_proxy()
|
| 729 |
+
|
| 730 |
+
return {
|
| 731 |
+
"current_proxy": str(current) if current else None,
|
| 732 |
+
**stats
|
| 733 |
+
}
|
| 734 |
+
except Exception as e:
|
| 735 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 736 |
+
|
| 737 |
+
@router.post("/proxy/test")
|
| 738 |
+
async def test_current_proxy():
|
| 739 |
+
"""Test if current proxy is working."""
|
| 740 |
+
try:
|
| 741 |
+
from proxy_manager import get_proxy_manager
|
| 742 |
+
|
| 743 |
+
proxy_mgr = get_proxy_manager()
|
| 744 |
+
current = proxy_mgr.get_current_proxy()
|
| 745 |
+
|
| 746 |
+
if not current:
|
| 747 |
+
raise HTTPException(status_code=400, detail="No proxy currently set")
|
| 748 |
+
|
| 749 |
+
is_working = await proxy_mgr.test_proxy(current)
|
| 750 |
+
|
| 751 |
+
return {
|
| 752 |
+
"status": "success",
|
| 753 |
+
"proxy": str(current),
|
| 754 |
+
"is_working": is_working,
|
| 755 |
+
"response_time": f"{current.response_time:.2f}s" if is_working else None
|
| 756 |
+
}
|
| 757 |
+
except HTTPException:
|
| 758 |
+
raise
|
| 759 |
+
except Exception as e:
|
| 760 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 761 |
+
|
| 762 |
+
@router.post("/portal/{provider}/restart-with-proxy")
|
| 763 |
+
async def restart_portal_with_proxy(provider: str):
|
| 764 |
+
"""Restart portal with current proxy."""
|
| 765 |
+
try:
|
| 766 |
+
from browser_portal import get_portal_manager, PortalProvider
|
| 767 |
+
from proxy_manager import get_proxy_manager
|
| 768 |
+
|
| 769 |
+
prov = PortalProvider(provider.lower())
|
| 770 |
+
portal = get_portal_manager().get_portal(prov)
|
| 771 |
+
proxy_mgr = get_proxy_manager()
|
| 772 |
+
|
| 773 |
+
# Get current proxy
|
| 774 |
+
current_proxy = proxy_mgr.get_current_proxy()
|
| 775 |
+
if not current_proxy:
|
| 776 |
+
# Fetch and test a new one
|
| 777 |
+
current_proxy = await proxy_mgr.get_working_proxy()
|
| 778 |
+
if not current_proxy:
|
| 779 |
+
raise HTTPException(status_code=503, detail="No working proxy available")
|
| 780 |
+
|
| 781 |
+
# Close existing portal
|
| 782 |
+
await portal.close()
|
| 783 |
+
|
| 784 |
+
# Reinitialize with proxy
|
| 785 |
+
await portal.initialize(headless=True, proxy=current_proxy)
|
| 786 |
+
|
| 787 |
+
return {
|
| 788 |
+
"status": "success",
|
| 789 |
+
"provider": provider,
|
| 790 |
+
"proxy": str(current_proxy),
|
| 791 |
+
"message": f"{provider} portal restarted with proxy {current_proxy.ip}"
|
| 792 |
+
}
|
| 793 |
+
except HTTPException:
|
| 794 |
+
raise
|
| 795 |
+
except Exception as e:
|
| 796 |
+
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -104,13 +104,16 @@ class BrowserPortal:
|
|
| 104 |
self.last_activity = None
|
| 105 |
self.is_logged_in = False
|
| 106 |
|
| 107 |
-
async def initialize(self, headless: bool = True):
|
| 108 |
-
"""Initialize the browser with enhanced stealth."""
|
| 109 |
if self.is_initialized:
|
| 110 |
return
|
| 111 |
|
| 112 |
try:
|
| 113 |
logger.info(f"π Portal [{self.provider.value}]: Launching browser...")
|
|
|
|
|
|
|
|
|
|
| 114 |
self.playwright = await async_playwright().start()
|
| 115 |
|
| 116 |
# Enhanced stealth args
|
|
@@ -125,10 +128,17 @@ class BrowserPortal:
|
|
| 125 |
"--force-color-profile=srgb",
|
| 126 |
]
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
self.context = await self.browser.new_context(
|
| 134 |
viewport=self.config.viewport,
|
|
|
|
| 104 |
self.last_activity = None
|
| 105 |
self.is_logged_in = False
|
| 106 |
|
| 107 |
+
async def initialize(self, headless: bool = True, proxy: Optional[Any] = None):
|
| 108 |
+
"""Initialize the browser with enhanced stealth and optional proxy."""
|
| 109 |
if self.is_initialized:
|
| 110 |
return
|
| 111 |
|
| 112 |
try:
|
| 113 |
logger.info(f"π Portal [{self.provider.value}]: Launching browser...")
|
| 114 |
+
if proxy:
|
| 115 |
+
logger.info(f"Using proxy: {proxy}")
|
| 116 |
+
|
| 117 |
self.playwright = await async_playwright().start()
|
| 118 |
|
| 119 |
# Enhanced stealth args
|
|
|
|
| 128 |
"--force-color-profile=srgb",
|
| 129 |
]
|
| 130 |
|
| 131 |
+
# Build browser launch options
|
| 132 |
+
launch_options = {
|
| 133 |
+
"headless": headless,
|
| 134 |
+
"args": args,
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
# Add proxy if provided
|
| 138 |
+
if proxy:
|
| 139 |
+
launch_options["proxy"] = proxy.to_playwright_format()
|
| 140 |
+
|
| 141 |
+
self.browser = await self.playwright.chromium.launch(**launch_options)
|
| 142 |
|
| 143 |
self.context = await self.browser.new_context(
|
| 144 |
viewport=self.config.viewport,
|
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Free Proxy Manager for Browser Portals
|
| 3 |
+
--------------------------------------
|
| 4 |
+
Fetches and manages free proxy servers for ChatGPT and Copilot.
|
| 5 |
+
Automatically tests proxies to ensure they're active.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import asyncio
|
| 9 |
+
import aiohttp
|
| 10 |
+
import random
|
| 11 |
+
import logging
|
| 12 |
+
from typing import Optional, List, Dict
|
| 13 |
+
from dataclasses import dataclass
|
| 14 |
+
from datetime import datetime, timedelta
|
| 15 |
+
|
| 16 |
+
logger = logging.getLogger("kai_api.proxy_manager")
|
| 17 |
+
|
| 18 |
+
@dataclass
|
| 19 |
+
class Proxy:
|
| 20 |
+
"""Represents a proxy server."""
|
| 21 |
+
ip: str
|
| 22 |
+
port: int
|
| 23 |
+
country: str
|
| 24 |
+
protocol: str = "http"
|
| 25 |
+
last_tested: Optional[datetime] = None
|
| 26 |
+
is_working: bool = False
|
| 27 |
+
response_time: float = 0.0
|
| 28 |
+
|
| 29 |
+
def __str__(self):
|
| 30 |
+
return f"{self.protocol}://{self.ip}:{self.port}"
|
| 31 |
+
|
| 32 |
+
def to_playwright_format(self) -> Dict:
|
| 33 |
+
"""Convert to Playwright proxy format."""
|
| 34 |
+
return {
|
| 35 |
+
"server": f"{self.protocol}://{self.ip}:{self.port}",
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class FreeProxyManager:
|
| 40 |
+
"""Manages free proxy servers for browser portals."""
|
| 41 |
+
|
| 42 |
+
def __init__(self):
|
| 43 |
+
self.proxies: List[Proxy] = []
|
| 44 |
+
self.current_proxy: Optional[Proxy] = None
|
| 45 |
+
self.proxy_history: List[Proxy] = []
|
| 46 |
+
self.max_history = 10
|
| 47 |
+
|
| 48 |
+
async def fetch_proxies(self, limit: int = 20) -> List[Proxy]:
|
| 49 |
+
"""Fetch free proxies from multiple sources."""
|
| 50 |
+
proxies = []
|
| 51 |
+
|
| 52 |
+
# Source 1: proxylist.geonode.com
|
| 53 |
+
try:
|
| 54 |
+
url = f"https://proxylist.geonode.com/api/proxy-list?limit={limit}&page=1&sort_by=lastChecked&sort_type=desc&protocols=http%2Chttps"
|
| 55 |
+
async with aiohttp.ClientSession() as session:
|
| 56 |
+
async with session.get(url, timeout=10) as response:
|
| 57 |
+
if response.status == 200:
|
| 58 |
+
data = await response.json()
|
| 59 |
+
for item in data.get('data', []):
|
| 60 |
+
proxy = Proxy(
|
| 61 |
+
ip=item['ip'],
|
| 62 |
+
port=int(item['port']),
|
| 63 |
+
country=item.get('country', 'Unknown'),
|
| 64 |
+
protocol=item.get('protocols', ['http'])[0] if isinstance(item.get('protocols'), list) else 'http'
|
| 65 |
+
)
|
| 66 |
+
proxies.append(proxy)
|
| 67 |
+
logger.info(f"Fetched proxy: {proxy}")
|
| 68 |
+
except Exception as e:
|
| 69 |
+
logger.warning(f"Failed to fetch from geonode: {e}")
|
| 70 |
+
|
| 71 |
+
# Source 2: proxy-list.download
|
| 72 |
+
try:
|
| 73 |
+
url = "https://www.proxy-list.download/api/v1/get?type=http"
|
| 74 |
+
async with aiohttp.ClientSession() as session:
|
| 75 |
+
async with session.get(url, timeout=10) as response:
|
| 76 |
+
if response.status == 200:
|
| 77 |
+
text = await response.text()
|
| 78 |
+
lines = text.strip().split('\n')
|
| 79 |
+
for line in lines[:limit]:
|
| 80 |
+
if ':' in line:
|
| 81 |
+
ip, port = line.strip().split(':')
|
| 82 |
+
proxy = Proxy(
|
| 83 |
+
ip=ip,
|
| 84 |
+
port=int(port),
|
| 85 |
+
country='Unknown',
|
| 86 |
+
protocol='http'
|
| 87 |
+
)
|
| 88 |
+
if proxy not in proxies:
|
| 89 |
+
proxies.append(proxy)
|
| 90 |
+
except Exception as e:
|
| 91 |
+
logger.warning(f"Failed to fetch from proxy-list: {e}")
|
| 92 |
+
|
| 93 |
+
# Source 3: free-proxy-list.net (simple API)
|
| 94 |
+
try:
|
| 95 |
+
# This is a fallback - scrape a few common free proxies
|
| 96 |
+
fallback_proxies = [
|
| 97 |
+
("20.235.104.105", 3128, "US"),
|
| 98 |
+
("159.89.49.172", 3128, "US"),
|
| 99 |
+
("20.210.113.32", 8123, "US"),
|
| 100 |
+
("103.152.232.142", 8080, "ID"),
|
| 101 |
+
("43.135.166.179", 8080, "SG"),
|
| 102 |
+
("47.74.152.190", 8888, "JP"),
|
| 103 |
+
("52.196.1.179", 8080, "JP"),
|
| 104 |
+
("13.231.21.152", 3128, "JP"),
|
| 105 |
+
("54.179.34.32", 3128, "SG"),
|
| 106 |
+
("18.141.176.104", 3128, "SG"),
|
| 107 |
+
]
|
| 108 |
+
|
| 109 |
+
for ip, port, country in fallback_proxies:
|
| 110 |
+
proxy = Proxy(ip=ip, port=port, country=country, protocol='http')
|
| 111 |
+
if proxy not in proxies:
|
| 112 |
+
proxies.append(proxy)
|
| 113 |
+
|
| 114 |
+
except Exception as e:
|
| 115 |
+
logger.warning(f"Fallback proxy error: {e}")
|
| 116 |
+
|
| 117 |
+
logger.info(f"Total proxies fetched: {len(proxies)}")
|
| 118 |
+
return proxies
|
| 119 |
+
|
| 120 |
+
async def test_proxy(self, proxy: Proxy, test_url: str = "https://httpbin.org/ip") -> bool:
|
| 121 |
+
"""Test if a proxy is working."""
|
| 122 |
+
try:
|
| 123 |
+
timeout = aiohttp.ClientTimeout(total=10)
|
| 124 |
+
|
| 125 |
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
| 126 |
+
start_time = asyncio.get_event_loop().time()
|
| 127 |
+
|
| 128 |
+
async with session.get(
|
| 129 |
+
test_url,
|
| 130 |
+
proxy=f"http://{proxy.ip}:{proxy.port}",
|
| 131 |
+
ssl=False
|
| 132 |
+
) as response:
|
| 133 |
+
elapsed = asyncio.get_event_loop().time() - start_time
|
| 134 |
+
|
| 135 |
+
if response.status == 200:
|
| 136 |
+
proxy.is_working = True
|
| 137 |
+
proxy.response_time = elapsed
|
| 138 |
+
proxy.last_tested = datetime.now()
|
| 139 |
+
logger.info(f"β
Proxy working: {proxy} ({elapsed:.2f}s)")
|
| 140 |
+
return True
|
| 141 |
+
else:
|
| 142 |
+
proxy.is_working = False
|
| 143 |
+
logger.warning(f"β Proxy failed with status {response.status}: {proxy}")
|
| 144 |
+
return False
|
| 145 |
+
|
| 146 |
+
except Exception as e:
|
| 147 |
+
proxy.is_working = False
|
| 148 |
+
logger.warning(f"β Proxy test failed: {proxy} - {str(e)}")
|
| 149 |
+
return False
|
| 150 |
+
|
| 151 |
+
async def get_working_proxy(self, max_attempts: int = 5) -> Optional[Proxy]:
|
| 152 |
+
"""Get a working proxy, testing multiple if needed."""
|
| 153 |
+
# First, try to use current proxy if it exists and is working
|
| 154 |
+
if self.current_proxy and self.current_proxy.is_working:
|
| 155 |
+
# Retest it to make sure it's still working
|
| 156 |
+
if await self.test_proxy(self.current_proxy):
|
| 157 |
+
return self.current_proxy
|
| 158 |
+
|
| 159 |
+
# Fetch new proxies if we don't have enough
|
| 160 |
+
if len(self.proxies) < 5:
|
| 161 |
+
logger.info("Fetching new proxies...")
|
| 162 |
+
self.proxies = await self.fetch_proxies(limit=30)
|
| 163 |
+
|
| 164 |
+
# Test proxies until we find a working one
|
| 165 |
+
random.shuffle(self.proxies) # Randomize to distribute load
|
| 166 |
+
|
| 167 |
+
for i, proxy in enumerate(self.proxies[:max_attempts]):
|
| 168 |
+
logger.info(f"Testing proxy {i+1}/{max_attempts}: {proxy}")
|
| 169 |
+
|
| 170 |
+
if await self.test_proxy(proxy):
|
| 171 |
+
# Save current to history
|
| 172 |
+
if self.current_proxy:
|
| 173 |
+
self.proxy_history.insert(0, self.current_proxy)
|
| 174 |
+
if len(self.proxy_history) > self.max_history:
|
| 175 |
+
self.proxy_history.pop()
|
| 176 |
+
|
| 177 |
+
self.current_proxy = proxy
|
| 178 |
+
return proxy
|
| 179 |
+
|
| 180 |
+
logger.error("No working proxy found!")
|
| 181 |
+
return None
|
| 182 |
+
|
| 183 |
+
async def rotate_proxy(self) -> Optional[Proxy]:
|
| 184 |
+
"""Rotate to a new working proxy."""
|
| 185 |
+
logger.info("Rotating to new proxy...")
|
| 186 |
+
|
| 187 |
+
# Mark current as not working
|
| 188 |
+
if self.current_proxy:
|
| 189 |
+
self.current_proxy.is_working = False
|
| 190 |
+
|
| 191 |
+
# Get a new working proxy
|
| 192 |
+
return await self.get_working_proxy()
|
| 193 |
+
|
| 194 |
+
def get_current_proxy(self) -> Optional[Proxy]:
|
| 195 |
+
"""Get the current active proxy."""
|
| 196 |
+
return self.current_proxy
|
| 197 |
+
|
| 198 |
+
def get_proxy_stats(self) -> Dict:
|
| 199 |
+
"""Get statistics about proxies."""
|
| 200 |
+
working = sum(1 for p in self.proxies if p.is_working)
|
| 201 |
+
return {
|
| 202 |
+
"total_proxies": len(self.proxies),
|
| 203 |
+
"working_proxies": working,
|
| 204 |
+
"current_proxy": str(self.current_proxy) if self.current_proxy else None,
|
| 205 |
+
"proxy_history": [str(p) for p in self.proxy_history[:5]]
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
# Global proxy manager instance
|
| 210 |
+
_proxy_manager: Optional[FreeProxyManager] = None
|
| 211 |
+
|
| 212 |
+
def get_proxy_manager() -> FreeProxyManager:
|
| 213 |
+
"""Get the global proxy manager instance."""
|
| 214 |
+
global _proxy_manager
|
| 215 |
+
if _proxy_manager is None:
|
| 216 |
+
_proxy_manager = FreeProxyManager()
|
| 217 |
+
return _proxy_manager
|
|
@@ -1150,8 +1150,166 @@
|
|
| 1150 |
</div>
|
| 1151 |
<div id="portal-response" style="display: none; margin-top: 15px; padding: 15px; background: var(--surface); border-radius: 6px; border: 1px solid var(--border); white-space: pre-wrap; font-family: monospace; font-size: 13px; max-height: 200px; overflow-y: auto;"></div>
|
| 1152 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1153 |
</div>
|
| 1154 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1155 |
</body>
|
| 1156 |
|
| 1157 |
</html>
|
|
|
|
| 1150 |
</div>
|
| 1151 |
<div id="portal-response" style="display: none; margin-top: 15px; padding: 15px; background: var(--surface); border-radius: 6px; border: 1px solid var(--border); white-space: pre-wrap; font-family: monospace; font-size: 13px; max-height: 200px; overflow-y: auto;"></div>
|
| 1152 |
</div>
|
| 1153 |
+
|
| 1154 |
+
<!-- π Proxy Rotation Controls -->
|
| 1155 |
+
<div style="background: linear-gradient(135deg, rgba(139, 92, 246, 0.1), rgba(59, 130, 246, 0.1)); border: 2px solid var(--accent); border-radius: 8px; padding: 15px; margin-top: 20px;">
|
| 1156 |
+
<h4 style="color: var(--accent); margin-bottom: 10px; display: flex; align-items: center;">
|
| 1157 |
+
π Proxy Rotation for ChatGPT & Copilot
|
| 1158 |
+
<span id="proxy-status-badge" style="font-size: 11px; background: var(--surface); padding: 2px 8px; border-radius: 4px; margin-left: 10px; color: var(--text-muted);">No Proxy</span>
|
| 1159 |
+
</h4>
|
| 1160 |
+
<p style="color: var(--text-muted); font-size: 12px; margin-bottom: 12px;">
|
| 1161 |
+
Rotate IPs to bypass restrictions. Free proxies from multiple countries.
|
| 1162 |
+
</p>
|
| 1163 |
+
|
| 1164 |
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px; margin-bottom: 12px;">
|
| 1165 |
+
<button onclick="fetchNewProxies()" style="background: var(--accent); color: white; border: none; padding: 10px; border-radius: 6px; font-weight: 600; cursor: pointer; font-size: 12px;">
|
| 1166 |
+
π Fetch New IPs
|
| 1167 |
+
</button>
|
| 1168 |
+
<button onclick="rotateProxy()" style="background: var(--warning); color: black; border: none; padding: 10px; border-radius: 6px; font-weight: 600; cursor: pointer; font-size: 12px;">
|
| 1169 |
+
π² Rotate IP
|
| 1170 |
+
</button>
|
| 1171 |
+
<button onclick="testProxy()" style="background: var(--success); color: white; border: none; padding: 10px; border-radius: 6px; font-weight: 600; cursor: pointer; font-size: 12px;">
|
| 1172 |
+
β
Test Current IP
|
| 1173 |
+
</button>
|
| 1174 |
+
<button onclick="restartWithProxy()" style="background: var(--surface); color: var(--text); border: 1px solid var(--border); padding: 10px; border-radius: 6px; font-weight: 600; cursor: pointer; font-size: 12px;">
|
| 1175 |
+
π Restart with Proxy
|
| 1176 |
+
</button>
|
| 1177 |
+
</div>
|
| 1178 |
+
|
| 1179 |
+
<div id="proxy-info" style="background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 10px; font-family: monospace; font-size: 12px; display: none;">
|
| 1180 |
+
<div><strong>Current IP:</strong> <span id="current-proxy-display">None</span></div>
|
| 1181 |
+
<div><strong>Country:</strong> <span id="proxy-country-display">-</span></div>
|
| 1182 |
+
<div><strong>Response Time:</strong> <span id="proxy-response-display">-</span></div>
|
| 1183 |
+
</div>
|
| 1184 |
+
|
| 1185 |
+
<div id="proxy-message" style="margin-top: 8px; font-size: 12px; color: var(--text-muted);"></div>
|
| 1186 |
+
</div>
|
| 1187 |
</div>
|
| 1188 |
</div>
|
| 1189 |
+
|
| 1190 |
+
<script>
|
| 1191 |
+
// π Proxy Management Functions
|
| 1192 |
+
async function fetchNewProxies() {
|
| 1193 |
+
const btn = event.target;
|
| 1194 |
+
const originalText = btn.textContent;
|
| 1195 |
+
btn.textContent = 'Fetching...';
|
| 1196 |
+
btn.disabled = true;
|
| 1197 |
+
|
| 1198 |
+
try {
|
| 1199 |
+
const res = await fetch('/qaz/proxy/fetch', { method: 'POST' });
|
| 1200 |
+
const data = await res.json();
|
| 1201 |
+
|
| 1202 |
+
if (data.status === 'success') {
|
| 1203 |
+
updateProxyDisplay(data.working_proxy, data.stats);
|
| 1204 |
+
showProxyMessage(`β
Fetched ${data.stats.total_proxies} proxies. Working: ${data.stats.working_proxies}`);
|
| 1205 |
+
} else {
|
| 1206 |
+
throw new Error(data.message);
|
| 1207 |
+
}
|
| 1208 |
+
} catch (e) {
|
| 1209 |
+
showProxyMessage('β Error: ' + e.message);
|
| 1210 |
+
} finally {
|
| 1211 |
+
btn.textContent = originalText;
|
| 1212 |
+
btn.disabled = false;
|
| 1213 |
+
}
|
| 1214 |
+
}
|
| 1215 |
+
|
| 1216 |
+
async function rotateProxy() {
|
| 1217 |
+
const btn = event.target;
|
| 1218 |
+
const originalText = btn.textContent;
|
| 1219 |
+
btn.textContent = 'Rotating...';
|
| 1220 |
+
btn.disabled = true;
|
| 1221 |
+
|
| 1222 |
+
try {
|
| 1223 |
+
const res = await fetch('/qaz/proxy/rotate', { method: 'POST' });
|
| 1224 |
+
const data = await res.json();
|
| 1225 |
+
|
| 1226 |
+
if (data.status === 'success') {
|
| 1227 |
+
document.getElementById('proxy-status-badge').textContent = 'Active';
|
| 1228 |
+
document.getElementById('proxy-status-badge').style.color = 'var(--success)';
|
| 1229 |
+
document.getElementById('current-proxy-display').textContent = data.proxy;
|
| 1230 |
+
document.getElementById('proxy-country-display').textContent = data.country;
|
| 1231 |
+
document.getElementById('proxy-response-display').textContent = data.response_time;
|
| 1232 |
+
document.getElementById('proxy-info').style.display = 'block';
|
| 1233 |
+
showProxyMessage(`β
Rotated to new IP: ${data.country} (${data.response_time})`);
|
| 1234 |
+
} else {
|
| 1235 |
+
throw new Error(data.message);
|
| 1236 |
+
}
|
| 1237 |
+
} catch (e) {
|
| 1238 |
+
showProxyMessage('β Error: ' + e.message);
|
| 1239 |
+
} finally {
|
| 1240 |
+
btn.textContent = originalText;
|
| 1241 |
+
btn.disabled = false;
|
| 1242 |
+
}
|
| 1243 |
+
}
|
| 1244 |
+
|
| 1245 |
+
async function testProxy() {
|
| 1246 |
+
const btn = event.target;
|
| 1247 |
+
const originalText = btn.textContent;
|
| 1248 |
+
btn.textContent = 'Testing...';
|
| 1249 |
+
btn.disabled = true;
|
| 1250 |
+
|
| 1251 |
+
try {
|
| 1252 |
+
const res = await fetch('/qaz/proxy/test', { method: 'POST' });
|
| 1253 |
+
const data = await res.json();
|
| 1254 |
+
|
| 1255 |
+
if (data.is_working) {
|
| 1256 |
+
showProxyMessage(`β
Proxy working! Response time: ${data.response_time}`);
|
| 1257 |
+
} else {
|
| 1258 |
+
showProxyMessage('β Proxy not working. Try rotating.');
|
| 1259 |
+
}
|
| 1260 |
+
} catch (e) {
|
| 1261 |
+
showProxyMessage('β Error: ' + e.message);
|
| 1262 |
+
} finally {
|
| 1263 |
+
btn.textContent = originalText;
|
| 1264 |
+
btn.disabled = false;
|
| 1265 |
+
}
|
| 1266 |
+
}
|
| 1267 |
+
|
| 1268 |
+
async function restartWithProxy() {
|
| 1269 |
+
if (!currentProvider) {
|
| 1270 |
+
alert('Please select a provider first');
|
| 1271 |
+
return;
|
| 1272 |
+
}
|
| 1273 |
+
|
| 1274 |
+
const btn = event.target;
|
| 1275 |
+
const originalText = btn.textContent;
|
| 1276 |
+
btn.textContent = 'Restarting...';
|
| 1277 |
+
btn.disabled = true;
|
| 1278 |
+
|
| 1279 |
+
try {
|
| 1280 |
+
const res = await fetch(`/qaz/portal/${currentProvider}/restart-with-proxy`, { method: 'POST' });
|
| 1281 |
+
const data = await res.json();
|
| 1282 |
+
|
| 1283 |
+
if (data.status === 'success') {
|
| 1284 |
+
showProxyMessage(`β
${data.message}`);
|
| 1285 |
+
setTimeout(() => takePortalScreenshot(), 3000);
|
| 1286 |
+
} else {
|
| 1287 |
+
throw new Error(data.message);
|
| 1288 |
+
}
|
| 1289 |
+
} catch (e) {
|
| 1290 |
+
showProxyMessage('β Error: ' + e.message);
|
| 1291 |
+
} finally {
|
| 1292 |
+
btn.textContent = originalText;
|
| 1293 |
+
btn.disabled = false;
|
| 1294 |
+
}
|
| 1295 |
+
}
|
| 1296 |
+
|
| 1297 |
+
function updateProxyDisplay(proxy, stats) {
|
| 1298 |
+
if (proxy) {
|
| 1299 |
+
document.getElementById('current-proxy-display').textContent = proxy;
|
| 1300 |
+
document.getElementById('proxy-info').style.display = 'block';
|
| 1301 |
+
}
|
| 1302 |
+
}
|
| 1303 |
+
|
| 1304 |
+
function showProxyMessage(msg) {
|
| 1305 |
+
const el = document.getElementById('proxy-message');
|
| 1306 |
+
el.textContent = msg;
|
| 1307 |
+
el.style.color = msg.startsWith('β
') ? 'var(--success)' : 'var(--error)';
|
| 1308 |
+
setTimeout(() => {
|
| 1309 |
+
el.textContent = '';
|
| 1310 |
+
}, 5000);
|
| 1311 |
+
}
|
| 1312 |
+
</script>
|
| 1313 |
</body>
|
| 1314 |
|
| 1315 |
</html>
|