修改目录结构
This commit is contained in:
56
child/utils/__init__.py
Normal file
56
child/utils/__init__.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
EZProxy Utilities Module
|
||||
========================
|
||||
|
||||
工具函数模块,提供各种辅助功能
|
||||
"""
|
||||
|
||||
# 导入工具函数
|
||||
from .config_utils import (
|
||||
init_config,
|
||||
load_config,
|
||||
save_config,
|
||||
parse_vless_url,
|
||||
generate_v2ray_config
|
||||
)
|
||||
|
||||
from .system_utils import (
|
||||
get_architecture,
|
||||
get_v2ray_binary_path,
|
||||
set_system_proxy,
|
||||
get_temp_dir
|
||||
)
|
||||
|
||||
from .network_utils import (
|
||||
download_file,
|
||||
fetch_json,
|
||||
fetch_yaml,
|
||||
check_connectivity,
|
||||
get_public_ip
|
||||
)
|
||||
|
||||
# 定义包的公开接口
|
||||
__all__ = [
|
||||
# 配置工具
|
||||
'init_config',
|
||||
'load_config',
|
||||
'save_config',
|
||||
'parse_vless_url',
|
||||
'generate_v2ray_config',
|
||||
|
||||
# 系统工具
|
||||
'get_architecture',
|
||||
'get_v2ray_binary_path',
|
||||
'set_system_proxy',
|
||||
'get_temp_dir',
|
||||
|
||||
# 网络工具
|
||||
'download_file',
|
||||
'fetch_json',
|
||||
'fetch_yaml',
|
||||
'check_connectivity',
|
||||
'get_public_ip'
|
||||
]
|
||||
|
||||
# 包版本信息
|
||||
__version__ = "1.0.0"
|
||||
161
child/utils/config_utils.py
Normal file
161
child/utils/config_utils.py
Normal file
@@ -0,0 +1,161 @@
|
||||
import os
|
||||
import yaml
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
def init_config(conf_dir):
|
||||
"""初始化配置文件"""
|
||||
config_path = Path(conf_dir) / "config.yaml"
|
||||
|
||||
if not config_path.exists():
|
||||
default_config = {
|
||||
'proxy_url': '',
|
||||
'config_version': '1.0',
|
||||
'app_version': '1.0',
|
||||
'domains': [
|
||||
'google.com',
|
||||
'youtube.com',
|
||||
'facebook.com',
|
||||
'twitter.com',
|
||||
'instagram.com',
|
||||
'netflix.com',
|
||||
'github.com',
|
||||
'gitlab.com',
|
||||
'stackoverflow.com',
|
||||
'reddit.com'
|
||||
],
|
||||
'v2ray_config_path': str(Path(conf_dir) / "v2ray.json"),
|
||||
'v2ray_template_path': str(Path(conf_dir) / "v2ray_template.json"),
|
||||
'v2ray_log_path': str(Path(__file__).parent.parent / "logs" / "v2ray.log")
|
||||
}
|
||||
|
||||
with open(config_path, 'w') as f:
|
||||
yaml.dump(default_config, f)
|
||||
logging.info("Default config file created")
|
||||
|
||||
return load_config()
|
||||
|
||||
def load_config():
|
||||
"""加载配置文件"""
|
||||
config_path = Path(__file__).parent.parent / "conf" / "config.yaml"
|
||||
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
return yaml.safe_load(f)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to load config: {str(e)}")
|
||||
return {
|
||||
'proxy_url': '',
|
||||
'config_version': '1.0',
|
||||
'app_version': '1.0',
|
||||
'domains': [],
|
||||
'v2ray_config_path': str(Path(__file__).parent.parent / "conf" / "v2ray.json"),
|
||||
'v2ray_template_path': str(Path(__file__).parent.parent / "conf" / "v2ray_template.json"),
|
||||
'v2ray_log_path': str(Path(__file__).parent.parent / "logs" / "v2ray.log")
|
||||
}
|
||||
|
||||
def save_config(config):
|
||||
"""保存配置文件"""
|
||||
config_path = Path(__file__).parent.parent / "conf" / "config.yaml"
|
||||
|
||||
try:
|
||||
with open(config_path, 'w') as f:
|
||||
yaml.dump(config, f)
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to save config: {str(e)}")
|
||||
return False
|
||||
|
||||
def parse_vless_url(url):
|
||||
"""解析vless://URL"""
|
||||
try:
|
||||
# 移除URL编码
|
||||
url = url.replace('%2F', '/').replace('%3D', '=').replace('%3F', '?')
|
||||
|
||||
# 解析URL
|
||||
parsed = urlparse(url)
|
||||
query_params = parse_qs(parsed.query)
|
||||
|
||||
# 提取必要参数
|
||||
uuid = parsed.username
|
||||
address = parsed.hostname
|
||||
port = parsed.port
|
||||
name = parsed.fragment
|
||||
|
||||
# 提取REALITY参数
|
||||
security = query_params.get('security', ['none'])[0]
|
||||
encryption = query_params.get('encryption', ['none'])[0]
|
||||
flow = query_params.get('flow', [''])[0]
|
||||
type_ = query_params.get('type', ['tcp'])[0]
|
||||
sni = query_params.get('sni', [''])[0]
|
||||
fp = query_params.get('fp', ['chrome'])[0]
|
||||
pbk = query_params.get('pbk', [''])[0]
|
||||
sid = query_params.get('sid', [''])[0]
|
||||
spx = query_params.get('spx', [''])[0]
|
||||
|
||||
return {
|
||||
'uuid': uuid,
|
||||
'address': address,
|
||||
'port': port,
|
||||
'name': name,
|
||||
'security': security,
|
||||
'encryption': encryption,
|
||||
'flow': flow,
|
||||
'type': type_,
|
||||
'sni': sni,
|
||||
'fp': fp,
|
||||
'pbk': pbk,
|
||||
'sid': sid,
|
||||
'spx': spx
|
||||
}
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to parse vless URL: {str(e)}")
|
||||
return None
|
||||
|
||||
def generate_v2ray_config(template_path, output_path, proxy_info, domains):
|
||||
"""生成v2ray配置文件"""
|
||||
try:
|
||||
# 读取模板
|
||||
with open(template_path, 'r') as f:
|
||||
template = json.load(f)
|
||||
|
||||
# 替换模板中的占位符
|
||||
outbound = template['outbounds'][0]
|
||||
|
||||
# 设置vless服务器信息
|
||||
outbound['settings']['vnext'][0]['address'] = proxy_info['address']
|
||||
outbound['settings']['vnext'][0]['port'] = proxy_info['port']
|
||||
outbound['settings']['vnext'][0]['users'][0]['id'] = proxy_info['uuid']
|
||||
outbound['settings']['vnext'][0]['users'][0]['encryption'] = proxy_info['encryption']
|
||||
outbound['settings']['vnext'][0]['users'][0]['flow'] = proxy_info['flow']
|
||||
|
||||
# 设置streamSettings
|
||||
stream_settings = outbound['streamSettings']
|
||||
stream_settings['network'] = proxy_info['type']
|
||||
stream_settings['security'] = proxy_info['security']
|
||||
|
||||
if proxy_info['security'] == 'reality':
|
||||
reality_settings = {
|
||||
'serverName': proxy_info['sni'],
|
||||
'fingerprint': proxy_info['fp'],
|
||||
'publicKey': proxy_info['pbk'],
|
||||
'shortId': proxy_info['sid'],
|
||||
'spiderX': proxy_info['spx']
|
||||
}
|
||||
stream_settings['realitySettings'] = reality_settings
|
||||
|
||||
# 替换域名列表
|
||||
for rule in template['routing']['rules']:
|
||||
if 'domain' in rule and 'REPLACE_DOMAINS' in rule['domain']:
|
||||
rule['domain'] = domains
|
||||
|
||||
# 保存配置
|
||||
with open(output_path, 'w') as f:
|
||||
json.dump(template, f, indent=2)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to generate v2ray config: {str(e)}")
|
||||
return False
|
||||
86
child/utils/network_utils.py
Normal file
86
child/utils/network_utils.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import requests
|
||||
import json
|
||||
import yaml
|
||||
import logging
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
def download_file(url, save_path, timeout=30):
|
||||
"""下载文件"""
|
||||
try:
|
||||
response = requests.get(url, timeout=timeout, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
with open(save_path, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to download file from {url}: {str(e)}")
|
||||
return False
|
||||
|
||||
def fetch_json(url, timeout=15):
|
||||
"""获取JSON数据"""
|
||||
try:
|
||||
response = requests.get(url, timeout=timeout)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to fetch JSON from {url}: {str(e)}")
|
||||
return None
|
||||
|
||||
def fetch_yaml(url, timeout=15):
|
||||
"""获取YAML数据"""
|
||||
try:
|
||||
response = requests.get(url, timeout=timeout)
|
||||
response.raise_for_status()
|
||||
return yaml.safe_load(response.text)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to fetch YAML from {url}: {str(e)}")
|
||||
return None
|
||||
|
||||
def check_connectivity(host="8.8.8.8", port=53, timeout=3):
|
||||
"""检查网络连接"""
|
||||
import socket
|
||||
|
||||
try:
|
||||
socket.setdefaulttimeout(timeout)
|
||||
socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port))
|
||||
return True
|
||||
except socket.error:
|
||||
return False
|
||||
|
||||
def get_public_ip():
|
||||
"""获取公网IP"""
|
||||
try:
|
||||
response = requests.get('https://api.ipify.org?format=json', timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()['ip']
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to get public IP: {str(e)}")
|
||||
return None
|
||||
|
||||
def measure_speedtest():
|
||||
"""简单测速"""
|
||||
try:
|
||||
# 下载测试
|
||||
start_time = time.time()
|
||||
response = requests.get('https://speedtest.tele2.net/10MB.zip', timeout=30, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
total_bytes = 0
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
total_bytes += len(chunk)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
download_speed = (total_bytes * 8) / (elapsed_time * 1024 * 1024) # Mbps
|
||||
|
||||
return {
|
||||
'download_speed': download_speed,
|
||||
'elapsed_time': elapsed_time,
|
||||
'total_bytes': total_bytes
|
||||
}
|
||||
except Exception as e:
|
||||
logging.error(f"Speed test failed: {str(e)}")
|
||||
return None
|
||||
48
child/utils/system_utils.py
Normal file
48
child/utils/system_utils.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import platform
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
def get_architecture():
|
||||
"""检测系统架构"""
|
||||
machine = platform.machine().lower()
|
||||
if 'arm' in machine or 'aarch64' in machine:
|
||||
return 'arm64'
|
||||
return 'amd64'
|
||||
|
||||
def get_v2ray_binary_path():
|
||||
"""获取v2ray二进制路径"""
|
||||
arch = get_architecture()
|
||||
base_dir = Path(__file__).parent.parent
|
||||
v2ray_dir = base_dir / "v2ray"
|
||||
|
||||
if arch == 'arm64':
|
||||
return v2ray_dir / "v2ray-macos-arm64"
|
||||
return v2ray_dir / "v2ray-macos-64"
|
||||
|
||||
def set_system_proxy(enable, host="127.0.0.1", port=1081):
|
||||
"""配置macOS系统代理"""
|
||||
# 获取当前网络服务
|
||||
service_cmd = "networksetup -listnetworkserviceorder | grep $(route -n get default | grep 'interface' | awk '{print $2}') -A1 | grep -o '[^ ]*$'"
|
||||
service = subprocess.check_output(service_cmd, shell=True).decode().strip()
|
||||
|
||||
if enable:
|
||||
cmd = f"""
|
||||
osascript -e 'do shell script "networksetup -setwebproxy \\"{service}\\" {host} {port}" with administrator privileges'
|
||||
osascript -e 'do shell script "networksetup -setsecurewebproxy \\"{service}\\" {host} {port}" with administrator privileges'
|
||||
"""
|
||||
else:
|
||||
cmd = f"""
|
||||
osascript -e 'do shell script "networksetup -setwebproxystate \\"{service}\\" off" with administrator privileges'
|
||||
osascript -e 'do shell script "networksetup -setsecurewebproxystate \\"{service}\\" off" with administrator privileges'
|
||||
"""
|
||||
|
||||
try:
|
||||
subprocess.run(cmd, shell=True, check=True)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
def get_temp_dir():
|
||||
"""获取安全的临时目录"""
|
||||
import tempfile
|
||||
return Path(tempfile.gettempdir()) / f"ezproxy_update_{os.getpid()}"
|
||||
Reference in New Issue
Block a user