mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 21:04:49 +00:00
backend: PROXY_STATIC_LIST support (manual proxies without API token)
- proxy_pool now loads from both PROXY_STATIC_LIST (env, comma-separated) and PROXY6_TOKEN (API) - Static list has priority, merged with API list (dedup by URL) - /api/proxy_status returns masked proxy URLs for diagnostic (passwords hidden) - Supports formats: 'http://user:pass@host:port' or 'host:port' (assumed http://)
This commit is contained in:
parent
c2be5e846f
commit
3ee5275ea0
@ -20,6 +20,7 @@ class Config:
|
|||||||
grace_period_days: int
|
grace_period_days: int
|
||||||
|
|
||||||
proxy6_token: str # пусто = без прокси (прямой HTTP)
|
proxy6_token: str # пусто = без прокси (прямой HTTP)
|
||||||
|
proxy_static_list: str # статический список прокси через запятую: "http://user:pass@host:port,..."
|
||||||
|
|
||||||
|
|
||||||
def _required(name: str) -> str:
|
def _required(name: str) -> str:
|
||||||
@ -42,4 +43,5 @@ def get_config() -> Config:
|
|||||||
active_period_days=int(os.getenv("ACTIVE_PERIOD_DAYS", "90")),
|
active_period_days=int(os.getenv("ACTIVE_PERIOD_DAYS", "90")),
|
||||||
grace_period_days=int(os.getenv("GRACE_PERIOD_DAYS", "14")),
|
grace_period_days=int(os.getenv("GRACE_PERIOD_DAYS", "14")),
|
||||||
proxy6_token=os.getenv("PROXY6_TOKEN", ""),
|
proxy6_token=os.getenv("PROXY6_TOKEN", ""),
|
||||||
|
proxy_static_list=os.getenv("PROXY_STATIC_LIST", ""),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -29,37 +29,52 @@ _pool: list[str] = [] # ["http://user:pass@host:port", ...]
|
|||||||
_pool_loaded_at: float = 0.0
|
_pool_loaded_at: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_static_list(raw: str) -> list[str]:
|
||||||
|
"""Парсит PROXY_STATIC_LIST — строка с прокси через запятую/перевод строки."""
|
||||||
|
if not raw:
|
||||||
|
return []
|
||||||
|
parts = [p.strip() for p in raw.replace("\n", ",").split(",")]
|
||||||
|
proxies = []
|
||||||
|
for p in parts:
|
||||||
|
if not p:
|
||||||
|
continue
|
||||||
|
# Если протокол не указан — добавляем http://
|
||||||
|
if "://" not in p:
|
||||||
|
p = "http://" + p
|
||||||
|
proxies.append(p)
|
||||||
|
return proxies
|
||||||
|
|
||||||
|
|
||||||
def _load_pool(force: bool = False) -> list[str]:
|
def _load_pool(force: bool = False) -> list[str]:
|
||||||
"""Загружает активные прокси из Proxy6 API. Кэшируется на _POOL_TTL_SEC."""
|
"""Загружает прокси: сначала статический список из ENV, потом дополняет из Proxy6 API.
|
||||||
|
Кэшируется на _POOL_TTL_SEC."""
|
||||||
global _pool, _pool_loaded_at
|
global _pool, _pool_loaded_at
|
||||||
with _lock:
|
with _lock:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if not force and _pool and now - _pool_loaded_at < _POOL_TTL_SEC:
|
if not force and _pool and now - _pool_loaded_at < _POOL_TTL_SEC:
|
||||||
return _pool
|
return _pool
|
||||||
|
|
||||||
token = get_config().proxy6_token
|
cfg = get_config()
|
||||||
if not token:
|
proxies: list[str] = []
|
||||||
return _pool # без токена — пустой пул, парсеры пойдут напрямую
|
|
||||||
|
|
||||||
|
# 1) Статический список из ENV (приоритет, для одиночных IP без API)
|
||||||
|
static = _parse_static_list(cfg.proxy_static_list)
|
||||||
|
if static:
|
||||||
|
proxies.extend(static)
|
||||||
|
log.info("Static proxy list: %d entries", len(static))
|
||||||
|
|
||||||
|
# 2) Динамический пул из Proxy6 API (если есть токен)
|
||||||
|
if cfg.proxy6_token:
|
||||||
try:
|
try:
|
||||||
with httpx.Client(timeout=10.0) as client:
|
with httpx.Client(timeout=10.0) as client:
|
||||||
r = client.get(f"{_API_URL}/{token}/getproxy", params={"state": "active"})
|
r = client.get(f"{_API_URL}/{cfg.proxy6_token}/getproxy",
|
||||||
|
params={"state": "active"})
|
||||||
data = r.json()
|
data = r.json()
|
||||||
except Exception as e:
|
if data.get("status") == "yes":
|
||||||
log.warning("Proxy6 API request failed: %s", e)
|
|
||||||
return _pool
|
|
||||||
|
|
||||||
if data.get("status") != "yes":
|
|
||||||
log.warning("Proxy6 returned status=%s, error=%s",
|
|
||||||
data.get("status"), data.get("error"))
|
|
||||||
return _pool
|
|
||||||
|
|
||||||
proxies: list[str] = []
|
|
||||||
for _, p in (data.get("list") or {}).items():
|
for _, p in (data.get("list") or {}).items():
|
||||||
if str(p.get("active")) != "1":
|
if str(p.get("active")) != "1":
|
||||||
continue
|
continue
|
||||||
proto = (p.get("type") or "http").lower()
|
proto = (p.get("type") or "http").lower()
|
||||||
# Proxy6 возвращает 'socks' для SOCKS5
|
|
||||||
if proto == "socks":
|
if proto == "socks":
|
||||||
proto = "socks5"
|
proto = "socks5"
|
||||||
host = p.get("host") or p.get("ip")
|
host = p.get("host") or p.get("ip")
|
||||||
@ -68,11 +83,20 @@ def _load_pool(force: bool = False) -> list[str]:
|
|||||||
pwd = p.get("pass")
|
pwd = p.get("pass")
|
||||||
if not (host and port and user and pwd):
|
if not (host and port and user and pwd):
|
||||||
continue
|
continue
|
||||||
proxies.append(f"{proto}://{user}:{pwd}@{host}:{port}")
|
url = f"{proto}://{user}:{pwd}@{host}:{port}"
|
||||||
|
if url not in proxies:
|
||||||
|
proxies.append(url)
|
||||||
|
log.info("Proxy6 API: total pool now %d proxies", len(proxies))
|
||||||
|
else:
|
||||||
|
log.warning("Proxy6 API returned status=%s error=%s",
|
||||||
|
data.get("status"), data.get("error"))
|
||||||
|
except Exception as e:
|
||||||
|
log.warning("Proxy6 API request failed: %s", e)
|
||||||
|
|
||||||
_pool = proxies
|
_pool = proxies
|
||||||
_pool_loaded_at = now
|
_pool_loaded_at = now
|
||||||
log.info("Proxy6 pool loaded: %d active proxies", len(_pool))
|
if not _pool:
|
||||||
|
log.info("Proxy pool is empty — parsers will use direct HTTP")
|
||||||
return _pool
|
return _pool
|
||||||
|
|
||||||
|
|
||||||
@ -95,8 +119,19 @@ def proxied_client(timeout: float = 15.0, **client_kwargs) -> httpx.Client:
|
|||||||
def pool_status() -> dict:
|
def pool_status() -> dict:
|
||||||
"""Для диагностики — текущее состояние пула."""
|
"""Для диагностики — текущее состояние пула."""
|
||||||
pool = _load_pool()
|
pool = _load_pool()
|
||||||
|
cfg = get_config()
|
||||||
|
# Маскируем пароли в URL для diagnostic
|
||||||
|
masked = []
|
||||||
|
for p in pool:
|
||||||
|
try:
|
||||||
|
import re as _re
|
||||||
|
masked.append(_re.sub(r"://([^:]+):([^@]+)@", r"://\1:***@", p))
|
||||||
|
except Exception:
|
||||||
|
masked.append("***")
|
||||||
return {
|
return {
|
||||||
"count": len(pool),
|
"count": len(pool),
|
||||||
"loaded_age_sec": int(time.time() - _pool_loaded_at) if _pool_loaded_at else None,
|
"loaded_age_sec": int(time.time() - _pool_loaded_at) if _pool_loaded_at else None,
|
||||||
"token_configured": bool(get_config().proxy6_token),
|
"token_configured": bool(cfg.proxy6_token),
|
||||||
|
"static_list_size": len(_parse_static_list(cfg.proxy_static_list)),
|
||||||
|
"proxies": masked,
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user