Загрузка данных
import os
import sys
import json
import urllib.parse
import urllib.request
import subprocess
import winreg
import time
import ctypes
import atexit
import signal
import socket
import threading
from concurrent.futures import ThreadPoolExecutor
# --- НАСТРОЙКИ ---
XRAY_EXE = "xray.exe"
KEY_FILE = "key.txt"
CONFIG_FILE = "config.json"
PROXY_HOST = "127.0.0.1"
HTTP_PORT = 10809
SOCKS_PORT = 10808
INTERNET_OPTION_SETTINGS_CHANGED = 39
INTERNET_OPTION_REFRESH = 37
global_process = None
process_lock = threading.Lock()
stop_ping_monitor = threading.Event()
_PROXY_HANDLER = urllib.request.ProxyHandler({
'http': f'http://{PROXY_HOST}:{HTTP_PORT}',
'https': f'http://{PROXY_HOST}:{HTTP_PORT}'
})
_PROXY_OPENER = urllib.request.build_opener(_PROXY_HANDLER)
def clear_console():
""" Очистка экрана консоли """
os.system('cls' if os.name == 'nt' else 'clear')
def refresh_wininet():
""" Уведомление Windows об изменении параметров сети """
try:
wininet = ctypes.windll.Wininet
wininet.InternetSetOptionW(0, INTERNET_OPTION_SETTINGS_CHANGED, 0, 0)
wininet.InternetSetOptionW(0, INTERNET_OPTION_REFRESH, 0, 0)
except Exception as e:
print(f"[!] Ошибка WinAPI: {e}")
def measure_ping(host, port, timeout=1.5):
""" Измерение задержки TCP-соединения (мс) c фильтрацией IPv4 """
sock = None
try:
prev_timeout = socket.getdefaulttimeout()
socket.setdefaulttimeout(timeout)
try:
addr_info = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
finally:
socket.setdefaulttimeout(prev_timeout)
if not addr_info:
return None
target_ip = addr_info[0][4][0]
start_time = time.perf_counter()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect((target_ip, port))
return int((time.perf_counter() - start_time) * 1000)
except Exception:
return None
finally:
if sock:
sock.close()
def check_proxy_health(timeout=3.0):
""" Проверка реального прохождения HTTP-трафика через прокси """
try:
req = urllib.request.Request(
"https://cp.cloudflare.com/generate_204",
headers={'User-Agent': 'Mozilla/5.0'}
)
with _PROXY_OPENER.open(req, timeout=timeout) as response:
return response.status in (200, 204)
except Exception:
return False
def parse_vless_url(url_str, log_level="warning"):
""" Парсинг VLESS ссылки и формирование конфигурации """
parsed = urllib.parse.urlparse(url_str)
if parsed.scheme != "vless":
raise ValueError("Ссылка должна начинаться с vless://")
uuid = parsed.username
host = parsed.hostname
port = parsed.port or 443
params = dict(urllib.parse.parse_qsl(parsed.query))
stream_type = params.get("type", "tcp")
security = params.get("security", "none")
sni = params.get("sni", host)
fp = params.get("fp", "chrome")
pbk = params.get("pbk", "")
sid = params.get("sid", "")
flow = params.get("flow", "")
path = params.get("path", "/")
service_name = params.get("serviceName", "")
alpn_raw = params.get("alpn", "")
spider_x = params.get("spiderx", "/")
header_type = params.get("headerType", "none")
stream_settings = {
"network": stream_type,
"security": security
}
if security == "reality":
reality_settings = {
"serverName": sni,
"fingerprint": fp if fp else "chrome",
"publicKey": pbk,
"spiderX": spider_x
}
if sid:
reality_settings["shortId"] = sid
stream_settings["realitySettings"] = reality_settings
elif security == "tls":
tls_settings = {
"serverName": sni,
"allowInsecure": False,
"fingerprint": fp if fp else "chrome"
}
if alpn_raw:
tls_settings["alpn"] = [a.strip() for a in alpn_raw.split(",")]
stream_settings["tlsSettings"] = tls_settings
if stream_type == "grpc":
stream_settings["grpcSettings"] = {"serviceName": service_name}
elif stream_type in ("ws", "xhttp"):
stream_settings[f"{stream_type}Settings"] = {"path": path}
elif stream_type == "tcp" and header_type == "http":
stream_settings["tcpSettings"] = {
"header": {
"type": "http",
"request": {"path": [path]}
}
}
user_data = {"id": uuid, "encryption": "none"}
if flow:
user_data["flow"] = flow
config = {
"log": {"loglevel": log_level},
"inbounds": [
{"port": HTTP_PORT, "listen": PROXY_HOST, "protocol": "http", "settings": {"timeout": 300}},
{"port": SOCKS_PORT, "listen": PROXY_HOST, "protocol": "socks", "settings": {"udp": True}}
],
"outbounds": [
{
"protocol": "vless",
"settings": {
"vnext": [
{
"address": host,
"port": port,
"users": [user_data]
}
]
},
"streamSettings": stream_settings
}
]
}
return config, host, port
def set_windows_proxy(enable=True):
""" Управление системным прокси Windows """
reg_path = r"Software\Microsoft\Windows\CurrentVersion\Internet Settings"
try:
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, reg_path, 0, winreg.KEY_WRITE) as registry:
if enable:
winreg.SetValueEx(registry, "ProxyEnable", 0, winreg.REG_DWORD, 1)
winreg.SetValueEx(registry, "ProxyServer", 0, winreg.REG_SZ, f"{PROXY_HOST}:{HTTP_PORT}")
winreg.SetValueEx(registry, "ProxyOverride", 0, winreg.REG_SZ, "<local>;127.*;10.*;172.16.*;192.168.*")
print(f"\n[+] Системный прокси Windows: ВКЛЮЧЕН ({PROXY_HOST}:{HTTP_PORT})")
else:
winreg.SetValueEx(registry, "ProxyEnable", 0, winreg.REG_DWORD, 0)
winreg.SetValueEx(registry, "ProxyServer", 0, winreg.REG_SZ, "")
refresh_wininet()
except Exception as e:
print(f"[!] Ошибка реестра Windows: {e}")
def cleanup():
""" Завершение процессов и сброс параметров """
global global_process
stop_ping_monitor.set()
set_windows_proxy(False)
with process_lock:
if global_process and global_process.poll() is None:
global_process.terminate()
try:
global_process.wait(timeout=2)
except subprocess.TimeoutExpired:
global_process.kill()
global_process = None
if os.path.exists(CONFIG_FILE):
for _ in range(5):
try:
os.remove(CONFIG_FILE)
break
except OSError:
time.sleep(0.1)
def _win_ctrl_handler(ctrl_type):
cleanup()
os._exit(0)
def setup_os_handlers():
atexit.register(cleanup)
signal.signal(signal.SIGINT, lambda s, f: sys.exit(0))
signal.signal(signal.SIGTERM, lambda s, f: sys.exit(0))
if sys.platform == "win32":
PHANDLER_ROUTINE = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_ulong)
global _handler_ref
_handler_ref = PHANDLER_ROUTINE(_win_ctrl_handler)
ctypes.windll.kernel32.SetConsoleCtrlHandler(_handler_ref, True)
def get_server_metadata(url_str):
try:
parsed = urllib.parse.urlparse(url_str)
host = parsed.hostname
port = parsed.port or 443
if not host:
return None
name = urllib.parse.unquote(parsed.fragment) if parsed.fragment else f"{host}:{port}"
ping = measure_ping(host, port)
return {
"name": name,
"host": host,
"port": port,
"ping": ping,
"url": url_str
}
except Exception:
return None
def select_server(vless_urls):
while True:
print("\n[*] Измерение задержки серверов...")
servers = []
with ThreadPoolExecutor(max_workers=min(len(vless_urls), 20)) as executor:
results = executor.map(get_server_metadata, vless_urls)
servers = [res for res in results if res]
if not servers:
print("[!] Нет доступных серверов.")
return None
servers.sort(key=lambda x: (x['ping'] is None, x['ping']))
print("\nСписок серверов:")
print("-" * 60)
for idx, srv in enumerate(servers, start=1):
ping_str = f"{srv['ping']} ms" if srv['ping'] is not None else "TIMEOUT"
print(f" [{idx}] {srv['name']:<30} | Пинг: {ping_str}")
print(" [R] Обновить пинг")
print("-" * 60)
choice = input(f"Выберите номер сервера (1-{len(servers)}) или 'R' для обновления: ").strip()
if choice.lower() in ("r", "refresh", "к"):
continue
try:
idx = int(choice) - 1
if 0 <= idx < len(servers):
return servers[idx]['url']
print("[!] Неверный номер.")
except ValueError:
print("[!] Неверный ввод.")
except (KeyboardInterrupt, EOFError):
return None
def live_ping_monitor(host, port, show_logs):
""" Фоновое динамическое обновление пинга активного сервера """
while not stop_ping_monitor.is_set():
ping = measure_ping(host, port)
ping_str = f"{ping} ms" if ping is not None else "TIMEOUT"
if not show_logs:
msg = f"\r[STATUS] {host}:{port} | Пинг: {ping_str:<8} | Enter — выход"
sys.stdout.write(msg.ljust(75))
sys.stdout.flush()
stop_ping_monitor.wait(3.0)
if not show_logs:
sys.stdout.write("\r" + " " * 75 + "\r")
sys.stdout.flush()
def main():
global global_process
setup_os_handlers()
set_windows_proxy(False)
print("=" * 60)
print(" МИКРО VPN-КЛИЕНТ (XRAY CORE)")
print("=" * 60)
if not os.path.exists(XRAY_EXE):
print(f"\n[!] ОШИБКА: Файл '{XRAY_EXE}' не найден!")
input("\nНажмите Enter для выхода...")
return
if not os.path.exists(KEY_FILE):
print(f"\n[!] ОШИБКА: Файл '{KEY_FILE}' не найден!")
input("\nНажмите Enter для выхода...")
return
with open(KEY_FILE, "r", encoding="utf-8") as f:
vless_urls = [line.strip() for line in f if line.strip().startswith("vless://")]
if not vless_urls:
print(f"\n[!] В '{KEY_FILE}' не найдено VLESS-ссылок!")
input("\nНажмите Enter для выхода...")
return
selected_url = select_server(vless_urls)
if not selected_url:
return
show_logs = input("\nПоказывать логи Xray в консоли? (y/n): ").strip().lower() in ("y", "yes", "д", "да")
log_level = "info" if show_logs else "warning"
# Очищаем консоль от списка серверов и вопросов перед запуском
clear_console()
try:
config_data, target_host, target_port = parse_vless_url(selected_url, log_level=log_level)
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(config_data, f, indent=2)
print("[+] Конфигурация сформирована.")
except Exception as e:
print(f"\n[!] Ошибка парсинга ссылки: {e}")
input("\nНажмите Enter для выхода...")
return
try:
print("[*] Запуск Xray-core...")
stdout_dest = None if show_logs else subprocess.DEVNULL
stderr_dest = None if show_logs else subprocess.DEVNULL
with process_lock:
global_process = subprocess.Popen(
[XRAY_EXE, "run", "-c", CONFIG_FILE],
stdout=stdout_dest,
stderr=stderr_dest
)
time.sleep(1.0)
if global_process.poll() is not None:
print("\n[!] ОШИБКА: Xray-core завершил работу сразу после запуска.")
print("[!] Проверьте занятость портов 10808 / 10809.")
input("\nНажмите Enter для выхода...")
return
print("[*] Проверка проходимости трафика через прокси...")
if not check_proxy_health(timeout=3.5):
print("\n[!] ОШИБКА: Трафик не проходит через прокси.")
print("[!] Ключ недействителен или соединение блокируется.")
cleanup()
input("\nНажмите Enter для выхода...")
return
set_windows_proxy(True)
stop_ping_monitor.clear()
ping_thread = threading.Thread(
target=live_ping_monitor,
args=(target_host, target_port, show_logs),
daemon=True
)
ping_thread.start()
if show_logs:
print("\nНажмите Enter для отключения...")
input()
except KeyboardInterrupt:
pass
except Exception as e:
print(f"[!] Ошибка исполнения: {e}")
finally:
print("\n[*] Завершение работы и сброс прокси...")
cleanup()
print("[+] Готово.")
if __name__ == "__main__":
main()