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