|
@@ -61,7 +61,9 @@ def load_env_file():
|
|
|
# 只在环境变量未设置时加载
|
|
# 只在环境变量未设置时加载
|
|
|
if key not in os.environ:
|
|
if key not in os.environ:
|
|
|
os.environ[key] = value
|
|
os.environ[key] = value
|
|
|
- print(f"[Config] Loaded: {key}=***" if 'PASSWORD' in key or 'SECRET' in key else f"[Config] Loaded: {key}={value}")
|
|
|
|
|
|
|
+ safe_key = key.upper()
|
|
|
|
|
+ is_sensitive = any(p in safe_key for p in ['PASSWORD', 'SECRET', 'TOKEN', 'KEY', 'ENCRYPT'])
|
|
|
|
|
+ print(f"[Config] Loaded: {key}=***" if is_sensitive else f"[Config] Loaded: {key}={value}")
|
|
|
else:
|
|
else:
|
|
|
print(f"[Config] .env file not found: {env_path}")
|
|
print(f"[Config] .env file not found: {env_path}")
|
|
|
|
|
|
|
@@ -137,7 +139,9 @@ def _test_proxy_connectivity(test_url: str, host: str, port: int, username: str
|
|
|
proxies = {"http": proxy_meta, "https": proxy_meta}
|
|
proxies = {"http": proxy_meta, "https": proxy_meta}
|
|
|
start = int(round(time.time() * 1000))
|
|
start = int(round(time.time() * 1000))
|
|
|
try:
|
|
try:
|
|
|
- resp = requests.get(test_url, proxies=proxies, timeout=timeout)
|
|
|
|
|
|
|
+ session = requests.Session()
|
|
|
|
|
+ session.trust_env = False
|
|
|
|
|
+ resp = session.get(test_url, proxies=proxies, timeout=timeout)
|
|
|
_ = resp.text
|
|
_ = resp.text
|
|
|
cost = int(round(time.time() * 1000)) - start
|
|
cost = int(round(time.time() * 1000)) - start
|
|
|
print(f"[Proxy] test ok: {_mask_ip_port(host + ':' + str(port))} cost={cost}ms", flush=True)
|
|
print(f"[Proxy] test ok: {_mask_ip_port(host + ':' + str(port))} cost={cost}ms", flush=True)
|
|
@@ -147,47 +151,83 @@ def _test_proxy_connectivity(test_url: str, host: str, port: int, username: str
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+_PROXY_CACHE_TTL_SECONDS = 20 * 60
|
|
|
|
|
+_resolved_proxy_cache = {}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def _resolve_shenlong_proxy(proxy_payload: dict) -> dict:
|
|
def _resolve_shenlong_proxy(proxy_payload: dict) -> dict:
|
|
|
test_url = 'http://myip.ipip.net'
|
|
test_url = 'http://myip.ipip.net'
|
|
|
city = str(proxy_payload.get('city') or '').strip()
|
|
city = str(proxy_payload.get('city') or '').strip()
|
|
|
region_code = str(proxy_payload.get('regionCode') or '').strip()
|
|
region_code = str(proxy_payload.get('regionCode') or '').strip()
|
|
|
api_url = str(proxy_payload.get('apiUrl') or '').strip()
|
|
api_url = str(proxy_payload.get('apiUrl') or '').strip()
|
|
|
product_key = str(proxy_payload.get('productKey') or '').strip()
|
|
product_key = str(proxy_payload.get('productKey') or '').strip()
|
|
|
|
|
+ signature = str(proxy_payload.get('signature') or '').strip()
|
|
|
|
|
+ isp = str(proxy_payload.get('isp') or '').strip()
|
|
|
|
|
+ publish_task_id = str(proxy_payload.get('publish_task_id') or '').strip()
|
|
|
|
|
+
|
|
|
|
|
+ if not product_key:
|
|
|
|
|
+ raise Exception('缺少神龙产品Key')
|
|
|
|
|
+ if not signature:
|
|
|
|
|
+ raise Exception('缺少神龙签名')
|
|
|
|
|
+
|
|
|
|
|
+ if region_code and region_code.isdigit() and len(region_code) == 6:
|
|
|
|
|
+ if region_code.endswith('0000'):
|
|
|
|
|
+ region_code = ''
|
|
|
|
|
+ elif not region_code.endswith('00'):
|
|
|
|
|
+ region_code = region_code[:4] + '00'
|
|
|
|
|
+
|
|
|
|
|
+ cache_key = ''
|
|
|
|
|
+ if publish_task_id:
|
|
|
|
|
+ cache_key = f"publish_task:{publish_task_id}:area:{region_code or '-'}:isp:{isp or '-'}"
|
|
|
|
|
+ now = int(time.time())
|
|
|
|
|
+ cached = _resolved_proxy_cache.get(cache_key)
|
|
|
|
|
+ if isinstance(cached, dict) and cached.get('expire_at', 0) > now and cached.get('server'):
|
|
|
|
|
+ server = str(cached.get('server') or '').strip()
|
|
|
|
|
+ if server:
|
|
|
|
|
+ print(f"[Proxy] cache hit: task={publish_task_id} area={region_code or '-'} isp={isp or '-'}", flush=True)
|
|
|
|
|
+ return {'server': server}
|
|
|
|
|
+
|
|
|
|
|
+ request_url = api_url or 'http://api.shenlongip.com/ip'
|
|
|
|
|
+ params = {
|
|
|
|
|
+ 'key': product_key,
|
|
|
|
|
+ 'sign': signature,
|
|
|
|
|
+ 'count': 1,
|
|
|
|
|
+ 'pattern': 'json',
|
|
|
|
|
+ 'mr': 1,
|
|
|
|
|
+ }
|
|
|
|
|
+ if region_code:
|
|
|
|
|
+ params['area'] = region_code
|
|
|
|
|
+ if isp:
|
|
|
|
|
+ params['isp'] = isp
|
|
|
|
|
|
|
|
- request_url = api_url
|
|
|
|
|
- params = {}
|
|
|
|
|
-
|
|
|
|
|
- if request_url:
|
|
|
|
|
- if city:
|
|
|
|
|
- params['city'] = city
|
|
|
|
|
- else:
|
|
|
|
|
- if not product_key:
|
|
|
|
|
- raise Exception('缺少神龙产品Key')
|
|
|
|
|
- request_url = 'https://api.shenlongip.com/getip'
|
|
|
|
|
- params = {
|
|
|
|
|
- 'key': product_key,
|
|
|
|
|
- 'count': 1,
|
|
|
|
|
- 'protocol': 'http',
|
|
|
|
|
- }
|
|
|
|
|
- if city:
|
|
|
|
|
- params['city'] = city
|
|
|
|
|
- if region_code:
|
|
|
|
|
- params['city_code'] = region_code
|
|
|
|
|
- params['region_code'] = region_code
|
|
|
|
|
-
|
|
|
|
|
- resp = requests.get(request_url, params=params, timeout=15)
|
|
|
|
|
- if resp.status_code >= 400:
|
|
|
|
|
- raise Exception(f"代理提取失败: HTTP {resp.status_code}")
|
|
|
|
|
-
|
|
|
|
|
|
|
+ payload = None
|
|
|
|
|
+ session = requests.Session()
|
|
|
|
|
+ session.trust_env = False
|
|
|
|
|
+ resp = session.get(
|
|
|
|
|
+ request_url,
|
|
|
|
|
+ params=params,
|
|
|
|
|
+ headers={
|
|
|
|
|
+ 'User-Agent': 'Mozilla/5.0',
|
|
|
|
|
+ 'Accept': 'application/json',
|
|
|
|
|
+ },
|
|
|
|
|
+ timeout=15,
|
|
|
|
|
+ )
|
|
|
content_type = (resp.headers.get('content-type') or '').lower()
|
|
content_type = (resp.headers.get('content-type') or '').lower()
|
|
|
raw_text = resp.text or ''
|
|
raw_text = resp.text or ''
|
|
|
-
|
|
|
|
|
- payload = None
|
|
|
|
|
- if 'application/json' in content_type or raw_text.strip().startswith('{') or raw_text.strip().startswith('['):
|
|
|
|
|
- try:
|
|
|
|
|
|
|
+ try:
|
|
|
|
|
+ if 'application/json' in content_type or raw_text.strip().startswith('{') or raw_text.strip().startswith('['):
|
|
|
payload = resp.json()
|
|
payload = resp.json()
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ payload = None
|
|
|
|
|
+ if isinstance(payload, dict) and payload.get('code') is not None:
|
|
|
|
|
+ try:
|
|
|
|
|
+ api_code = int(payload.get('code'))
|
|
|
except Exception:
|
|
except Exception:
|
|
|
- payload = None
|
|
|
|
|
|
|
+ api_code = -1
|
|
|
|
|
+ if api_code != 200:
|
|
|
|
|
+ raise Exception(f"代理提取失败: code={api_code} msg={str(payload.get('msg') or '').strip() or 'unknown'}")
|
|
|
|
|
+ elif resp.status_code >= 400:
|
|
|
|
|
+ raise Exception(f"代理提取失败: HTTP {resp.status_code}")
|
|
|
|
|
|
|
|
def collect_ip_ports(data_list, city_filter: str):
|
|
def collect_ip_ports(data_list, city_filter: str):
|
|
|
ips = []
|
|
ips = []
|
|
@@ -215,19 +255,13 @@ def _resolve_shenlong_proxy(proxy_payload: dict) -> dict:
|
|
|
if payload is not None:
|
|
if payload is not None:
|
|
|
if isinstance(payload, dict):
|
|
if isinstance(payload, dict):
|
|
|
if isinstance(payload.get('data'), list):
|
|
if isinstance(payload.get('data'), list):
|
|
|
- ip_ports = collect_ip_ports(payload.get('data'), city)
|
|
|
|
|
- if city and not ip_ports:
|
|
|
|
|
- ip_ports = collect_ip_ports(payload.get('data'), '')
|
|
|
|
|
|
|
+ ip_ports = collect_ip_ports(payload.get('data'), '')
|
|
|
elif isinstance(payload.get('list'), list):
|
|
elif isinstance(payload.get('list'), list):
|
|
|
- ip_ports = collect_ip_ports(payload.get('list'), city)
|
|
|
|
|
- if city and not ip_ports:
|
|
|
|
|
- ip_ports = collect_ip_ports(payload.get('list'), '')
|
|
|
|
|
|
|
+ ip_ports = collect_ip_ports(payload.get('list'), '')
|
|
|
elif payload.get('ip') and payload.get('port'):
|
|
elif payload.get('ip') and payload.get('port'):
|
|
|
- ip_ports = collect_ip_ports([payload], city)
|
|
|
|
|
|
|
+ ip_ports = collect_ip_ports([payload], '')
|
|
|
elif isinstance(payload, list):
|
|
elif isinstance(payload, list):
|
|
|
- ip_ports = collect_ip_ports(payload, city)
|
|
|
|
|
- if city and not ip_ports:
|
|
|
|
|
- ip_ports = collect_ip_ports(payload, '')
|
|
|
|
|
|
|
+ ip_ports = collect_ip_ports(payload, '')
|
|
|
else:
|
|
else:
|
|
|
ip_ports = _extract_ip_ports(raw_text)
|
|
ip_ports = _extract_ip_ports(raw_text)
|
|
|
|
|
|
|
@@ -237,7 +271,7 @@ def _resolve_shenlong_proxy(proxy_payload: dict) -> dict:
|
|
|
random.shuffle(ip_ports)
|
|
random.shuffle(ip_ports)
|
|
|
candidates = ip_ports[: min(10, len(ip_ports))]
|
|
candidates = ip_ports[: min(10, len(ip_ports))]
|
|
|
|
|
|
|
|
- print(f"[Proxy] shenlong resolved: city={city or '-'} regionCode={region_code or '-'} candidates={len(candidates)}/{len(ip_ports)}", flush=True)
|
|
|
|
|
|
|
+ print(f"[Proxy] shenlong resolved: city={city or '-'} area={region_code or '-'} candidates={len(candidates)}/{len(ip_ports)}", flush=True)
|
|
|
|
|
|
|
|
for ip_port in candidates:
|
|
for ip_port in candidates:
|
|
|
try:
|
|
try:
|
|
@@ -247,9 +281,14 @@ def _resolve_shenlong_proxy(proxy_payload: dict) -> dict:
|
|
|
continue
|
|
continue
|
|
|
|
|
|
|
|
if _test_proxy_connectivity(test_url, host, port, timeout=10):
|
|
if _test_proxy_connectivity(test_url, host, port, timeout=10):
|
|
|
- return {
|
|
|
|
|
- 'server': f"http://{host}:{port}",
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ server = f"http://{host}:{port}"
|
|
|
|
|
+ if cache_key:
|
|
|
|
|
+ _resolved_proxy_cache[cache_key] = {
|
|
|
|
|
+ 'server': server,
|
|
|
|
|
+ 'expire_at': int(time.time()) + _PROXY_CACHE_TTL_SECONDS,
|
|
|
|
|
+ }
|
|
|
|
|
+ print(f"[Proxy] cache set: task={publish_task_id} ttl={_PROXY_CACHE_TTL_SECONDS}s", flush=True)
|
|
|
|
|
+ return {'server': server}
|
|
|
|
|
|
|
|
raise Exception('未找到可用代理IP')
|
|
raise Exception('未找到可用代理IP')
|
|
|
|
|
|
|
@@ -281,14 +320,15 @@ CORS(app)
|
|
|
|
|
|
|
|
# 配置日志以显示所有 HTTP 请求
|
|
# 配置日志以显示所有 HTTP 请求
|
|
|
import logging
|
|
import logging
|
|
|
-logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
|
|
|
+logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
# 让 werkzeug 日志显示
|
|
# 让 werkzeug 日志显示
|
|
|
werkzeug_logger = logging.getLogger('werkzeug')
|
|
werkzeug_logger = logging.getLogger('werkzeug')
|
|
|
werkzeug_logger.setLevel(logging.INFO)
|
|
werkzeug_logger.setLevel(logging.INFO)
|
|
|
# 添加 StreamHandler 确保输出到控制台
|
|
# 添加 StreamHandler 确保输出到控制台
|
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
|
-handler.setLevel(logging.DEBUG)
|
|
|
|
|
|
|
+handler.setLevel(logging.INFO)
|
|
|
werkzeug_logger.addHandler(handler)
|
|
werkzeug_logger.addHandler(handler)
|
|
|
|
|
+logging.getLogger('urllib3').setLevel(logging.WARNING)
|
|
|
|
|
|
|
|
# 添加请求钩子,打印所有收到的请求
|
|
# 添加请求钩子,打印所有收到的请求
|
|
|
@app.before_request
|
|
@app.before_request
|
|
@@ -513,7 +553,10 @@ def publish_video():
|
|
|
if isinstance(proxy_payload, dict) and proxy_payload.get('enabled'):
|
|
if isinstance(proxy_payload, dict) and proxy_payload.get('enabled'):
|
|
|
provider = str(proxy_payload.get('provider') or 'shenlong').strip().lower()
|
|
provider = str(proxy_payload.get('provider') or 'shenlong').strip().lower()
|
|
|
if provider == 'shenlong':
|
|
if provider == 'shenlong':
|
|
|
- publisher.proxy_config = _resolve_shenlong_proxy(proxy_payload)
|
|
|
|
|
|
|
+ proxy_payload_with_task = dict(proxy_payload)
|
|
|
|
|
+ if data.get('publish_task_id') is not None:
|
|
|
|
|
+ proxy_payload_with_task['publish_task_id'] = data.get('publish_task_id')
|
|
|
|
|
+ publisher.proxy_config = _resolve_shenlong_proxy(proxy_payload_with_task)
|
|
|
|
|
|
|
|
# 执行发布
|
|
# 执行发布
|
|
|
result = asyncio.run(publisher.run(cookie_str, params))
|
|
result = asyncio.run(publisher.run(cookie_str, params))
|
|
@@ -643,7 +686,10 @@ def publish_ai_assisted():
|
|
|
if isinstance(proxy_payload, dict) and proxy_payload.get('enabled'):
|
|
if isinstance(proxy_payload, dict) and proxy_payload.get('enabled'):
|
|
|
provider = str(proxy_payload.get('provider') or 'shenlong').strip().lower()
|
|
provider = str(proxy_payload.get('provider') or 'shenlong').strip().lower()
|
|
|
if provider == 'shenlong':
|
|
if provider == 'shenlong':
|
|
|
- publisher.proxy_config = _resolve_shenlong_proxy(proxy_payload)
|
|
|
|
|
|
|
+ proxy_payload_with_task = dict(proxy_payload)
|
|
|
|
|
+ if data.get('publish_task_id') is not None:
|
|
|
|
|
+ proxy_payload_with_task['publish_task_id'] = data.get('publish_task_id')
|
|
|
|
|
+ publisher.proxy_config = _resolve_shenlong_proxy(proxy_payload_with_task)
|
|
|
try:
|
|
try:
|
|
|
publisher.user_id = int(data.get("user_id")) if data.get("user_id") is not None else None
|
|
publisher.user_id = int(data.get("user_id")) if data.get("user_id") is not None else None
|
|
|
except Exception:
|
|
except Exception:
|
|
@@ -1499,8 +1545,7 @@ def main():
|
|
|
print(f"启动服务: http://{args.host}:{args.port}")
|
|
print(f"启动服务: http://{args.host}:{args.port}")
|
|
|
print("=" * 60)
|
|
print("=" * 60)
|
|
|
|
|
|
|
|
- # 启用 debug 模式以获取详细日志,使用 use_reloader=False 避免重复启动
|
|
|
|
|
- app.run(host=args.host, port=args.port, debug=True, threaded=True, use_reloader=False)
|
|
|
|
|
|
|
+ app.run(host=args.host, port=args.port, debug=bool(args.debug), threaded=True, use_reloader=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/auto-reply', methods=['POST'])
|
|
@app.route('/auto-reply', methods=['POST'])
|