onesvm-browser-server/bench/proxy/lib.py
chii 983259836d chore: init workspace with onesvm-dev-md + casa-commander
docs: 联网搜索服务架构方案全套(plan-final/design-arch/选型决策/整合导览/MCP文档/部署预设/联调手册)
bench: 5 方案 + 代理 + 站点矩阵本机实测工程(无密钥)
部署目标:primary mgr1 先行测试(待批准后执行)
2026-09-01 15:19:52 +08:00

148 lines
4.9 KiB
Python
Executable file

#!/usr/bin/env python3
"""Sanitized Clash-sub helpers. Never print or persist credentials."""
from __future__ import annotations
import collections
import json
import os
import re
import urllib.parse
from typing import Any
INFO_NAME_RE = re.compile(r"^(剩余流量|套餐到期|到期|流量)")
REGION_RULES: list[tuple[str, list[str]]] = [
("香港", ["香港", "HK", "🇭🇰"]),
("台湾", ["台湾", "臺灣", "🇹🇼"]),
("日本", ["日本", "东京", "大阪", "🇯🇵"]),
("韩国", ["韩国", "韓國", "首尔", "🇰🇷"]),
("新加坡", ["新加坡", "狮城", "🇸🇬"]),
("美国", ["美国", "美國", "圣何塞", "洛杉矶", "硅谷", "西雅图", "芝加哥", "纽约", "🇺🇸"]),
("英国", ["英国", "英國", "伦敦", "🇬🇧"]),
("德国", ["德国", "德國", "法兰克福", "🇩🇪"]),
("法国", ["法国", "法國", "巴黎", "🇫🇷"]),
("加拿大", ["加拿大", "🇨🇦"]),
("澳大利亚", ["澳大利亚", "澳洲", "🇦🇺"]),
("澳门", ["澳门", "澳門", "🇲🇴"]),
("中国", ["中国", "回国"]),
]
def infer_region(name: str) -> str:
for label, keys in REGION_RULES:
for key in keys:
if key in name:
return label
return "其他/未知"
def is_info_node(name: str) -> bool:
return bool(INFO_NAME_RE.search(name))
def parse_flow_proxies(text: str) -> list[dict[str, str]]:
proxies: list[dict[str, str]] = []
for line in text.splitlines():
if "name:" not in line or "type:" not in line or "server:" not in line:
continue
name_m = re.search(r"name:\s*'([^']+)'|name:\s*([^,}]+)", line)
type_m = re.search(r"type:\s*([a-zA-Z0-9-]+)", line)
if not name_m or not type_m:
continue
name = (name_m.group(1) or name_m.group(2)).strip()
proxies.append(
{
"name": name,
"type": type_m.group(1),
"region": infer_region(name),
}
)
return proxies
def detect_format(text: str) -> str:
head = text.lstrip()[:400]
if "proxies:" in text[:4000] and (
"mixed-port:" in head or "port:" in head or head.startswith("proxies:")
):
return "clash-yaml"
if any(head.startswith(s) for s in ("ss://", "vmess://", "trojan://", "vless://")):
return "uri-list"
compact = re.sub(r"\s+", "", text)
if compact and re.fullmatch(r"[A-Za-z0-9+/=_-]+", compact[:80] or ""):
return "base64-maybe"
return "other"
def rewrite_runtime_config(src: str, mixed_port: int, controller: str) -> str:
"""Rewrite listen ports / controller; strip original rules to MATCH selector."""
lines = src.splitlines()
out: list[str] = []
skipping_rules = False
selector = "🚀节点选择"
for line in lines:
if line.startswith("proxy-groups:"):
skipping_rules = False
if line.startswith("rules:"):
skipping_rules = True
continue
if skipping_rules:
continue
if line.startswith("mixed-port:"):
out.append(f"mixed-port: {mixed_port}")
continue
if line.startswith("allow-lan:"):
out.append("allow-lan: true")
continue
if line.startswith("external-controller:"):
out.append(f"external-controller: '{controller}'")
continue
if line.startswith("bind-address:"):
out.append("bind-address: '*'")
continue
out.append(line)
out.append("rules:")
out.append(f" - MATCH,{selector}")
return "\n".join(out) + "\n"
def mask_secret(value: str, keep: int = 4) -> str:
if not value:
return ""
return value[:keep] + "***"
def summarize_subscription(text: str) -> dict[str, Any]:
fmt = detect_format(text)
proxies = parse_flow_proxies(text)
real = [p for p in proxies if not is_info_node(p["name"])]
info = [p for p in proxies if is_info_node(p["name"])]
return {
"format": fmt,
"proxy_count_raw": len(proxies),
"proxy_count_real": len(real),
"info_nodes": [p["name"] for p in info],
"types": dict(collections.Counter(p["type"] for p in real)),
"regions": dict(collections.Counter(p["region"] for p in real)),
"proxies": real,
}
def api_quote(name: str) -> str:
return urllib.parse.quote(name, safe="")
def require_sub_url() -> str:
url = os.environ.get("PROXY_SUB_URL", "").strip()
if not url:
raise SystemExit("PROXY_SUB_URL is required (do not hardcode the URL)")
if not url.startswith(("http://", "https://")):
raise SystemExit("PROXY_SUB_URL must be an http(s) URL")
return url
def dump_json(path: str, obj: Any) -> None:
with open(path, "w", encoding="utf-8") as fh:
json.dump(obj, fh, ensure_ascii=False, indent=2)
fh.write("\n")