codex-console / src /core /dynamic_proxy.py
cjovs's picture
Deploy codex-console to HF Space
7482820 verified
"""
动态代理获取模块
支持通过外部 API 获取动态代理 URL
"""
import logging
import re
from typing import Optional
logger = logging.getLogger(__name__)
def fetch_dynamic_proxy(api_url: str, api_key: str = "", api_key_header: str = "X-API-Key", result_field: str = "") -> Optional[str]:
"""
从代理 API 获取代理 URL
Args:
api_url: 代理 API 地址,响应应为代理 URL 字符串或含代理 URL 的 JSON
api_key: API 密钥(可选)
api_key_header: API 密钥请求头名称
result_field: 从 JSON 响应中提取代理 URL 的字段路径,支持点号分隔(如 "data.proxy"),留空则使用响应原文
Returns:
代理 URL 字符串(如 http://user:pass@host:port),失败返回 None
"""
try:
from curl_cffi import requests as cffi_requests
headers = {}
if api_key:
headers[api_key_header] = api_key
response = cffi_requests.get(
api_url,
headers=headers,
timeout=10,
impersonate="chrome110"
)
if response.status_code != 200:
logger.warning(f"动态代理 API 返回错误状态码: {response.status_code}")
return None
text = response.text.strip()
# 尝试解析 JSON
if result_field or text.startswith("{") or text.startswith("["):
try:
import json
data = json.loads(text)
if result_field:
# 按点号路径逐层提取
for key in result_field.split("."):
if isinstance(data, dict):
data = data.get(key)
elif isinstance(data, list) and key.isdigit():
data = data[int(key)]
else:
data = None
if data is None:
break
proxy_url = str(data).strip() if data is not None else None
else:
# 无指定字段,尝试常见键名
for key in ("proxy", "url", "proxy_url", "data", "ip"):
val = data.get(key) if isinstance(data, dict) else None
if val:
proxy_url = str(val).strip()
break
else:
proxy_url = text
except (ValueError, AttributeError):
proxy_url = text
else:
proxy_url = text
if not proxy_url:
logger.warning("动态代理 API 返回空代理 URL")
return None
# 若未包含协议头,默认加 http://
if not re.match(r'^(http|socks5)://', proxy_url):
proxy_url = "http://" + proxy_url
logger.info(f"动态代理获取成功: {proxy_url[:40]}..." if len(proxy_url) > 40 else f"动态代理获取成功: {proxy_url}")
return proxy_url
except Exception as e:
logger.error(f"获取动态代理失败: {e}")
return None
def get_proxy_url_for_task() -> Optional[str]:
"""
为注册任务获取代理 URL。
优先使用动态代理(若启用),否则使用静态代理配置。
Returns:
代理 URL 或 None
"""
from ..config.settings import get_settings
settings = get_settings()
# 优先使用动态代理
if settings.proxy_dynamic_enabled and settings.proxy_dynamic_api_url:
api_key = settings.proxy_dynamic_api_key.get_secret_value() if settings.proxy_dynamic_api_key else ""
proxy_url = fetch_dynamic_proxy(
api_url=settings.proxy_dynamic_api_url,
api_key=api_key,
api_key_header=settings.proxy_dynamic_api_key_header,
result_field=settings.proxy_dynamic_result_field,
)
if proxy_url:
return proxy_url
logger.warning("动态代理获取失败,回退到静态代理")
# 使用静态代理
return settings.proxy_url