feat: Реализован новый веб-интерфейс и бэкенд для управления VPN-клиентом, включая списки серверов, элементы управления прокси и опции конфигурации.
This commit is contained in:
176
web/app/vless.py
Normal file
176
web/app/vless.py
Normal file
@@ -0,0 +1,176 @@
|
||||
from urllib.parse import unquote
|
||||
import json
|
||||
import urllib.request
|
||||
from .config import PROXY_PORT, DATA_DIR, CONFIG_FILE
|
||||
|
||||
def parse_vless_url(url: str) -> dict:
|
||||
"""Parse VLESS URL and extract connection parameters"""
|
||||
if not url.startswith("vless://"):
|
||||
raise ValueError("URL must start with vless://")
|
||||
|
||||
# Remove scheme
|
||||
url_no_scheme = url[8:]
|
||||
|
||||
# Split by fragment (#tag)
|
||||
if '#' in url_no_scheme:
|
||||
url_part, tag = url_no_scheme.split('#', 1)
|
||||
tag = unquote(tag)
|
||||
else:
|
||||
url_part = url_no_scheme
|
||||
tag = "reality"
|
||||
|
||||
# Split by query (?)
|
||||
if '?' in url_part:
|
||||
uuid_host_port, query_string = url_part.split('?', 1)
|
||||
else:
|
||||
raise ValueError("Missing query parameters")
|
||||
|
||||
# Parse UUID@host:port
|
||||
if '@' not in uuid_host_port:
|
||||
raise ValueError("Missing @ separator")
|
||||
|
||||
uuid_str, host_port = uuid_host_port.split('@', 1)
|
||||
|
||||
if ':' not in host_port:
|
||||
raise ValueError("Missing port")
|
||||
|
||||
host, port_str = host_port.rsplit(':', 1)
|
||||
port = int(port_str)
|
||||
|
||||
# Parse query parameters
|
||||
params = {}
|
||||
for param in query_string.split('&'):
|
||||
if '=' in param:
|
||||
key, value = param.split('=', 1)
|
||||
params[key] = unquote(value)
|
||||
|
||||
# Extract required parameters
|
||||
pbk = params.get('pbk', '')
|
||||
sid = params.get('sid', '')
|
||||
sni = params.get('sni', host)
|
||||
fp = params.get('fp', 'chrome')
|
||||
flow = params.get('flow', '')
|
||||
|
||||
if not pbk or not sid:
|
||||
raise ValueError("Missing required parameters: pbk or sid")
|
||||
|
||||
return {
|
||||
'uuid': uuid_str,
|
||||
'server': host,
|
||||
'server_port': port,
|
||||
'tag': tag,
|
||||
'public_key': pbk,
|
||||
'short_id': sid,
|
||||
'server_name': sni,
|
||||
'fingerprint': fp,
|
||||
'flow': flow
|
||||
}
|
||||
|
||||
|
||||
def generate_vless_config(vless_params: dict) -> dict:
|
||||
"""Generate sing-box configuration from VLESS parameters"""
|
||||
config = {
|
||||
"dns": {
|
||||
"independent_cache": True
|
||||
},
|
||||
"log": {
|
||||
"level": "debug",
|
||||
"disabled": True,
|
||||
"timestamp": True
|
||||
},
|
||||
"route": {
|
||||
"final": vless_params['tag'],
|
||||
"auto_detect_interface": True
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"tag": "mixed-in",
|
||||
"type": "mixed",
|
||||
"sniff": True,
|
||||
"users": [],
|
||||
"listen": "0.0.0.0",
|
||||
"listen_port": PROXY_PORT,
|
||||
"set_system_proxy": False
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": vless_params['tag'],
|
||||
"server": vless_params['server'],
|
||||
"server_port": vless_params['server_port'],
|
||||
"flow": vless_params['flow'],
|
||||
"tls": {
|
||||
"enabled": True,
|
||||
"server_name": vless_params['server_name'],
|
||||
"reality": {
|
||||
"enabled": True,
|
||||
"public_key": vless_params['public_key'],
|
||||
"short_id": vless_params['short_id']
|
||||
},
|
||||
"utls": {
|
||||
"enabled": True,
|
||||
"fingerprint": vless_params['fingerprint']
|
||||
}
|
||||
},
|
||||
"uuid": vless_params['uuid']
|
||||
},
|
||||
{
|
||||
"tag": "direct",
|
||||
"type": "direct"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def generate_direct_config() -> bool:
|
||||
"""Generate a direct connection config (bypass all proxies)"""
|
||||
try:
|
||||
config = {
|
||||
"dns": {
|
||||
"independent_cache": True
|
||||
},
|
||||
"log": {
|
||||
"level": "debug",
|
||||
"disabled": True,
|
||||
"timestamp": True
|
||||
},
|
||||
"route": {
|
||||
"final": "direct",
|
||||
"auto_detect_interface": True
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"tag": "mixed-in",
|
||||
"type": "mixed",
|
||||
"sniff": True,
|
||||
"users": [],
|
||||
"listen": "0.0.0.0",
|
||||
"listen_port": PROXY_PORT,
|
||||
"set_system_proxy": False
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"tag": "direct",
|
||||
"type": "direct"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
CONFIG_FILE.write_text(json.dumps(config, indent=2, ensure_ascii=False))
|
||||
|
||||
# Reload sing-box
|
||||
try:
|
||||
urllib.request.urlopen("http://127.0.0.1:9090/reload", timeout=3)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[WebUI] Failed to generate direct config: {e}")
|
||||
return False
|
||||
Reference in New Issue
Block a user