cursorpro commited on
Commit
050a103
·
verified ·
1 Parent(s): bfe2592

Upload 7 files

Browse files
Files changed (7) hide show
  1. main.py +41 -0
  2. proxy_manager.py +136 -0
  3. proxy_service.log +0 -0
  4. requirements.txt +7 -0
  5. templates/index.html +229 -0
  6. updater.py +61 -0
  7. web_server.py +42 -0
main.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import logging
3
+ import uvicorn
4
+ from contextlib import asynccontextmanager
5
+ from web_server import app, init_app
6
+ from proxy_manager import ProxyManager
7
+ from updater import ProxyUpdater
8
+
9
+ # Configure Logging
10
+ logging.basicConfig(
11
+ level=logging.INFO,
12
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
13
+ handlers=[
14
+ logging.StreamHandler(),
15
+ logging.FileHandler("proxy_service.log")
16
+ ]
17
+ )
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # Initialize components
21
+ pm = ProxyManager()
22
+ updater = ProxyUpdater(pm)
23
+
24
+ # Inject dependencies into web app
25
+ init_app(pm, updater)
26
+
27
+ @app.on_event("startup")
28
+ async def startup_event():
29
+ """Start the updater loop when the application starts."""
30
+ logger.info("Application starting...")
31
+ asyncio.create_task(updater.run_loop())
32
+
33
+ @app.on_event("shutdown")
34
+ async def shutdown_event():
35
+ """Clean up resources on shutdown."""
36
+ logger.info("Application shutting down...")
37
+ updater.stop()
38
+
39
+ if __name__ == "__main__":
40
+ logger.info("Starting Proxy Rotation Service...")
41
+ uvicorn.run(app, host="0.0.0.0", port=7860)
proxy_manager.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import aiohttp
2
+ import asyncio
3
+ import time
4
+ import logging
5
+ from typing import List, Optional, Dict
6
+ from dataclasses import dataclass, asdict
7
+
8
+ # Configure logging
9
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
10
+ logger = logging.getLogger(__name__)
11
+
12
+ @dataclass
13
+ class Proxy:
14
+ url: str
15
+ protocol: str
16
+ ip: str
17
+ port: str
18
+ latency: float = float('inf')
19
+
20
+ def to_dict(self):
21
+ return asdict(self)
22
+
23
+ class ProxyManager:
24
+ def __init__(self):
25
+ self.proxies: List[Proxy] = [] # This will hold ONLY working proxies
26
+ self.best_proxy: Optional[Proxy] = None
27
+ self.test_target = "http://www.google.com"
28
+ self.timeout = 5 # seconds
29
+ self.concurrency_limit = 200 # Higher concurrency
30
+ self._lock = asyncio.Lock() # Lock for thread-safe updates to self.proxies
31
+
32
+ async def fetch_json_proxies(self) -> List[Proxy]:
33
+ """Fetch proxies from proxies.free JSON source."""
34
+ url = f"https://proxies.free/data/proxies.json?t={int(time.time())}"
35
+ fetched = []
36
+ try:
37
+ async with aiohttp.ClientSession() as session:
38
+ async with session.get(url) as response:
39
+ if response.status == 200:
40
+ data = await response.json()
41
+ for p in data.get('proxies', []):
42
+ # Filter for http/https/socks5 only
43
+ protocol = p.get('protocol', '').lower()
44
+ if protocol in ['http', 'https', 'socks5']:
45
+ proxy_url = f"{protocol}://{p['ip']}:{p['port']}"
46
+ fetched.append(Proxy(
47
+ url=proxy_url,
48
+ protocol=protocol,
49
+ ip=p['ip'],
50
+ port=p['port']
51
+ ))
52
+ except Exception as e:
53
+ logger.error(f"Error fetching JSON proxies: {e}")
54
+ return fetched
55
+
56
+ async def fetch_txt_proxies(self) -> List[Proxy]:
57
+ """Fetch proxies from proxifly TXT source."""
58
+ url = "https://cdn.jsdelivr.net/gh/proxifly/free-proxy-list@main/proxies/all/data.txt"
59
+ fetched = []
60
+ try:
61
+ async with aiohttp.ClientSession() as session:
62
+ async with session.get(url) as response:
63
+ if response.status == 200:
64
+ text = await response.text()
65
+ for line in text.splitlines():
66
+ line = line.strip()
67
+ if not line:
68
+ continue
69
+ try:
70
+ protocol, rest = line.split('://')
71
+ ip, port = rest.split(':')
72
+ if protocol.lower() in ['http', 'https', 'socks5']:
73
+ fetched.append(Proxy(
74
+ url=line,
75
+ protocol=protocol.lower(),
76
+ ip=ip,
77
+ port=port
78
+ ))
79
+ except ValueError:
80
+ continue
81
+ except Exception as e:
82
+ logger.error(f"Error fetching TXT proxies: {e}")
83
+ return fetched
84
+
85
+ async def check_and_add_proxy(self, proxy: Proxy, semaphore: asyncio.Semaphore):
86
+ """Test a proxy and add it to the valid list immediately if successful."""
87
+ async with semaphore:
88
+ start_time = time.time()
89
+ try:
90
+ # Use a new session for each check to avoid connection pool limits/issues with proxies
91
+ timeout = aiohttp.ClientTimeout(total=self.timeout)
92
+ async with aiohttp.ClientSession(timeout=timeout) as session:
93
+ async with session.get(self.test_target, proxy=proxy.url) as response:
94
+ if response.status == 200:
95
+ proxy.latency = (time.time() - start_time) * 1000 # ms
96
+
97
+ # Add to working list immediately
98
+ async with self._lock:
99
+ self.proxies.append(proxy)
100
+ self.proxies.sort(key=lambda x: x.latency)
101
+ self.best_proxy = self.proxies[0]
102
+
103
+ except Exception:
104
+ pass # Check failed, just ignore
105
+
106
+ async def refresh_proxies(self) -> List[Proxy]:
107
+ """Fetch and test all proxies."""
108
+ logger.info("Fetching proxies...")
109
+ p1 = await self.fetch_json_proxies()
110
+ p2 = await self.fetch_txt_proxies()
111
+
112
+ # Deduplicate
113
+ seen = set()
114
+ unique_proxies = []
115
+ for p in p1 + p2:
116
+ if p.url not in seen:
117
+ seen.add(p.url)
118
+ unique_proxies.append(p)
119
+
120
+ logger.info(f"Testing {len(unique_proxies)} proxies with concurrency {self.concurrency_limit}...")
121
+
122
+ # Reset current list before refresh?
123
+ # Ideally, we keep old ones until new ones replace, but to keep it simple and stateless:
124
+ # We will clear it. UI might flicker but updates will come fast.
125
+ async with self._lock:
126
+ self.proxies = []
127
+ self.best_proxy = None
128
+
129
+ semaphore = asyncio.Semaphore(self.concurrency_limit)
130
+ tasks = [self.check_and_add_proxy(p, semaphore) for p in unique_proxies]
131
+
132
+ # Run all checks
133
+ await asyncio.gather(*tasks)
134
+
135
+ logger.info(f"Refresh complete. Found {len(self.proxies)} working proxies.")
136
+ return self.proxies
proxy_service.log ADDED
File without changes
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ aiohttp
4
+ aiohttp-socks
5
+ requests
6
+ schedule
7
+ jinja2
templates/index.html ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Proxy Rotation Service</title>
8
+ <style>
9
+ :root {
10
+ --bg-color: #0f172a;
11
+ --card-bg: #1e293b;
12
+ --text-primary: #f1f5f9;
13
+ --text-secondary: #94a3b8;
14
+ --accent: #3b82f6;
15
+ --success: #22c55e;
16
+ --error: #ef4444;
17
+ }
18
+
19
+ body {
20
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
21
+ background-color: var(--bg-color);
22
+ color: var(--text-primary);
23
+ margin: 0;
24
+ padding: 2rem;
25
+ line-height: 1.6;
26
+ }
27
+
28
+ .container {
29
+ max-width: 1200px;
30
+ margin: 0 auto;
31
+ }
32
+
33
+ header {
34
+ margin-bottom: 2rem;
35
+ border-bottom: 1px solid #334155;
36
+ padding-bottom: 1rem;
37
+ }
38
+
39
+ h1 {
40
+ font-weight: 300;
41
+ font-size: 2.5rem;
42
+ margin: 0;
43
+ }
44
+
45
+ .status-card {
46
+ background-color: var(--card-bg);
47
+ border-radius: 12px;
48
+ padding: 2rem;
49
+ margin-bottom: 2rem;
50
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
51
+ display: flex;
52
+ justify-content: space-between;
53
+ align-items: center;
54
+ }
55
+
56
+ .status-item {
57
+ text-align: center;
58
+ }
59
+
60
+ .status-label {
61
+ color: var(--text-secondary);
62
+ font-size: 0.9rem;
63
+ margin-bottom: 0.5rem;
64
+ }
65
+
66
+ .status-value {
67
+ font-size: 1.5rem;
68
+ font-weight: bold;
69
+ }
70
+
71
+ .status-value.highlight {
72
+ color: var(--accent);
73
+ }
74
+
75
+ .proxy-list {
76
+ background-color: var(--card-bg);
77
+ border-radius: 12px;
78
+ overflow: hidden;
79
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
80
+ }
81
+
82
+ table {
83
+ width: 100%;
84
+ border-collapse: collapse;
85
+ }
86
+
87
+ th,
88
+ td {
89
+ padding: 1rem;
90
+ text-align: left;
91
+ border-bottom: 1px solid #334155;
92
+ }
93
+
94
+ th {
95
+ background-color: #334155;
96
+ color: var(--text-primary);
97
+ font-weight: 600;
98
+ }
99
+
100
+ tr:hover {
101
+ background-color: #273548;
102
+ }
103
+
104
+ .latency-good {
105
+ color: var(--success);
106
+ }
107
+
108
+ .latency-bad {
109
+ color: var(--error);
110
+ }
111
+
112
+ .loading {
113
+ text-align: center;
114
+ padding: 2rem;
115
+ color: var(--text-secondary);
116
+ }
117
+ </style>
118
+ </head>
119
+
120
+ <body>
121
+ <div class="container">
122
+ <header>
123
+ <div style="display: flex; justify-content: space-between; align-items: center;">
124
+ <h1>Proxy Rotation Service</h1>
125
+ <div style="font-size: 0.8rem; color: var(--text-secondary);">Auto-refreshing every 2s</div>
126
+ </div>
127
+ </header>
128
+
129
+ <!-- Current Status -->
130
+ <div class="status-card">
131
+ <div class="status-item">
132
+ <div class="status-label">Current Best Proxy</div>
133
+ <div class="status-value highlight" id="best-proxy-url">Searching...</div>
134
+ </div>
135
+ <div class="status-item">
136
+ <div class="status-label">Protocol</div>
137
+ <div class="status-value" id="best-proxy-protocol">-</div>
138
+ </div>
139
+ <div class="status-item">
140
+ <div class="status-label">Latency</div>
141
+ <div class="status-value" id="best-proxy-latency">-</div>
142
+ </div>
143
+ <div class="status-item">
144
+ <div class="status-label">Total Active Proxies</div>
145
+ <div class="status-value" id="total-proxies">0</div>
146
+ </div>
147
+ </div>
148
+
149
+ <!-- Proxy List -->
150
+ <div class="proxy-list">
151
+ <table>
152
+ <thead>
153
+ <tr>
154
+ <th>Proxy URL</th>
155
+ <th>Protocol</th>
156
+ <th>IP</th>
157
+ <th>Port</th>
158
+ <th>Latency (ms)</th>
159
+ </tr>
160
+ </thead>
161
+ <tbody id="proxy-table-body">
162
+ <!-- Populated by JS -->
163
+ </tbody>
164
+ </table>
165
+ <div id="loading-msg" class="loading">Waiting for results...</div>
166
+ </div>
167
+ </div>
168
+
169
+ <script>
170
+ async def updateDashboard() {
171
+ try {
172
+ // Fetch Status
173
+ const statusRes = await fetch('/api/status');
174
+ const statusData = await statusRes.json();
175
+
176
+ const best = statusData.best_proxy;
177
+ document.getElementById('total-proxies').innerText = statusData.total_available;
178
+
179
+ if (best) {
180
+ document.getElementById('best-proxy-url').innerText = best.url;
181
+ document.getElementById('best-proxy-protocol').innerText = best.protocol.toUpperCase();
182
+
183
+ const latency = best.latency.toFixed(2);
184
+ const latEl = document.getElementById('best-proxy-latency');
185
+ latEl.innerText = latency + ' ms';
186
+ latEl.className = 'status-value ' + (best.latency < 200 ? 'latency-good' : 'latency-bad');
187
+ } else {
188
+ document.getElementById('best-proxy-url').innerText = "Searching...";
189
+ document.getElementById('best-proxy-protocol').innerText = "-";
190
+ document.getElementById('best-proxy-latency').innerText = "-";
191
+ }
192
+
193
+ // Fetch Proxy List
194
+ const listRes = await fetch('/api/proxies');
195
+ const listData = await listRes.json();
196
+
197
+ const tbody = document.getElementById('proxy-table-body');
198
+ tbody.innerHTML = '';
199
+
200
+ if (listData.proxies.length > 0) {
201
+ document.getElementById('loading-msg').style.display = 'none';
202
+ listData.proxies.forEach(p => {
203
+ const tr = document.createElement('tr');
204
+ const latClass = p.latency < 200 ? 'latency-good' : 'latency-bad';
205
+ tr.innerHTML = `
206
+ <td>${p.url}</td>
207
+ <td>${p.protocol}</td>
208
+ <td>${p.ip}</td>
209
+ <td>${p.port}</td>
210
+ <td class="${latClass}">${p.latency.toFixed(2)}</td>
211
+ `;
212
+ tbody.appendChild(tr);
213
+ });
214
+ }
215
+
216
+ } catch (e) {
217
+ console.error("Error updating dashboard:", e);
218
+ }
219
+ }
220
+
221
+ // Initial call
222
+ updateDashboard();
223
+
224
+ // Poll every 2 seconds
225
+ setInterval(updateDashboard, 2000);
226
+ </script>
227
+ </body>
228
+
229
+ </html>
updater.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import logging
3
+ import urllib.parse
4
+ import aiohttp
5
+ from proxy_manager import ProxyManager, Proxy
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ class ProxyUpdater:
10
+ def __init__(self, proxy_manager: ProxyManager):
11
+ self.pm = proxy_manager
12
+ self.update_url_template = "https://sasasas.zeabur.app/api/set/proxy/{}"
13
+ self.running = False
14
+
15
+ async def update_remote(self, proxy: Proxy):
16
+ """Update the remote instance with the new proxy."""
17
+ # URL encode the proxy URL
18
+ encoded_proxy = urllib.parse.quote(proxy.url, safe='')
19
+ target_url = self.update_url_template.format(encoded_proxy)
20
+
21
+ logger.info(f"Updating remote instance with proxy: {proxy.url}")
22
+ logger.info(f"Target URL: {target_url}")
23
+
24
+ try:
25
+ async with aiohttp.ClientSession() as session:
26
+ async with session.get(target_url) as response:
27
+ text = await response.text()
28
+ if response.status == 200:
29
+ logger.info(f"Successfully updated remote proxy. Response: {text}")
30
+ else:
31
+ logger.error(f"Failed to update remote proxy. Status: {response.status}, Response: {text}")
32
+ except Exception as e:
33
+ logger.error(f"Exception during remote update: {e}")
34
+
35
+ async def run_loop(self):
36
+ """Main loop ensuring proxy is updated every 5 minutes."""
37
+ self.running = True
38
+ while self.running:
39
+ logger.info("Starting scheduled proxy update...")
40
+
41
+ # 1. Refresh and find best proxy
42
+ proxies = await self.pm.refresh_proxies()
43
+
44
+ if self.pm.best_proxy:
45
+ # 2. Test one last time before updating (as requested)
46
+ logger.info(f"Verifying best proxy {self.pm.best_proxy.url} before update...")
47
+ await self.pm.check_proxy(self.pm.best_proxy)
48
+
49
+ if self.pm.best_proxy.latency != float('inf'):
50
+ # 3. Update remote
51
+ await self.update_remote(self.pm.best_proxy)
52
+ else:
53
+ logger.warning("Best proxy failed verification. Retrying in next cycle.")
54
+ else:
55
+ logger.warning("No valid proxies found to update.")
56
+
57
+ logger.info("Sleeping for 5 minutes...")
58
+ await asyncio.sleep(300) # 5 minutes
59
+
60
+ def stop(self):
61
+ self.running = False
web_server.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request
2
+ from fastapi.templating import Jinja2Templates
3
+ from fastapi.staticfiles import StaticFiles
4
+ from fastapi.responses import HTMLResponse
5
+ import uvicorn
6
+ from proxy_manager import ProxyManager
7
+ from updater import ProxyUpdater
8
+ import logging
9
+
10
+ app = FastAPI()
11
+ templates = Jinja2Templates(directory="templates")
12
+
13
+ # These will be injected from main.py
14
+ proxy_manager: ProxyManager = None
15
+ updater: ProxyUpdater = None
16
+
17
+ def init_app(pm: ProxyManager, up: ProxyUpdater):
18
+ global proxy_manager, updater
19
+ proxy_manager = pm
20
+ updater = up
21
+
22
+ @app.get("/", response_class=HTMLResponse)
23
+ async def read_root(request: Request):
24
+ return templates.TemplateResponse("index.html", {"request": request})
25
+
26
+ @app.get("/api/status")
27
+ async def get_status():
28
+ if not proxy_manager:
29
+ return {"best_proxy": None, "total_available": 0}
30
+
31
+ best = proxy_manager.best_proxy.to_dict() if proxy_manager.best_proxy else None
32
+ return {
33
+ "best_proxy": best,
34
+ "total_available": len(proxy_manager.proxies)
35
+ }
36
+
37
+ @app.get("/api/proxies")
38
+ async def get_proxies():
39
+ if not proxy_manager:
40
+ return {"proxies": []}
41
+ # Return top 50 proxies
42
+ return {"proxies": [p.to_dict() for p in proxy_manager.proxies[:50]]}