universal-autoreg: initial public release (restored, redacted, hardened)
This commit is contained in:
91
lib/proxies.py
Normal file
91
lib/proxies.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""proxies.py — пул прокси: SQLite (таблица pool) или txt-файл, live-проверка.
|
||||
|
||||
Правила (из практики):
|
||||
- sticky-прокси на ВЕСЬ флоу одного аккаунта (verify/register/login с одного IP, иначе 403 anti-bot)
|
||||
- live-тест перед волной (ThreadPool, ~20с на 250 прокси)
|
||||
- free-пул протухает за часы — перепроверять каждый запуск
|
||||
- panel/new-api банят прокси-IP (403) — панели напрямую, GH через прокси
|
||||
"""
|
||||
import os, random, socket, sqlite3, threading, urllib.request
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
DB = os.environ.get("UA_PROXY_DB", os.path.join(HERE, "..", "data", "proxies.db"))
|
||||
TXT = os.environ.get("UA_PROXY_TXT", os.path.join(HERE, "..", "data", "proxies.txt"))
|
||||
|
||||
|
||||
def load_pool():
|
||||
"""Все живые прокси: сначала sqlite, потом txt. Формат: http://user:pass@host:port"""
|
||||
out = []
|
||||
if os.path.exists(DB):
|
||||
try:
|
||||
con = sqlite3.connect(DB)
|
||||
rows = con.execute("SELECT url FROM pool WHERE alive=1").fetchall()
|
||||
con.close()
|
||||
out += [r[0] for r in rows]
|
||||
except sqlite3.Error:
|
||||
pass
|
||||
if os.path.exists(TXT):
|
||||
out += [l.strip() for l in open(TXT, encoding="utf-8") if l.strip() and not l.startswith("#")]
|
||||
return list(dict.fromkeys(out))
|
||||
|
||||
|
||||
def check_one(url, test_url="https://github.com/login", timeout=10):
|
||||
"""Проверить прокси. ВАЖНО: verify=True — SSL-MITM прокси (verify=False пропускал их)
|
||||
Firefox/Camoufox убивает SEC_ERROR_UNKNOWN_ISSUER."""
|
||||
try:
|
||||
import ssl
|
||||
ctx = ssl.create_default_context()
|
||||
opener = urllib.request.build_opener(
|
||||
urllib.request.ProxyHandler({"http": url, "https": url}),
|
||||
urllib.request.HTTPSHandler(context=ctx))
|
||||
req = urllib.request.Request(test_url, headers={"User-Agent": "Mozilla/5.0"})
|
||||
with opener.open(req, timeout=timeout) as r:
|
||||
return url, r.status in (200, 302)
|
||||
except Exception:
|
||||
return url, False
|
||||
|
||||
|
||||
def live_pool(test_url="https://github.com/login", workers=40, limit=None):
|
||||
pool = load_pool()
|
||||
if limit:
|
||||
pool = pool[:limit]
|
||||
good = []
|
||||
with ThreadPoolExecutor(max_workers=workers) as ex:
|
||||
for url, ok in ex.map(lambda u: check_one(u, test_url), pool):
|
||||
if ok:
|
||||
good.append(url)
|
||||
return good
|
||||
|
||||
|
||||
class StickyRotator:
|
||||
"""Один прокси = один аккаунт (sticky). Авто-ротация при смерти opener'а."""
|
||||
|
||||
def __init__(self, pool=None, test_url="https://github.com/login"):
|
||||
self.pool = list(pool if pool is not None else live_pool(test_url))
|
||||
random.shuffle(self.pool)
|
||||
self._i = 0
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def next(self):
|
||||
with self._lock:
|
||||
if not self.pool:
|
||||
return None
|
||||
p = self.pool[self._i % len(self.pool)]
|
||||
self._i += 1
|
||||
return p
|
||||
|
||||
def ban(self, url):
|
||||
with self._lock:
|
||||
if url in self.pool:
|
||||
self.pool.remove(url)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.pool)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pool = load_pool()
|
||||
print(f"pool total: {len(pool)}")
|
||||
live = live_pool(limit=min(len(pool), 100))
|
||||
print(f"live (first 100 tested): {len(live)}")
|
||||
Reference in New Issue
Block a user