v2: tabs, chain builder, obfuscation modes, hop slider, Defender exclusion, firewall kept
Made-with: Cursor
This commit is contained in:
96
proxy_chain_manager/validator.py
Normal file
96
proxy_chain_manager/validator.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
import httpx
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def validate_proxies(
|
||||
proxy_urls: list[str],
|
||||
check_url: str,
|
||||
concurrency: int,
|
||||
timeout_seconds: float,
|
||||
on_progress: Callable[[int, int], None] | None = None,
|
||||
) -> list[str]:
|
||||
sem = asyncio.Semaphore(max(1, concurrency))
|
||||
ok: list[str] = []
|
||||
lock = asyncio.Lock()
|
||||
total = len(proxy_urls)
|
||||
done = 0
|
||||
|
||||
async def one(u: str) -> None:
|
||||
nonlocal done
|
||||
async with sem:
|
||||
good = await _check_one(u, check_url, timeout_seconds)
|
||||
async with lock:
|
||||
done += 1
|
||||
if good:
|
||||
ok.append(u)
|
||||
if on_progress:
|
||||
on_progress(done, total)
|
||||
|
||||
await asyncio.gather(*(one(u) for u in proxy_urls))
|
||||
return ok
|
||||
|
||||
|
||||
async def _check_one(proxy_url: str, check_url: str, timeout_seconds: float) -> bool:
|
||||
try:
|
||||
t = httpx.Timeout(timeout_seconds, connect=min(8.0, timeout_seconds))
|
||||
async with httpx.AsyncClient(proxy=proxy_url, timeout=t, verify=True, follow_redirects=True) as c:
|
||||
r = await c.get(check_url)
|
||||
return r.status_code == 200 and len(r.content) > 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
async def check_chain_exit_ip(
|
||||
listen_proxy: str, check_url: str, timeout_seconds: float
|
||||
) -> str | None:
|
||||
"""Query the IP-check URL through the local chain proxy.
|
||||
Returns the exit IP string on success, or None on failure."""
|
||||
try:
|
||||
t = httpx.Timeout(timeout_seconds, connect=min(10.0, timeout_seconds))
|
||||
async with httpx.AsyncClient(proxy=listen_proxy, timeout=t, verify=True, follow_redirects=True) as c:
|
||||
r = await c.get(check_url)
|
||||
if r.status_code != 200:
|
||||
return None
|
||||
body = r.text.strip()
|
||||
# ipify returns {"ip":"x.x.x.x"} in json mode
|
||||
if body.startswith("{"):
|
||||
import json
|
||||
try:
|
||||
return json.loads(body).get("ip") or json.loads(body).get("origin")
|
||||
except Exception:
|
||||
return None
|
||||
# plain text mode
|
||||
if body and len(body) < 50:
|
||||
return body
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
async def get_direct_ip(check_url: str, timeout_seconds: float = 15.0) -> str | None:
|
||||
"""Get our own exit IP without the proxy chain (so we can compare)."""
|
||||
try:
|
||||
t = httpx.Timeout(timeout_seconds)
|
||||
async with httpx.AsyncClient(timeout=t, verify=True, follow_redirects=True) as c:
|
||||
r = await c.get(check_url)
|
||||
if r.status_code != 200:
|
||||
return None
|
||||
body = r.text.strip()
|
||||
if body.startswith("{"):
|
||||
import json
|
||||
try:
|
||||
return json.loads(body).get("ip") or json.loads(body).get("origin")
|
||||
except Exception:
|
||||
return None
|
||||
if body and len(body) < 50:
|
||||
return body
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
Reference in New Issue
Block a user