clash-linux commited on
Commit
7c6f04a
·
verified ·
1 Parent(s): 42ac4a0

Upload 14 files

Browse files
app/data/Dockerfile ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用官方Python 3.9 Alpine作为基础镜像(轻量级)
2
+ FROM python:3.9-alpine
3
+
4
+ # 设置工作目录
5
+ WORKDIR /app
6
+
7
+ # 安装系统依赖
8
+ # 添加 yaml-dev 提供编译PyYAML所需的libyaml
9
+ # 添加 unzip 用于解压 Yacd
10
+ RUN apk add --no-cache \
11
+ curl \
12
+ ca-certificates \
13
+ tzdata \
14
+ tar \
15
+ gzip \
16
+ python3-dev \
17
+ musl-dev \
18
+ libffi-dev \
19
+ yaml-dev \
20
+ unzip \
21
+ # 不再需要 py3-yaml
22
+ # py3-yaml \
23
+ # 添加调试工具
24
+ file \
25
+ libc-utils \
26
+ strace
27
+
28
+ # 设置时区为亚洲/上海
29
+ ENV TZ=Asia/Shanghai
30
+
31
+ # 创建必要的目录 (不再需要在这里 chmod,因为 COPY 会处理)
32
+ RUN mkdir -p ./clash_core ./subconverter ./data
33
+
34
+ # 下载并安装Clash Meta,保留原始文件名
35
+ RUN echo "Downloading Clash Meta..." && \
36
+ curl -L -f -o /tmp/clash-meta.gz "https://github.com/MetaCubeX/Clash.Meta/releases/download/v1.16.0/clash.meta-linux-amd64-v1.16.0.gz" && \
37
+ echo "Extracting Clash Meta..." && \
38
+ gunzip -c /tmp/clash-meta.gz > ./clash_core/clash.meta-linux-amd64 && \
39
+ echo "Setting Clash Meta permissions..." && \
40
+ chmod +x ./clash_core/clash.meta-linux-amd64 && \
41
+ # 确保Linux可执行属性已设置
42
+ ls -la ./clash_core/clash.meta-linux-amd64 && \
43
+ # 显示文件类型
44
+ file ./clash_core/clash.meta-linux-amd64 && \
45
+ echo "Verifying Clash Meta exists..." && \
46
+ test -f ./clash_core/clash.meta-linux-amd64 && \
47
+ echo "Cleaning up Clash Meta download..." && \
48
+ rm /tmp/clash-meta.gz
49
+
50
+ # 下载并完整解压subconverter
51
+ RUN echo "Downloading subconverter..." && \
52
+ curl -L -f -o /tmp/subconverter.tar.gz "https://github.com/tindy2013/subconverter/releases/download/v0.7.2/subconverter_linux64.tar.gz" && \
53
+ echo "Extracting subconverter archive..." && \
54
+ tar -xzf /tmp/subconverter.tar.gz -C ./subconverter --strip-components=1 && \
55
+ echo "Setting subconverter permissions..." && \
56
+ chmod +x ./subconverter/subconverter && \
57
+ # 确保Linux可执行属性已设置
58
+ ls -la ./subconverter/subconverter && \
59
+ # 显示文件类型
60
+ file ./subconverter/subconverter && \
61
+ echo "Verifying subconverter exists..." && \
62
+ test -f ./subconverter/subconverter && \
63
+ echo "Cleaning up subconverter download..." && \
64
+ rm /tmp/subconverter.tar.gz
65
+
66
+ # 复制Python依赖列表
67
+ COPY requirements.txt ./
68
+
69
+ # 升级 pip
70
+ RUN pip install --upgrade pip
71
+
72
+ # 安装Cython(用于编译PyYAML)
73
+ RUN echo "Installing Cython for PyYAML build..." && \
74
+ pip install --no-cache-dir Cython
75
+
76
+ # 安装Python依赖(包括PyYAML,现在应该能成功编译了)
77
+ RUN echo "Installing Python dependencies..." && \
78
+ pip install --no-cache-dir -r requirements.txt
79
+
80
+ # 可选:删除构建依赖以减小镜像体积
81
+ RUN apk del python3-dev musl-dev libffi-dev yaml-dev
82
+
83
+ # 下载并准备 Yacd UI 文件 (从 Yacd-meta 的 gh-pages 分支)
84
+ RUN echo "Downloading Yacd-meta UI (gh-pages branch)..." && \
85
+ YACD_DIR=/app/app/static/yacd && \
86
+ mkdir -p ${YACD_DIR} && \
87
+ # 下载 gh-pages 分支的 zip 压缩包
88
+ curl -L -f -o /tmp/yacd-gh-pages.zip "https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip" && \
89
+ echo "Extracting Yacd-meta UI (gh-pages)..." && \
90
+ # 解压到临时目录
91
+ unzip -q /tmp/yacd-gh-pages.zip -d /tmp && \
92
+ # 将解压后的 gh-pages 目录下的 *所有内容* 移动到目标位置
93
+ # 注意:解压后的文件夹名通常是 {repo_name}-{branch_name},即 Yacd-meta-gh-pages
94
+ mv /tmp/Yacd-meta-gh-pages/* ${YACD_DIR}/ && \
95
+ echo "Cleaning up Yacd-meta download..." && \
96
+ rm /tmp/yacd-gh-pages.zip && \
97
+ rm -rf /tmp/Yacd-meta-gh-pages
98
+
99
+ # 在容器内创建空的 minimal_pref.yml 文件
100
+ RUN touch /app/subconverter/minimal_pref.yml
101
+
102
+ # 添加 COPY data 目录
103
+ COPY data/ ./data/
104
+ # 添加日志确认文件复制
105
+ RUN echo "${GREEN}Contents of /app/data after copy:${NC}" && ls -la ./data
106
+
107
+ # 设置环境变量
108
+ ENV PYTHONDONTWRITEBYTECODE=1 \
109
+ PYTHONUNBUFFERED=1 \
110
+ FLASK_APP=app.main \
111
+ FLASK_ENV=production \
112
+ FLASK_PORT=7860 \
113
+ CLASH_PROXY_PORT=7890 \
114
+ CLASH_API_PORT=9090 \
115
+ PORT=7860
116
+
117
+ # 复制应用代码和静态文件
118
+ COPY app/ ./app/
119
+
120
+ # 复制启动脚本并赋予执行权限
121
+ COPY entrypoint.sh ./
122
+ RUN chmod +x ./entrypoint.sh
123
+
124
+ # 给脚本和二进制文件执行权限 (可以简化)
125
+ # RUN chmod +x ./clash_core/clash.meta-linux-amd64 || true
126
+ # RUN chmod +x ./subconverter/subconverter || true
127
+ # 确保在下载后就已设置
128
+
129
+ # 暴露端口
130
+ EXPOSE $FLASK_PORT $CLASH_PROXY_PORT $CLASH_API_PORT
131
+
132
+ # 使用entrypoint脚本启动应用
133
+ ENTRYPOINT ["/app/entrypoint.sh"]
app/data/app/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Simple Clash Relay - Python Package
2
+ # 这个文件用于标识当前目录为一个Python包
app/data/app/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (173 Bytes). View file
 
app/data/app/__pycache__/auth.cpython-313.pyc ADDED
Binary file (1.55 kB). View file
 
app/data/app/__pycache__/clash_manager.cpython-313.pyc ADDED
Binary file (19.9 kB). View file
 
app/data/app/__pycache__/debug_tools.cpython-313.pyc ADDED
Binary file (8.19 kB). View file
 
app/data/app/__pycache__/main.cpython-313.pyc ADDED
Binary file (27.2 kB). View file
 
app/data/app/__pycache__/sub_manager.cpython-313.pyc ADDED
Binary file (24.8 kB). View file
 
app/data/app/auth.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 认证模块 - 提供API访问认证功能
6
+ """
7
+
8
+ import os
9
+ import logging
10
+ import functools
11
+ from flask import request, jsonify
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # 从环境变量获取API密钥
16
+ API_KEY = os.environ.get("API_KEY", "changeme")
17
+
18
+ def authenticate(func):
19
+ """
20
+ 用于API路由的认证装饰器
21
+
22
+ Args:
23
+ func: 被装饰的视图函数
24
+
25
+ Returns:
26
+ 函数: 包含认证逻辑的包装函数
27
+ """
28
+ @functools.wraps(func)
29
+ def wrapper(*args, **kwargs):
30
+ # 获取请求头中的API Key
31
+ api_key = request.headers.get("X-API-Key")
32
+
33
+ # 验证API Key
34
+ if not api_key or api_key != API_KEY:
35
+ logger.warning(f"API认证失败:{'未提供API Key' if not api_key else 'API Key无效'}")
36
+ return jsonify({
37
+ "success": False,
38
+ "error": "未提供API Key或API Key无效"
39
+ }), 401
40
+
41
+ # 认证通过,调用原始视图函数
42
+ return func(*args, **kwargs)
43
+
44
+ return wrapper
app/data/app/clash_manager.py ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Clash管理器 - 负责Clash Core进程的启动、停止和API调用
6
+ """
7
+
8
+ import os
9
+ import time
10
+ import signal
11
+ import logging
12
+ import subprocess
13
+ import requests
14
+ import json
15
+ from .debug_tools import run_debug_diagnostics
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ class ClashManager:
20
+ """管理Clash Core进程和与其API的交互"""
21
+
22
+ def __init__(self, config_path, clash_path, api_port=9090, proxy_port=7890):
23
+ """
24
+ 初始化Clash管理器
25
+
26
+ Args:
27
+ config_path: Clash配置文件路径
28
+ clash_path: Clash可执行文件路径
29
+ api_port: Clash API监听端口
30
+ proxy_port: Clash代理监听端口
31
+ """
32
+ self.config_path = os.path.abspath(config_path)
33
+ self.clash_path = os.path.abspath(clash_path)
34
+ self.api_port = api_port
35
+ self.proxy_port = proxy_port
36
+ self.api_base_url = f"http://127.0.0.1:{api_port}"
37
+ self.clash_process = None
38
+
39
+ # 确保Clash可执行文件存在
40
+ if not os.path.exists(clash_path):
41
+ raise FileNotFoundError(f"Clash可执行文件未找到: {clash_path}")
42
+
43
+ def start_clash(self):
44
+ """启动Clash Core进程"""
45
+ if self.clash_process and self.clash_process.poll() is None:
46
+ logger.info("Clash Core已经在运行中")
47
+ return
48
+
49
+ # 确保配置文件存在
50
+ if not os.path.exists(self.config_path):
51
+ # 检查目录是否存在
52
+ config_dir = os.path.dirname(self.config_path)
53
+ if not os.path.exists(config_dir):
54
+ try:
55
+ os.makedirs(config_dir, exist_ok=True)
56
+ logger.info(f"创建了配置目录: {config_dir}")
57
+ except Exception as e:
58
+ logger.error(f"无法创建配置目录: {str(e)}")
59
+
60
+ # 如果目录存在但文件不存在,尝试创建一个基础配置
61
+ try:
62
+ with open(self.config_path, "w", encoding="utf-8") as f:
63
+ f.write(self._get_base_config())
64
+ logger.info(f"创建了基础配置文件: {self.config_path}")
65
+ except Exception as e:
66
+ logger.error(f"无法创建配置文件: {str(e)}")
67
+ raise FileNotFoundError(f"Clash配置文件未找到且无法创建: {self.config_path}")
68
+
69
+ # 验证配置文件内容
70
+ self._validate_config_file()
71
+
72
+ # 运行调试诊断
73
+ logger.info("运行环境诊断...")
74
+ try:
75
+ diagnostics = run_debug_diagnostics(self.clash_path, self.config_path)
76
+ if not diagnostics.get("clash_binary", {}).get("is_executable", False):
77
+ logger.warning("Clash Core不是可执行文件,尝试设置执行权限")
78
+ try:
79
+ os.chmod(self.clash_path, 0o755)
80
+ logger.info("已设置执行权限")
81
+ except Exception as e:
82
+ logger.error(f"设置执行权限失败: {str(e)}")
83
+ except Exception as e:
84
+ logger.error(f"运行诊断时出错: {str(e)}")
85
+
86
+ # 设置Clash命令行参数 (兼容Clash Meta)
87
+ cmd = [
88
+ self.clash_path,
89
+ "-f", self.config_path,
90
+ "-d", os.path.dirname(self.config_path)
91
+ ]
92
+
93
+ # 为Clash Meta添加额外参数
94
+ if "meta" in self.clash_path.lower():
95
+ # Clash Meta特有参数
96
+ cmd.extend([
97
+ "-ext-ctl", f"127.0.0.1:{self.api_port}",
98
+ # 如果需要可以添加更多Clash Meta特有参数
99
+ ])
100
+ else:
101
+ # 原始Clash参数
102
+ cmd.extend([
103
+ "-ext-ctl", f"127.0.0.1:{self.api_port}",
104
+ "-ext-ui", "" # 禁用外部UI
105
+ ])
106
+
107
+ # 启动Clash进程
108
+ logger.info(f"正在启动Clash Core: {' '.join(cmd)}")
109
+
110
+ # 首先尝试检查Clash可执行文件是否存在并且可执行
111
+ if not os.path.exists(self.clash_path):
112
+ raise FileNotFoundError(f"Clash可执行文件未找到: {self.clash_path}")
113
+
114
+ # 检查Clash文件是否有执行权限
115
+ try:
116
+ # 尝试设置执行权限
117
+ os.chmod(self.clash_path, 0o755)
118
+ logger.info(f"已设置Clash Core的执行权限")
119
+ except Exception as e:
120
+ logger.warning(f"无法设置执行权限: {str(e)}, 尝试继续执行...")
121
+
122
+ # 启动进程并捕获输出
123
+ try:
124
+ self.clash_process = subprocess.Popen(
125
+ cmd,
126
+ stdout=subprocess.PIPE,
127
+ stderr=subprocess.PIPE,
128
+ universal_newlines=True
129
+ )
130
+
131
+ # 等待Clash启动
132
+ logger.info("等待Clash Core启动...")
133
+ time.sleep(2)
134
+
135
+ # 检查进程是否成功启动
136
+ if self.clash_process.poll() is not None:
137
+ # 如果进程已经退出,获取错误输出
138
+ stdout, stderr = self.clash_process.communicate()
139
+ logger.error(f"Clash启动失败,退出代码: {self.clash_process.returncode}")
140
+ logger.error(f"标准输出: {stdout}")
141
+ logger.error(f"错误输出: {stderr}")
142
+
143
+ # 如果没有错误输出,尝试读取配置文件周围的日志
144
+ if not stderr.strip():
145
+ try:
146
+ # 查看配置文件内容
147
+ with open(self.config_path, 'r', encoding='utf-8') as f:
148
+ config_content = f.read()
149
+ logger.error(f"配置文件内容预览 (前100字符): {config_content[:100]}")
150
+ except Exception as file_err:
151
+ logger.error(f"读取配置文件失败: {str(file_err)}")
152
+
153
+ # 创建更有意义的错误消息
154
+ error_message = stderr or stdout or "无错误信息,可能是程序无法执行或立即崩溃"
155
+ raise RuntimeError(f"Clash启动失败: {error_message}")
156
+
157
+ # 进程仍在运行,验证API是否可访问
158
+ logger.info("进程仍在运行,验证API...")
159
+
160
+ try:
161
+ # 尝试多次连接API,有时需要一些时间
162
+ max_retries = 3
163
+ for i in range(max_retries):
164
+ try:
165
+ self._call_api("GET", "/version")
166
+ logger.info("Clash API已就绪")
167
+ return # 成功,退出函数
168
+ except Exception as api_err:
169
+ if i < max_retries - 1:
170
+ logger.warning(f"API连接尝试 {i+1}/{max_retries} 失败: {str(api_err)},重试...")
171
+ time.sleep(1)
172
+ else:
173
+ # 最后一次尝试失败,捕获并记录错误
174
+ stdout, stderr = self.clash_process.communicate(timeout=1)
175
+ logger.error(f"API连接失败,标准输出: {stdout}")
176
+ logger.error(f"API连接失败,错误输出: {stderr}")
177
+ # 杀掉进程并抛出异常
178
+ self.stop_clash()
179
+ raise RuntimeError(f"无法连接到Clash API: {str(api_err)},进程输出: {stderr or stdout}")
180
+ except Exception as e:
181
+ # 捕获其他异常
182
+ self.stop_clash()
183
+ raise RuntimeError(f"验证API时出错: {str(e)}")
184
+
185
+ except (subprocess.SubprocessError, OSError) as e:
186
+ # 进程启动失败
187
+ logger.error(f"启动Clash进程时出错: {str(e)}")
188
+ raise RuntimeError(f"无法启动Clash进程: {str(e)}")
189
+
190
+ def stop_clash(self):
191
+ """停止Clash Core进程"""
192
+ if self.clash_process and self.clash_process.poll() is None:
193
+ logger.info("正在停止Clash Core...")
194
+
195
+ # 尝试优雅地终止进程
196
+ self.clash_process.terminate()
197
+
198
+ # 等待进程终止
199
+ try:
200
+ self.clash_process.wait(timeout=5)
201
+ except subprocess.TimeoutExpired:
202
+ # 如果进程没有及时终止,强制结束
203
+ logger.warning("Clash进程未响应终止信号,强制结束...")
204
+ self.clash_process.kill()
205
+
206
+ self.clash_process = None
207
+ logger.info("Clash Core已停止")
208
+
209
+ def restart_clash(self):
210
+ """重启Clash Core进程"""
211
+ logger.info("正在重启Clash Core...")
212
+ self.stop_clash()
213
+ time.sleep(1) # 给进程一些时间完全终止
214
+ self.start_clash()
215
+ logger.info("Clash Core已重启")
216
+
217
+ def get_nodes(self):
218
+ """
219
+ 获取所有可用的代理节点名称列表
220
+
221
+ Returns:
222
+ list: 节点名称列表
223
+ """
224
+ response = self._call_api("GET", "/proxies")
225
+ proxies = response.get("proxies", {})
226
+
227
+ # 过滤出实际的代理节点(排除DIRECT, REJECT等内置代理和策略组)
228
+ node_names = []
229
+ for name, proxy in proxies.items():
230
+ if proxy.get("type") not in ["Direct", "Reject", "Selector", "URLTest", "Fallback", "LoadBalance"]:
231
+ node_names.append(name)
232
+
233
+ return node_names
234
+
235
+ def switch_node(self, node_name):
236
+ """
237
+ 切换到指定的代理节点
238
+
239
+ Args:
240
+ node_name: 节点名称
241
+
242
+ Raises:
243
+ ValueError: 如果节点名称无效
244
+ """
245
+ # 获取所有节点以验证目标节点存��
246
+ all_nodes = self.get_nodes()
247
+ if node_name not in all_nodes:
248
+ raise ValueError(f"无效的节点名称: {node_name}")
249
+
250
+ # 切换GLOBAL策略组到指定节点
251
+ # 注意:这里假设使用GLOBAL作为顶级策略组,你可能需要根据实际配置调整
252
+ try:
253
+ self._call_api("PUT", "/proxies/GLOBAL", json={"name": node_name})
254
+ logger.info(f"已切换到节点: {node_name}")
255
+ except Exception as e:
256
+ raise RuntimeError(f"切换节点失败: {str(e)}")
257
+
258
+ def get_current_node(self):
259
+ """
260
+ 获取当前使用的节点名称
261
+
262
+ Returns:
263
+ str: 当前节点名称
264
+ """
265
+ # 获取GLOBAL策略组的当前选择
266
+ # 注意:这里假设使用GLOBAL作为顶级策略组,你可能需要根据实际配置调整
267
+ response = self._call_api("GET", "/proxies/GLOBAL")
268
+ return response.get("now", "unknown")
269
+
270
+ def _call_api(self, method, endpoint, **kwargs):
271
+ """
272
+ 调用Clash的API
273
+
274
+ Args:
275
+ method: HTTP方法 (GET, POST, PUT等)
276
+ endpoint: API端点路径
277
+ **kwargs: 传递给requests的其他参数
278
+
279
+ Returns:
280
+ dict: API响应的JSON数据
281
+
282
+ Raises:
283
+ RuntimeError: 如果API调用失败
284
+ """
285
+ url = f"{self.api_base_url}{endpoint}"
286
+ logger.debug(f"调用Clash API: {method} {url}")
287
+
288
+ try:
289
+ response = requests.request(method, url, timeout=10, **kwargs)
290
+ response.raise_for_status()
291
+ return response.json()
292
+ except requests.RequestException as e:
293
+ logger.error(f"Clash API调用失败: {str(e)}")
294
+ raise RuntimeError(f"Clash API调用失败: {str(e)}")
295
+
296
+ def __del__(self):
297
+ """析构函数,确保进程在对象销毁时被终止"""
298
+ self.stop_clash()
299
+
300
+ def _validate_config_file(self):
301
+ """验证配置文件内容,确保基本配置存在"""
302
+ try:
303
+ with open(self.config_path, "r", encoding="utf-8") as f:
304
+ content = f.read()
305
+
306
+ # 检查文件大小
307
+ if len(content) < 10:
308
+ logger.warning(f"配置文件内容过短: {len(content)} 字节")
309
+ # 尝试使用基础配置替换
310
+ with open(self.config_path, "w", encoding="utf-8") as f:
311
+ f.write(self._get_base_config())
312
+ logger.info("已使用基础配置替换")
313
+ return
314
+
315
+ # 检查基本配置是否存在 (非严格YAML解析,简单文本检查)
316
+ missing_configs = []
317
+ if "mixed-port:" not in content and "port:" not in content:
318
+ missing_configs.append("端口配置")
319
+
320
+ if "proxies:" not in content:
321
+ missing_configs.append("代理配置")
322
+
323
+ if missing_configs:
324
+ logger.warning(f"配置文件缺少: {', '.join(missing_configs)}")
325
+ # 如果缺少关键配置,尝试修复
326
+ has_patch = False
327
+
328
+ if "mixed-port:" not in content and "port:" not in content:
329
+ content = f"mixed-port: {self.proxy_port}\n" + content
330
+ has_patch = True
331
+
332
+ if "external-controller:" not in content:
333
+ content = f"external-controller: 127.0.0.1:{self.api_port}\n" + content
334
+ has_patch = True
335
+
336
+ if "proxies:" not in content:
337
+ content += "\nproxies:\n - name: DIRECT\n type: Direct\n"
338
+ has_patch = True
339
+
340
+ if has_patch:
341
+ with open(self.config_path, "w", encoding="utf-8") as f:
342
+ f.write(content)
343
+ logger.info("已修补配置文件")
344
+
345
+ except Exception as e:
346
+ logger.error(f"验证配置文件时出错: {str(e)}")
347
+ # 如果验证失败,尝试使用基础配置
348
+ try:
349
+ with open(self.config_path, "w", encoding="utf-8") as f:
350
+ f.write(self._get_base_config())
351
+ logger.info("已使用基础配置替换")
352
+ except Exception as write_err:
353
+ logger.error(f"无法写入基础配置: {str(write_err)}")
354
+
355
+ def _get_base_config(self):
356
+ """返回基础Clash配置"""
357
+ return f"""# 基础Clash配置
358
+ mixed-port: {self.proxy_port}
359
+ allow-lan: true
360
+ mode: Rule
361
+ log-level: info
362
+ external-controller: 127.0.0.1:{self.api_port}
363
+ secret: ""
364
+
365
+ proxies:
366
+ - name: DIRECT
367
+ type: Direct
368
+
369
+ proxy-groups:
370
+ - name: GLOBAL
371
+ type: select
372
+ proxies:
373
+ - DIRECT
374
+
375
+ rules:
376
+ - MATCH,DIRECT
377
+ """
app/data/app/data/config.yaml ADDED
@@ -0,0 +1,618 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ mixed-port: 7890
2
+ allow-lan: false
3
+ bind-address: '*'
4
+ mode: rule
5
+ log-level: info
6
+ external-controller: '127.0.0.1:9090'
7
+ unified-delay: true
8
+ tcp-concurrent: true
9
+ dns:
10
+ enable: true
11
+ ipv6: false
12
+ default-nameserver: [223.5.5.5, 119.29.29.29]
13
+ enhanced-mode: fake-ip
14
+ fake-ip-range: 198.18.0.1/16
15
+ use-hosts: true
16
+ nameserver: ['https://dns.alidns.com/dns-query', 'https://doh.pub/dns-query']
17
+ fallback: ['https://dns.alidns.com/dns-query', 'https://doh.pub/dns-query']
18
+ fallback-filter: { geoip: true, ipcidr: [240.0.0.0/4, 0.0.0.0/32] }
19
+ proxies:
20
+ - { name: '剩余流量:88.25 GB', type: vless, server: pq.aws48.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: buylite.tv.apple.com, reality-opts: { public-key: 6T-kYBf65ERaEAhxIyHL1FCfu0QR6P2XQMtcvUgzSjM, short-id: 70ad150d } }
21
+ - { name: '距离下次重置剩余:6 天', type: vless, server: pq.aws48.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: buylite.tv.apple.com, reality-opts: { public-key: 6T-kYBf65ERaEAhxIyHL1FCfu0QR6P2XQMtcvUgzSjM, short-id: 70ad150d } }
22
+ - { name: 套餐到期:2025-10-10, type: vless, server: pq.aws48.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: buylite.tv.apple.com, reality-opts: { public-key: 6T-kYBf65ERaEAhxIyHL1FCfu0QR6P2XQMtcvUgzSjM, short-id: 70ad150d } }
23
+ - { name: '🇸🇬AWS新加坡01 | 高速专线推荐', type: vless, server: pq.aws48.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: buylite.tv.apple.com, reality-opts: { public-key: 6T-kYBf65ERaEAhxIyHL1FCfu0QR6P2XQMtcvUgzSjM, short-id: 70ad150d } }
24
+ - { name: '🇸🇬AWS新加坡02 | 高速专线推荐', type: vless, server: pq.aws49.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: buylite.tv.apple.com, reality-opts: { public-key: xOnY2ykLrcFMIhp7GBQu1mSH7yIW-yCc1ThJnVVtKDc, short-id: 5b6762be } }
25
+ - { name: '🇸🇬AWS新加坡03 | 高速专线推荐', type: vless, server: pq.aws50.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: tv.apple.com, reality-opts: { public-key: O5WC6waWDGR_ElOHmhqEQGUWDufcFxTuu7BPhwW1sE4, short-id: e1df2a15 } }
26
+ - { name: '🇸🇬AWS新加坡04 | 高速专线推荐', type: vless, server: pq.aws54.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: download-porter.hoyoverse.com, reality-opts: { public-key: asLJREsp9L0JbURQPIhFxc6bZIgpunEOjOJeHv3YcEs, short-id: b0fbe0f0 } }
27
+ - { name: '🇯🇵AWS日本东京01 | 高速专线推荐', type: vless, server: pq.aws51.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: buylite.tv.apple.com, reality-opts: { public-key: N86yyW-L91vOtC9qgJcYAhnva9M4WT3vclSnsQo4A2k, short-id: 79fd451e } }
28
+ - { name: '🇯🇵AWS日本东京02 | 高速专线推荐', type: vless, server: pq.aws52.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: tv.apple.com, reality-opts: { public-key: gkeUZppVQzutjGsRcsGMW8OrPHboJ3qRFpIvj8lcUj4, short-id: c174618d } }
29
+ - { name: '🇯🇵AWS日本东京03 | 高速专线推荐', type: vless, server: pq.aws55.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: download-porter.hoyoverse.com, reality-opts: { public-key: zZJg13rJNHw6Zq2TGATm8UAnTmqD97i8qzrXcJFyEls, short-id: d4f2d377 } }
30
+ - { name: 🇺🇸美国圣何塞0.1倍-可长期下载, server: 192.9.130.120, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
31
+ - { name: 🇺🇸美国圣何塞01-hy2, server: 64.181.238.32, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: tv.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
32
+ - { name: 🇺🇸美国圣何塞02-hy2, server: 192.9.157.98, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: tv.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
33
+ - { name: 🇺🇸美国圣何塞03-hy2, server: 64.181.243.177, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: tv.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
34
+ - { name: 🇺🇸美国圣何塞04-hy2, server: 146.235.201.189, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: tv.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
35
+ - { name: 🇺🇸美国圣何塞05-hy2, server: 146.235.212.192, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: tv.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
36
+ - { name: 🇺🇸美国圣何塞06-hy2, server: 138.2.229.162, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: tv.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
37
+ - { name: 🇺🇸美国圣何塞07-hy2, server: 167.234.208.240, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: tv.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
38
+ - { name: 🇺🇸美国圣何塞08-hy2, server: 192.18.133.190, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: tv.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
39
+ - { name: 🇺🇸美国圣何塞09-hy2, server: 167.234.210.122, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
40
+ - { name: 🇺🇸美国圣何塞10-hy2, server: 146.235.203.34, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
41
+ - { name: '🇦🇪迪拜2 | 高速专线-hy2', server: 193.123.76.84, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
42
+ - { name: '🇰🇷韩国-1倍 | 高速专线-hy2', server: 193.122.98.107, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: api.push.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
43
+ - { name: '🇰🇷韩国3-1倍 | 高速专线-hy2', server: 146.56.164.36, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: api.push.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
44
+ - { name: '🇰🇷韩国4-1倍 | 高速专线-hy2', server: 193.123.246.244, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: api.push.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
45
+ - { name: '🇰🇷韩国5-1倍 | 高速专线-hy2', server: 193.123.249.0, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: api.push.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
46
+ - { name: '🇰🇷韩国6-2倍 | 高速专线-hy2', server: 193.123.240.119, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
47
+ - { name: '🇰🇷韩国7-2倍 | 高速专线-hy2', server: 193.123.250.219, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
48
+ - { name: '🇰🇷韩国8-2倍 | 高速专线-hy2', server: 193.123.231.148, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
49
+ - { name: '🇰🇷韩国9-2倍 | 高速专线-hy2', server: 152.69.225.196, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
50
+ - { name: '🇰🇷韩国10-2倍 | 高速专线-hy2', server: 146.56.100.103, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
51
+ - { name: '🇩🇪德国 | 高速专线-hy2', server: 132.145.239.250, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: api.push.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
52
+ - { name: '🇩🇪德国2 | 高速专线-hy2', server: 141.147.19.58, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: api.push.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
53
+ - { name: '🇨🇭��士苏黎世 | 高速专线-hy2', server: 152.67.95.183, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
54
+ - { name: '🇦🇺澳大利亚 | 高速专线-hy2', server: 152.69.180.226, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: api.push.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
55
+ - { name: '🇦🇺澳大利亚2 | 高速专线-hy2', server: 158.179.18.251, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: api.push.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
56
+ - { name: '🇦🇺澳大利亚3 | 高速专线-hy2', server: 159.13.35.245, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: api.push.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
57
+ - { name: '🇸🇬新加坡 | 高速专线-hy2', server: 152.69.220.212, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: api.push.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
58
+ - { name: '🇸🇬新加坡2 | 高速专线-hy2', server: 158.178.236.104, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: api.push.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
59
+ - { name: '🇸🇬新加坡3 | 高速专线-hy2', server: 140.245.35.78, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
60
+ - { name: '🇸🇬新加坡4 | 高速专线-hy2', server: 140.245.37.170, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
61
+ - { name: '🇸🇬新加坡5 | 高速专线-hy2', server: 140.245.46.17, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
62
+ - { name: '🇸🇬新加坡6 | 高速专线-hy2', server: 140.245.36.207, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
63
+ - { name: '🇺🇸美国凤凰城 | 高速专线-hy2', server: 129.146.95.165, port: 35000, ports: 35000-39000, mport: 35000-39000, udp: true, skip-cert-verify: true, sni: www.apple.com, type: hysteria2, password: c193b455-1a17-43fa-bd5e-f467208b6747 }
64
+ - { name: 🇺🇸美国阿什本02-0.1倍, type: vless, server: 104.18.20.20, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, servername: us2.globals-download.com, network: ws, ws-opts: { path: /pq/us2, headers: { Host: us2.globals-download.com } } }
65
+ - { name: 🇺🇸美国阿什本03-0.1倍, type: vless, server: 104.18.20.20, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, servername: us3.globals-download.com, network: ws, ws-opts: { path: /pq/us3, headers: { Host: us3.globals-download.com } } }
66
+ - { name: 🇺🇸美国阿什本04-0.1倍, type: vless, server: 104.18.20.20, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, servername: us4.globals-download.com, network: ws, ws-opts: { path: /pq/us4, headers: { Host: us4.globals-download.com } } }
67
+ - { name: 🇺🇸美国阿什本05-0.1倍, type: vless, server: 104.18.20.20, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, servername: us5.globals-download.com, network: ws, ws-opts: { path: /pq/us5, headers: { Host: us5.globals-download.com } } }
68
+ - { name: 🇺🇸美国阿什本06-0.1倍, type: vless, server: 104.18.20.20, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, servername: us6.globals-download.com, network: ws, ws-opts: { path: /pq/us6, headers: { Host: us6.globals-download.com } } }
69
+ - { name: 🇺🇸美国阿什本07-0.1倍, type: vless, server: 104.18.20.20, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, servername: us7.globals-download.com, network: ws, ws-opts: { path: /pq/us7, headers: { Host: us7.globals-download.com } } }
70
+ - { name: 🇰🇷韩国首尔01, type: vless, server: 193.123.226.5, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: edge, servername: buylite.music.apple.com, reality-opts: { public-key: 8nT5x5OM-97ibWMfu5y0mrvWdmuwVeg_f_RE5Ycl_yw, short-id: c5835b1f } }
71
+ - { name: 🇰🇷韩国首尔02, type: vless, server: 193.122.114.15, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: ios, servername: buylite.music.apple.com, reality-opts: { public-key: azYFzTtT4e2UwpmBzZICle8qJOLjN8kc5bq8i8aVSj0, short-id: c289c65d } }
72
+ - { name: 🇰🇷韩国首尔03, type: vless, server: 131.186.22.238, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: buylite.music.apple.com, reality-opts: { public-key: P--AseuV7sGvgy7YJ8iX58GxeP5-M2oq0Mq2YxPAXRs, short-id: f00d40f38482 } }
73
+ - { name: '🇭🇰香港HKT1 | 三网推荐', type: vless, server: pq.hkt1.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: ios, servername: download.globals-download.com }
74
+ - { name: '🇭🇰香港HKT2 | 三网推荐', type: vless, server: pq.hkt2.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: ios, servername: download.globals-download.com }
75
+ - { name: '🇭🇰香港HKT3 | 三网推荐', type: vless, server: pq.hkt3.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: ios, servername: download.globals-download.com }
76
+ - { name: '🇭🇰香港HKT4 | 三网推荐', type: vless, server: pq.hkt4.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: ios, servername: download.globals-download.com }
77
+ - { name: '🇹🇼台湾 | 避免晚高峰使用', type: vless, server: pq.hinet.tw1.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: download.yydjc.top }
78
+ - { name: '🇹🇼台湾2 | 避免晚高峰使用', type: vless, server: pq.hinet.tw2.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: download.yydjc.top }
79
+ - { name: '🇹🇼台湾3 | 避免晚高峰使用', type: vless, server: pq.hinet.tw3.yydjc.top, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: download.yydjc.top }
80
+ - { name: 🇯🇵日本高速01-0.1倍, type: vless, server: 172.67.79.136, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, network: ws, ws-opts: { path: /pq/jp1, headers: { Host: jp1.xn--ghqu5fm27b67w.com } } }
81
+ - { name: 🇮🇳印度01-0.01倍, type: vless, server: 23.227.38.0, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, network: ws, ws-opts: { path: /pq/in1, headers: { Host: in1_pq_user_vip_api.pqvip.top } } }
82
+ - { name: 🇮🇳印度02-0.01倍, type: vless, server: 23.227.38.0, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, network: ws, ws-opts: { path: /pq/in2, headers: { Host: in2_pq_user_vip_api.pqvip.top } } }
83
+ - { name: 🇮🇳印度03-0.01倍, type: vless, server: 23.227.38.0, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, network: ws, ws-opts: { path: /pq/in3, headers: { Host: in3_pq_user_vip_api.pqvip.top } } }
84
+ - { name: 🇮🇳印度04-0.01倍, type: vless, server: 23.227.38.0, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, network: ws, ws-opts: { path: /pq/in4, headers: { Host: in4_pq_user_vip_api.pqvip.top } } }
85
+ - { name: 🇺🇸美国洛杉矶01-0.01倍, type: vless, server: 23.227.38.0, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, network: ws, ws-opts: { path: /pq/us1, headers: { Host: us1_pq_user_vip_api.pqvip.top } } }
86
+ - { name: 🇺🇸美国洛杉矶02-0.01倍, type: vless, server: 23.227.38.0, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, network: ws, ws-opts: { path: /pq/us2, headers: { Host: us2_pq_user_vip_api.pqvip.top } } }
87
+ - { name: 🇺🇸美国洛杉矶03-0.01倍, type: vless, server: 23.227.38.0, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, network: ws, ws-opts: { path: /pq/us3, headers: { Host: us3_pq_user_vip_api.pqvip.top } } }
88
+ - { name: 🇺🇸美国洛杉矶04-0.01倍, type: vless, server: 23.227.38.0, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: chrome, network: ws, ws-opts: { path: /pq/us4, headers: { Host: us4_pq_user_vip_api.pqvip.top } } }
89
+ - { name: 🇺🇸美国洛杉矶05-0.01倍, type: vless, server: 23.227.38.0, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: '', client-fingerprint: ios, network: ws, ws-opts: { path: /pq/us5, headers: { Host: us5_pq_user_vip_api.pqvip.top } } }
90
+ - { name: '🇺🇸美国圣何塞01 | 三网推荐', type: vless, server: 146.235.208.162, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: ios, servername: dcpqjs.yydjc.top }
91
+ - { name: '🇺🇸美国圣何塞02 | 三网推荐', type: vless, server: 64.181.225.150, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: ios, servername: dcpqjs.yydjc.top }
92
+ - { name: '🇺🇸美国圣何塞03 | 三网推荐', type: vless, server: 192.9.129.206, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: ios, servername: dcpqjs.yydjc.top }
93
+ - { name: '🇺🇸美国圣何塞04 | 三网推荐', type: vless, server: 64.181.233.90, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: ios, servername: dcpqjs.yydjc.top }
94
+ - { name: '🇺🇸美国圣何塞05 | 三网推荐', type: vless, server: 146.235.228.34, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: ios, servername: dcpqjs.yydjc.top }
95
+ - { name: 🇸🇬新加坡, type: vless, server: 140.245.43.199, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: tv.apple.com, reality-opts: { public-key: Wmz2q2twfWYu6FrO5xU1M87LO5KjkyGif0AuPLNMPW0, short-id: a86103965ac913f2 } }
96
+ - { name: 🇸🇬新加坡2, type: vless, server: 140.245.58.56, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: tv.apple.com, reality-opts: { public-key: Wmz2q2twfWYu6FrO5xU1M87LO5KjkyGif0AuPLNMPW0, short-id: a86103965ac913f2 } }
97
+ - { name: 🇸🇬新加坡3, type: vless, server: 213.35.108.22, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: buylite.music.apple.com, reality-opts: { public-key: IPno1w9GUxhRN2JY_yIka2DNlnuJHQeb3_0yOCM4pQg, short-id: 3ae54db4833c } }
98
+ - { name: 🇸🇬新加坡4, type: vless, server: 168.138.165.109, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: ios, servername: buylite.music.apple.com, reality-opts: { public-key: kR-_5J2EPiB9nrdgyXjgzfJQnxQodkGeV_jgKOEnlEM, short-id: fc814374abba } }
99
+ - { name: 🇯🇵日本东京, type: vless, server: 152.70.99.180, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: buylite.music.apple.com, reality-opts: { public-key: IHJ7w7m13QsYPFq8eppdqyTyeLmXzTtOG9EGu_-ep2c, short-id: 49824283a241 } }
100
+ - { name: 🇬🇧英国伦敦, type: vless, server: 130.162.163.216, port: 443, uuid: c193b455-1a17-43fa-bd5e-f467208b6747, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: buylite.tv.apple.com, reality-opts: { public-key: b5WuWodd3tB9cHWUJuoEFNvgYVOcLuDyjaxHy70umU4, short-id: '50211843' } }
101
+ proxy-groups:
102
+ - { name: 赔钱机场, type: select, proxies: [自动选择, 故障转移, '剩余流量:88.25 GB', '距离下次重置剩余:6 天', 套餐到期:2025-10-10, '🇸🇬AWS新加坡01 | 高速专线推荐', '🇸🇬AWS新加坡02 | 高速专线推荐', '🇸🇬AWS新加坡03 | 高速专线推荐', '🇸🇬AWS新加坡04 | 高速专线推荐', '🇯🇵AWS日本东京01 | 高速专线推荐', '🇯🇵AWS日本东京02 | 高速专线推荐', '🇯🇵AWS日本东京03 | 高速专线推荐', 🇺🇸美国圣何塞0.1倍-可长期下载, 🇺🇸美国圣何塞01-hy2, 🇺🇸美国圣何塞02-hy2, 🇺🇸美国圣何塞03-hy2, 🇺🇸美国圣何塞04-hy2, 🇺🇸美国圣何塞05-hy2, 🇺🇸美国圣何塞06-hy2, 🇺🇸美国圣何塞07-hy2, 🇺🇸美国圣何塞08-hy2, 🇺🇸美国圣何塞09-hy2, 🇺🇸美国圣何塞10-hy2, '🇦🇪迪拜2 | 高速专线-hy2', '🇰🇷韩国-1倍 | 高速专线-hy2', '🇰🇷韩国3-1倍 | 高速专线-hy2', '🇰🇷韩国4-1倍 | 高速专线-hy2', '🇰🇷韩国5-1倍 | 高速专线-hy2', '🇰🇷韩国6-2倍 | 高速专线-hy2', '🇰🇷韩国7-2倍 | 高速专线-hy2', '🇰🇷韩国8-2倍 | 高速专线-hy2', '🇰🇷韩国9-2倍 | 高速专线-hy2', '🇰🇷韩国10-2倍 | 高速专线-hy2', '🇩🇪德国 | 高速专线-hy2', '🇩🇪德国2 | 高速专线-hy2', '🇨🇭瑞士苏黎世 | 高速专线-hy2', '🇦🇺澳大利亚 | 高速专线-hy2', '🇦🇺澳大利亚2 | 高速专线-hy2', '🇦🇺澳大利亚3 | 高速专线-hy2', '🇸🇬新加坡 | 高速专线-hy2', '🇸🇬新加坡2 | 高速专线-hy2', '🇸🇬新加坡3 | 高速专线-hy2', '🇸🇬新加坡4 | 高速专线-hy2', '🇸🇬新加坡5 | 高速专线-hy2', '🇸🇬新加坡6 | 高速专线-hy2', '🇺🇸美国凤凰城 | 高速专线-hy2', 🇺🇸美国阿什本02-0.1倍, 🇺🇸美国阿什本03-0.1倍, 🇺🇸美国阿什本04-0.1倍, 🇺🇸美国阿什本05-0.1倍, 🇺🇸美国阿什本06-0.1倍, 🇺🇸美国阿什本07-0.1倍, 🇰🇷韩国首尔01, 🇰🇷韩国首尔02, 🇰🇷韩国首尔03, '🇭🇰香港HKT1 | 三网推荐', '🇭🇰香港HKT2 | 三网推荐', '🇭🇰香港HKT3 | 三网推荐', '🇭🇰香港HKT4 | 三网推荐', '🇹🇼台湾 | 避免晚高峰使用', '🇹🇼台湾2 | 避免晚高峰使用', '🇹🇼台湾3 | 避免晚高峰使用', 🇯🇵日本高速01-0.1倍, 🇮🇳印度01-0.01倍, 🇮🇳印度02-0.01倍, 🇮🇳印度03-0.01倍, 🇮🇳印度04-0.01倍, 🇺🇸美国洛杉矶01-0.01倍, 🇺🇸美国洛杉矶02-0.01倍, 🇺🇸美国洛杉矶03-0.01倍, 🇺🇸美国洛杉矶04-0.01倍, 🇺🇸美国洛杉矶05-0.01倍, '🇺🇸美国圣何塞01 | 三网推荐', '🇺🇸美国圣何塞02 | 三网推荐', '🇺🇸美国圣何塞03 | 三网推荐', '🇺🇸美国圣何塞04 | 三网推荐', '🇺🇸美国圣何塞05 | 三网推荐', 🇸🇬新加坡, 🇸🇬新加坡2, 🇸🇬新加坡3, 🇸🇬新加坡4, 🇯🇵日本东京, 🇬🇧英国伦敦] }
103
+ - { name: 自动选择, type: url-test, proxies: ['剩余流量:88.25 GB', '距离下次重置剩余:6 天', 套餐到期:2025-10-10, '🇸🇬AWS新加坡01 | 高速专线推荐', '🇸🇬AWS新加坡02 | 高速专线推荐', '🇸🇬AWS新加坡03 | 高速专线推荐', '🇸🇬AWS新加坡04 | 高速专线推荐', '🇯🇵AWS日本东京01 | 高速专线推荐', '🇯🇵AWS日本东京02 | 高速专线推荐', '🇯🇵AWS日本东京03 | 高速专线推荐', 🇺🇸美国圣何塞0.1倍-可长期下载, 🇺🇸美国圣何塞01-hy2, 🇺🇸美国圣何塞02-hy2, 🇺🇸美国圣何塞03-hy2, 🇺🇸美国圣何塞04-hy2, 🇺🇸美国圣何塞05-hy2, 🇺🇸美国圣何塞06-hy2, 🇺🇸美国圣何塞07-hy2, 🇺🇸美国圣何塞08-hy2, 🇺🇸美国圣何塞09-hy2, 🇺🇸美国圣何塞10-hy2, '🇦🇪迪拜2 | 高速专线-hy2', '🇰🇷韩国-1倍 | 高速专线-hy2', '🇰🇷韩国3-1倍 | 高速专线-hy2', '🇰🇷韩国4-1倍 | 高速专线-hy2', '🇰🇷韩国5-1倍 | 高速专线-hy2', '🇰🇷韩国6-2倍 | 高速专线-hy2', '🇰🇷韩国7-2倍 | 高速专线-hy2', '🇰🇷韩国8-2倍 | 高速专线-hy2', '🇰🇷韩国9-2倍 | 高速专线-hy2', '🇰🇷韩国10-2倍 | 高速专线-hy2', '🇩🇪德国 | 高速专线-hy2', '🇩🇪德国2 | 高速专线-hy2', '🇨🇭瑞士苏黎世 | 高速专线-hy2', '🇦🇺澳大利亚 | 高速专线-hy2', '🇦🇺澳大利亚2 | 高速专线-hy2', '🇦🇺澳大利亚3 | 高速专线-hy2', '🇸🇬新加坡 | 高速专线-hy2', '🇸🇬新加坡2 | 高速专线-hy2', '🇸🇬新加坡3 | 高速专线-hy2', '🇸🇬新加坡4 | 高速专线-hy2', '🇸🇬新加坡5 | 高速专线-hy2', '🇸🇬新加坡6 | 高速专线-hy2', '🇺🇸美国凤凰城 | 高速专线-hy2', 🇺🇸美国阿什本02-0.1倍, 🇺🇸美国阿什本03-0.1倍, 🇺🇸美国阿什本04-0.1倍, 🇺🇸美国阿什本05-0.1倍, 🇺🇸美国阿什本06-0.1倍, 🇺🇸美国阿什本07-0.1倍, 🇰🇷韩国首尔01, 🇰🇷韩国首尔02, 🇰🇷韩国首尔03, '🇭🇰香港HKT1 | 三网推荐', '🇭🇰香港HKT2 | 三网推荐', '🇭🇰香港HKT3 | 三网推荐', '🇭🇰香港HKT4 | 三网推荐', '🇹🇼台湾 | 避免晚高峰使用', '🇹🇼台湾2 | 避免晚高峰使用', '🇹🇼台湾3 | 避免晚高峰使用', 🇯🇵日本高速01-0.1倍, 🇮🇳印度01-0.01倍, 🇮🇳印度02-0.01倍, 🇮🇳印度03-0.01倍, 🇮🇳印度04-0.01倍, 🇺🇸美国洛杉矶01-0.01倍, 🇺🇸美国洛杉矶02-0.01倍, 🇺🇸美国洛杉矶03-0.01倍, 🇺🇸美国洛杉矶04-0.01倍, 🇺🇸美国洛杉矶05-0.01倍, '🇺🇸美国圣何塞01 | 三网推荐', '🇺🇸美国圣何塞02 | 三网推荐', '🇺🇸美国圣何塞03 | 三网推荐', '🇺🇸美国圣何塞04 | 三网推荐', '🇺🇸美国圣何塞05 | 三网推荐', 🇸🇬新加坡, 🇸🇬新加坡2, 🇸🇬新加坡3, 🇸🇬新加坡4, 🇯🇵日本东京, 🇬🇧英国伦敦], url: 'http://www.gstatic.com/generate_204', interval: 86400 }
104
+ - { name: 故障转移, type: fallback, proxies: ['剩余流量:88.25 GB', '距离下次重置剩余:6 天', 套餐到期:2025-10-10, '🇸🇬AWS新加坡01 | 高速专线推荐', '🇸🇬AWS新加坡02 | 高速专线推荐', '🇸🇬AWS新加坡03 | 高速专线推荐', '🇸🇬AWS新加坡04 | 高速专线推荐', '🇯🇵AWS日本东京01 | 高速专线推荐', '🇯🇵AWS日本东京02 | 高速专线推荐', '🇯🇵AWS日本东京03 | 高速专线推荐', 🇺🇸美国圣何塞0.1倍-可长期下载, 🇺🇸美国圣何塞01-hy2, 🇺🇸美国圣何塞02-hy2, 🇺🇸美国圣何塞03-hy2, 🇺🇸美国圣何塞04-hy2, 🇺🇸美国圣何塞05-hy2, 🇺🇸美国圣何塞06-hy2, 🇺🇸美国圣何塞07-hy2, 🇺🇸美国圣何塞08-hy2, 🇺🇸美国圣何塞09-hy2, 🇺🇸美国圣何塞10-hy2, '🇦🇪迪拜2 | 高速专线-hy2', '🇰🇷韩国-1倍 | 高速专线-hy2', '🇰🇷韩国3-1倍 | 高速专线-hy2', '🇰🇷韩国4-1倍 | 高速专线-hy2', '🇰🇷韩国5-1倍 | 高速专线-hy2', '🇰🇷韩国6-2倍 | 高速专线-hy2', '🇰🇷韩国7-2倍 | 高速专线-hy2', '🇰🇷韩国8-2倍 | 高速专线-hy2', '🇰🇷韩国9-2倍 | 高速专线-hy2', '🇰🇷韩国10-2倍 | 高速专线-hy2', '🇩🇪德国 | 高速专线-hy2', '🇩🇪德国2 | 高速专线-hy2', '🇨🇭瑞士苏黎世 | 高速专线-hy2', '🇦🇺澳大利亚 | 高速专线-hy2', '🇦🇺澳大利亚2 | 高速专线-hy2', '🇦🇺澳大利亚3 | 高速专线-hy2', '🇸🇬新加坡 | 高速专线-hy2', '🇸🇬新加坡2 | 高速专线-hy2', '🇸🇬新加坡3 | 高速专线-hy2', '🇸🇬新加坡4 | 高速专线-hy2', '🇸🇬新加坡5 | 高速专线-hy2', '🇸🇬新加坡6 | 高速专线-hy2', '🇺🇸美国凤凰城 | 高速专线-hy2', 🇺🇸美国阿什本02-0.1倍, 🇺🇸美国阿什本03-0.1倍, 🇺🇸美国阿什本04-0.1倍, 🇺🇸美国阿什本05-0.1倍, 🇺🇸美国阿什本06-0.1倍, 🇺🇸美国阿什本07-0.1倍, 🇰🇷韩国首尔01, 🇰🇷韩国首尔02, 🇰🇷韩国首尔03, '🇭🇰香港HKT1 | 三网推荐', '🇭🇰香港HKT2 | 三网推荐', '🇭🇰香港HKT3 | 三网推荐', '🇭🇰香港HKT4 | 三网推荐', '🇹🇼台湾 | 避免晚高峰使用', '🇹🇼台湾2 | 避免晚高峰使用', '🇹🇼台湾3 | 避免晚高峰使用', 🇯🇵日本高速01-0.1倍, 🇮🇳印度01-0.01倍, 🇮🇳印度02-0.01倍, 🇮🇳印度03-0.01倍, 🇮🇳印度04-0.01倍, 🇺🇸美国洛杉矶01-0.01倍, 🇺🇸美国洛杉矶02-0.01倍, 🇺🇸美国洛杉矶03-0.01倍, 🇺🇸美国洛杉矶04-0.01倍, 🇺🇸美国洛杉矶05-0.01倍, '🇺🇸美国圣何塞01 | 三网推荐', '🇺🇸美国圣何塞02 | 三网推荐', '🇺🇸美国圣何塞03 | 三网推荐', '🇺🇸美国圣何塞04 | 三网推荐', '🇺🇸美国圣何塞05 | 三网推荐', 🇸🇬新加坡, 🇸🇬新加坡2, 🇸🇬新加坡3, 🇸🇬新加坡4, 🇯🇵日本东京, 🇬🇧英国伦敦], url: 'http://www.gstatic.com/generate_204', interval: 7200 }
105
+ rules:
106
+ - 'DOMAIN-SUFFIX,services.googleapis.cn,赔钱机场'
107
+ - 'DOMAIN-SUFFIX,xn--ngstr-lra8j.com,赔钱机场'
108
+ - 'DOMAIN,safebrowsing.urlsec.qq.com,DIRECT'
109
+ - 'DOMAIN,safebrowsing.googleapis.com,DIRECT'
110
+ - 'DOMAIN,developer.apple.com,赔钱机场'
111
+ - 'DOMAIN-SUFFIX,digicert.com,赔钱机场'
112
+ - 'DOMAIN,ocsp.apple.com,赔钱机场'
113
+ - 'DOMAIN,ocsp.comodoca.com,赔钱机场'
114
+ - 'DOMAIN,ocsp.usertrust.com,赔钱机场'
115
+ - 'DOMAIN,ocsp.sectigo.com,赔钱机场'
116
+ - 'DOMAIN,ocsp.verisign.net,赔钱机场'
117
+ - 'DOMAIN-SUFFIX,apple-dns.net,赔钱机场'
118
+ - 'DOMAIN,testflight.apple.com,赔钱机场'
119
+ - 'DOMAIN,sandbox.itunes.apple.com,赔钱机场'
120
+ - 'DOMAIN,itunes.apple.com,赔钱机场'
121
+ - 'DOMAIN-SUFFIX,apps.apple.com,赔钱机场'
122
+ - 'DOMAIN-SUFFIX,blobstore.apple.com,赔钱机场'
123
+ - 'DOMAIN,cvws.icloud-content.com,赔钱机场'
124
+ - 'DOMAIN-SUFFIX,mzstatic.com,DIRECT'
125
+ - 'DOMAIN-SUFFIX,itunes.apple.com,DIRECT'
126
+ - 'DOMAIN-SUFFIX,icloud.com,DIRECT'
127
+ - 'DOMAIN-SUFFIX,icloud-content.com,DIRECT'
128
+ - 'DOMAIN-SUFFIX,me.com,DIRECT'
129
+ - 'DOMAIN-SUFFIX,aaplimg.com,DIRECT'
130
+ - 'DOMAIN-SUFFIX,cdn20.com,DIRECT'
131
+ - 'DOMAIN-SUFFIX,cdn-apple.com,DIRECT'
132
+ - 'DOMAIN-SUFFIX,akadns.net,DIRECT'
133
+ - 'DOMAIN-SUFFIX,akamaiedge.net,DIRECT'
134
+ - 'DOMAIN-SUFFIX,edgekey.net,DIRECT'
135
+ - 'DOMAIN-SUFFIX,mwcloudcdn.com,DIRECT'
136
+ - 'DOMAIN-SUFFIX,mwcname.com,DIRECT'
137
+ - 'DOMAIN-SUFFIX,apple.com,DIRECT'
138
+ - 'DOMAIN-SUFFIX,apple-cloudkit.com,DIRECT'
139
+ - 'DOMAIN-SUFFIX,apple-mapkit.com,DIRECT'
140
+ - 'DOMAIN-SUFFIX,126.com,DIRECT'
141
+ - 'DOMAIN-SUFFIX,126.net,DIRECT'
142
+ - 'DOMAIN-SUFFIX,127.net,DIRECT'
143
+ - 'DOMAIN-SUFFIX,163.com,DIRECT'
144
+ - 'DOMAIN-SUFFIX,360buyimg.com,DIRECT'
145
+ - 'DOMAIN-SUFFIX,36kr.com,DIRECT'
146
+ - 'DOMAIN-SUFFIX,acfun.tv,DIRECT'
147
+ - 'DOMAIN-SUFFIX,air-matters.com,DIRECT'
148
+ - 'DOMAIN-SUFFIX,aixifan.com,DIRECT'
149
+ - 'DOMAIN-KEYWORD,alicdn,DIRECT'
150
+ - 'DOMAIN-KEYWORD,alipay,DIRECT'
151
+ - 'DOMAIN-KEYWORD,taobao,DIRECT'
152
+ - 'DOMAIN-SUFFIX,amap.com,DIRECT'
153
+ - 'DOMAIN-SUFFIX,autonavi.com,DIRECT'
154
+ - 'DOMAIN-KEYWORD,baidu,DIRECT'
155
+ - 'DOMAIN-SUFFIX,bdimg.com,DIRECT'
156
+ - 'DOMAIN-SUFFIX,bdstatic.com,DIRECT'
157
+ - 'DOMAIN-SUFFIX,bilibili.com,DIRECT'
158
+ - 'DOMAIN-SUFFIX,bilivideo.com,DIRECT'
159
+ - 'DOMAIN-SUFFIX,caiyunapp.com,DIRECT'
160
+ - 'DOMAIN-SUFFIX,clouddn.com,DIRECT'
161
+ - 'DOMAIN-SUFFIX,cnbeta.com,DIRECT'
162
+ - 'DOMAIN-SUFFIX,cnbetacdn.com,DIRECT'
163
+ - 'DOMAIN-SUFFIX,cootekservice.com,DIRECT'
164
+ - 'DOMAIN-SUFFIX,csdn.net,DIRECT'
165
+ - 'DOMAIN-SUFFIX,ctrip.com,DIRECT'
166
+ - 'DOMAIN-SUFFIX,dgtle.com,DIRECT'
167
+ - 'DOMAIN-SUFFIX,dianping.com,DIRECT'
168
+ - 'DOMAIN-SUFFIX,douban.com,DIRECT'
169
+ - 'DOMAIN-SUFFIX,doubanio.com,DIRECT'
170
+ - 'DOMAIN-SUFFIX,duokan.com,DIRECT'
171
+ - 'DOMAIN-SUFFIX,easou.com,DIRECT'
172
+ - 'DOMAIN-SUFFIX,ele.me,DIRECT'
173
+ - 'DOMAIN-SUFFIX,feng.com,DIRECT'
174
+ - 'DOMAIN-SUFFIX,fir.im,DIRECT'
175
+ - 'DOMAIN-SUFFIX,frdic.com,DIRECT'
176
+ - 'DOMAIN-SUFFIX,g-cores.com,DIRECT'
177
+ - 'DOMAIN-SUFFIX,godic.net,DIRECT'
178
+ - 'DOMAIN-SUFFIX,gtimg.com,DIRECT'
179
+ - 'DOMAIN,cdn.hockeyapp.net,DIRECT'
180
+ - 'DOMAIN-SUFFIX,hongxiu.com,DIRECT'
181
+ - 'DOMAIN-SUFFIX,hxcdn.net,DIRECT'
182
+ - 'DOMAIN-SUFFIX,iciba.com,DIRECT'
183
+ - 'DOMAIN-SUFFIX,ifeng.com,DIRECT'
184
+ - 'DOMAIN-SUFFIX,ifengimg.com,DIRECT'
185
+ - 'DOMAIN-SUFFIX,ipip.net,DIRECT'
186
+ - 'DOMAIN-SUFFIX,iqiyi.com,DIRECT'
187
+ - 'DOMAIN-SUFFIX,jd.com,DIRECT'
188
+ - 'DOMAIN-SUFFIX,jianshu.com,DIRECT'
189
+ - 'DOMAIN-SUFFIX,knewone.com,DIRECT'
190
+ - 'DOMAIN-SUFFIX,le.com,DIRECT'
191
+ - 'DOMAIN-SUFFIX,lecloud.com,DIRECT'
192
+ - 'DOMAIN-SUFFIX,lemicp.com,DIRECT'
193
+ - 'DOMAIN-SUFFIX,licdn.com,DIRECT'
194
+ - 'DOMAIN-SUFFIX,luoo.net,DIRECT'
195
+ - 'DOMAIN-SUFFIX,meituan.com,DIRECT'
196
+ - 'DOMAIN-SUFFIX,meituan.net,DIRECT'
197
+ - 'DOMAIN-SUFFIX,mi.com,DIRECT'
198
+ - 'DOMAIN-SUFFIX,miaopai.com,DIRECT'
199
+ - 'DOMAIN-SUFFIX,microsoft.com,DIRECT'
200
+ - 'DOMAIN-SUFFIX,microsoftonline.com,DIRECT'
201
+ - 'DOMAIN-SUFFIX,miui.com,DIRECT'
202
+ - 'DOMAIN-SUFFIX,miwifi.com,DIRECT'
203
+ - 'DOMAIN-SUFFIX,mob.com,DIRECT'
204
+ - 'DOMAIN-SUFFIX,netease.com,DIRECT'
205
+ - 'DOMAIN-SUFFIX,office.com,DIRECT'
206
+ - 'DOMAIN-SUFFIX,office365.com,DIRECT'
207
+ - 'DOMAIN-KEYWORD,officecdn,DIRECT'
208
+ - 'DOMAIN-SUFFIX,oschina.net,DIRECT'
209
+ - 'DOMAIN-SUFFIX,ppsimg.com,DIRECT'
210
+ - 'DOMAIN-SUFFIX,pstatp.com,DIRECT'
211
+ - 'DOMAIN-SUFFIX,qcloud.com,DIRECT'
212
+ - 'DOMAIN-SUFFIX,qdaily.com,DIRECT'
213
+ - 'DOMAIN-SUFFIX,qdmm.com,DIRECT'
214
+ - 'DOMAIN-SUFFIX,qhimg.com,DIRECT'
215
+ - 'DOMAIN-SUFFIX,qhres.com,DIRECT'
216
+ - 'DOMAIN-SUFFIX,qidian.com,DIRECT'
217
+ - 'DOMAIN-SUFFIX,qihucdn.com,DIRECT'
218
+ - 'DOMAIN-SUFFIX,qiniu.com,DIRECT'
219
+ - 'DOMAIN-SUFFIX,qiniucdn.com,DIRECT'
220
+ - 'DOMAIN-SUFFIX,qiyipic.com,DIRECT'
221
+ - 'DOMAIN-SUFFIX,qq.com,DIRECT'
222
+ - 'DOMAIN-SUFFIX,qqurl.com,DIRECT'
223
+ - 'DOMAIN-SUFFIX,rarbg.to,DIRECT'
224
+ - 'DOMAIN-SUFFIX,ruguoapp.com,DIRECT'
225
+ - 'DOMAIN-SUFFIX,segmentfault.com,DIRECT'
226
+ - 'DOMAIN-SUFFIX,sinaapp.com,DIRECT'
227
+ - 'DOMAIN-SUFFIX,smzdm.com,DIRECT'
228
+ - 'DOMAIN-SUFFIX,snapdrop.net,DIRECT'
229
+ - 'DOMAIN-SUFFIX,sogou.com,DIRECT'
230
+ - 'DOMAIN-SUFFIX,sogoucdn.com,DIRECT'
231
+ - 'DOMAIN-SUFFIX,sohu.com,DIRECT'
232
+ - 'DOMAIN-SUFFIX,soku.com,DIRECT'
233
+ - 'DOMAIN-SUFFIX,speedtest.net,DIRECT'
234
+ - 'DOMAIN-SUFFIX,sspai.com,DIRECT'
235
+ - 'DOMAIN-SUFFIX,suning.com,DIRECT'
236
+ - 'DOMAIN-SUFFIX,taobao.com,DIRECT'
237
+ - 'DOMAIN-SUFFIX,tencent.com,DIRECT'
238
+ - 'DOMAIN-SUFFIX,tenpay.com,DIRECT'
239
+ - 'DOMAIN-SUFFIX,tianyancha.com,DIRECT'
240
+ - 'DOMAIN-SUFFIX,tmall.com,DIRECT'
241
+ - 'DOMAIN-SUFFIX,tudou.com,DIRECT'
242
+ - 'DOMAIN-SUFFIX,umetrip.com,DIRECT'
243
+ - 'DOMAIN-SUFFIX,upaiyun.com,DIRECT'
244
+ - 'DOMAIN-SUFFIX,upyun.com,DIRECT'
245
+ - 'DOMAIN-SUFFIX,veryzhun.com,DIRECT'
246
+ - 'DOMAIN-SUFFIX,weather.com,DIRECT'
247
+ - 'DOMAIN-SUFFIX,weibo.com,DIRECT'
248
+ - 'DOMAIN-SUFFIX,xiami.com,DIRECT'
249
+ - 'DOMAIN-SUFFIX,xiami.net,DIRECT'
250
+ - 'DOMAIN-SUFFIX,xiaomicp.com,DIRECT'
251
+ - 'DOMAIN-SUFFIX,ximalaya.com,DIRECT'
252
+ - 'DOMAIN-SUFFIX,xmcdn.com,DIRECT'
253
+ - 'DOMAIN-SUFFIX,xunlei.com,DIRECT'
254
+ - 'DOMAIN-SUFFIX,yhd.com,DIRECT'
255
+ - 'DOMAIN-SUFFIX,yihaodianimg.com,DIRECT'
256
+ - 'DOMAIN-SUFFIX,yinxiang.com,DIRECT'
257
+ - 'DOMAIN-SUFFIX,ykimg.com,DIRECT'
258
+ - 'DOMAIN-SUFFIX,youdao.com,DIRECT'
259
+ - 'DOMAIN-SUFFIX,youku.com,DIRECT'
260
+ - 'DOMAIN-SUFFIX,zealer.com,DIRECT'
261
+ - 'DOMAIN-SUFFIX,zhihu.com,DIRECT'
262
+ - 'DOMAIN-SUFFIX,zhimg.com,DIRECT'
263
+ - 'DOMAIN-SUFFIX,zimuzu.tv,DIRECT'
264
+ - 'DOMAIN-SUFFIX,zoho.com,DIRECT'
265
+ - 'DOMAIN-KEYWORD,amazon,赔钱机场'
266
+ - 'DOMAIN-KEYWORD,google,赔钱机场'
267
+ - 'DOMAIN-KEYWORD,gmail,赔钱机场'
268
+ - 'DOMAIN-KEYWORD,youtube,赔钱机场'
269
+ - 'DOMAIN-KEYWORD,facebook,赔钱机场'
270
+ - 'DOMAIN-SUFFIX,fb.me,赔钱机场'
271
+ - 'DOMAIN-SUFFIX,fbcdn.net,赔钱机场'
272
+ - 'DOMAIN-KEYWORD,twitter,赔钱机场'
273
+ - 'DOMAIN-KEYWORD,instagram,赔钱机场'
274
+ - 'DOMAIN-KEYWORD,dropbox,赔钱机场'
275
+ - 'DOMAIN-SUFFIX,twimg.com,赔钱机场'
276
+ - 'DOMAIN-KEYWORD,blogspot,赔钱机场'
277
+ - 'DOMAIN-SUFFIX,youtu.be,赔钱机场'
278
+ - 'DOMAIN-KEYWORD,whatsapp,赔钱机场'
279
+ - 'DOMAIN-KEYWORD,admarvel,REJECT'
280
+ - 'DOMAIN-KEYWORD,admaster,REJECT'
281
+ - 'DOMAIN-KEYWORD,adsage,REJECT'
282
+ - 'DOMAIN-KEYWORD,adsmogo,REJECT'
283
+ - 'DOMAIN-KEYWORD,adsrvmedia,REJECT'
284
+ - 'DOMAIN-KEYWORD,adwords,REJECT'
285
+ - 'DOMAIN-KEYWORD,adservice,REJECT'
286
+ - 'DOMAIN-SUFFIX,appsflyer.com,REJECT'
287
+ - 'DOMAIN-KEYWORD,domob,REJECT'
288
+ - 'DOMAIN-SUFFIX,doubleclick.net,REJECT'
289
+ - 'DOMAIN-KEYWORD,duomeng,REJECT'
290
+ - 'DOMAIN-KEYWORD,dwtrack,REJECT'
291
+ - 'DOMAIN-KEYWORD,guanggao,REJECT'
292
+ - 'DOMAIN-KEYWORD,lianmeng,REJECT'
293
+ - 'DOMAIN-SUFFIX,mmstat.com,REJECT'
294
+ - 'DOMAIN-KEYWORD,mopub,REJECT'
295
+ - 'DOMAIN-KEYWORD,omgmta,REJECT'
296
+ - 'DOMAIN-KEYWORD,openx,REJECT'
297
+ - 'DOMAIN-KEYWORD,partnerad,REJECT'
298
+ - 'DOMAIN-KEYWORD,pingfore,REJECT'
299
+ - 'DOMAIN-KEYWORD,supersonicads,REJECT'
300
+ - 'DOMAIN-KEYWORD,uedas,REJECT'
301
+ - 'DOMAIN-KEYWORD,umeng,REJECT'
302
+ - 'DOMAIN-KEYWORD,usage,REJECT'
303
+ - 'DOMAIN-SUFFIX,vungle.com,REJECT'
304
+ - 'DOMAIN-KEYWORD,wlmonitor,REJECT'
305
+ - 'DOMAIN-KEYWORD,zjtoolbar,REJECT'
306
+ - 'DOMAIN-SUFFIX,9to5mac.com,赔钱机场'
307
+ - 'DOMAIN-SUFFIX,abpchina.org,赔钱机场'
308
+ - 'DOMAIN-SUFFIX,adblockplus.org,赔钱机场'
309
+ - 'DOMAIN-SUFFIX,adobe.com,赔钱机场'
310
+ - 'DOMAIN-SUFFIX,akamaized.net,赔钱机场'
311
+ - 'DOMAIN-SUFFIX,alfredapp.com,赔钱机场'
312
+ - 'DOMAIN-SUFFIX,amplitude.com,赔钱机场'
313
+ - 'DOMAIN-SUFFIX,ampproject.org,赔钱机场'
314
+ - 'DOMAIN-SUFFIX,android.com,赔钱机场'
315
+ - 'DOMAIN-SUFFIX,angularjs.org,赔钱机场'
316
+ - 'DOMAIN-SUFFIX,aolcdn.com,赔钱机场'
317
+ - 'DOMAIN-SUFFIX,apkpure.com,赔钱机场'
318
+ - 'DOMAIN-SUFFIX,appledaily.com,赔钱机场'
319
+ - 'DOMAIN-SUFFIX,appshopper.com,赔钱机场'
320
+ - 'DOMAIN-SUFFIX,appspot.com,赔钱机场'
321
+ - 'DOMAIN-SUFFIX,arcgis.com,赔钱机场'
322
+ - 'DOMAIN-SUFFIX,archive.org,赔钱机场'
323
+ - 'DOMAIN-SUFFIX,armorgames.com,赔钱机场'
324
+ - 'DOMAIN-SUFFIX,aspnetcdn.com,赔钱机场'
325
+ - 'DOMAIN-SUFFIX,att.com,赔钱机场'
326
+ - 'DOMAIN-SUFFIX,awsstatic.com,赔钱机场'
327
+ - 'DOMAIN-SUFFIX,azureedge.net,赔钱机场'
328
+ - 'DOMAIN-SUFFIX,azurewebsites.net,赔钱机场'
329
+ - 'DOMAIN-SUFFIX,bing.com,赔钱机场'
330
+ - 'DOMAIN-SUFFIX,bintray.com,赔钱机场'
331
+ - 'DOMAIN-SUFFIX,bit.com,赔钱机场'
332
+ - 'DOMAIN-SUFFIX,bit.ly,赔钱机场'
333
+ - 'DOMAIN-SUFFIX,bitbucket.org,赔钱机场'
334
+ - 'DOMAIN-SUFFIX,bjango.com,赔钱机场'
335
+ - 'DOMAIN-SUFFIX,bkrtx.com,赔钱机场'
336
+ - 'DOMAIN-SUFFIX,blog.com,赔钱机场'
337
+ - 'DOMAIN-SUFFIX,blogcdn.com,赔钱机场'
338
+ - 'DOMAIN-SUFFIX,blogger.com,赔钱机场'
339
+ - 'DOMAIN-SUFFIX,blogsmithmedia.com,赔钱机场'
340
+ - 'DOMAIN-SUFFIX,blogspot.com,赔钱机场'
341
+ - 'DOMAIN-SUFFIX,blogspot.hk,赔钱机场'
342
+ - 'DOMAIN-SUFFIX,bloomberg.com,赔钱机场'
343
+ - 'DOMAIN-SUFFIX,box.com,赔钱机场'
344
+ - 'DOMAIN-SUFFIX,box.net,赔钱机场'
345
+ - 'DOMAIN-SUFFIX,cachefly.net,赔钱机场'
346
+ - 'DOMAIN-SUFFIX,chromium.org,赔钱机场'
347
+ - 'DOMAIN-SUFFIX,cl.ly,赔钱机场'
348
+ - 'DOMAIN-SUFFIX,cloudflare.com,赔钱机场'
349
+ - 'DOMAIN-SUFFIX,cloudfront.net,赔钱机场'
350
+ - 'DOMAIN-SUFFIX,cloudmagic.com,赔钱机场'
351
+ - 'DOMAIN-SUFFIX,cmail19.com,赔钱机场'
352
+ - 'DOMAIN-SUFFIX,cnet.com,赔钱机场'
353
+ - 'DOMAIN-SUFFIX,cocoapods.org,赔钱机场'
354
+ - 'DOMAIN-SUFFIX,comodoca.com,赔钱机场'
355
+ - 'DOMAIN-SUFFIX,crashlytics.com,赔钱机场'
356
+ - 'DOMAIN-SUFFIX,culturedcode.com,赔钱机场'
357
+ - 'DOMAIN-SUFFIX,d.pr,赔钱机场'
358
+ - 'DOMAIN-SUFFIX,danilo.to,赔钱机场'
359
+ - 'DOMAIN-SUFFIX,dayone.me,赔钱机场'
360
+ - 'DOMAIN-SUFFIX,db.tt,赔钱机场'
361
+ - 'DOMAIN-SUFFIX,deskconnect.com,赔钱机场'
362
+ - 'DOMAIN-SUFFIX,disq.us,赔钱机场'
363
+ - 'DOMAIN-SUFFIX,disqus.com,赔钱机场'
364
+ - 'DOMAIN-SUFFIX,disquscdn.com,赔钱机场'
365
+ - 'DOMAIN-SUFFIX,dnsimple.com,赔钱机场'
366
+ - 'DOMAIN-SUFFIX,docker.com,赔钱机场'
367
+ - 'DOMAIN-SUFFIX,dribbble.com,赔钱机场'
368
+ - 'DOMAIN-SUFFIX,droplr.com,赔钱机场'
369
+ - 'DOMAIN-SUFFIX,duckduckgo.com,赔钱机场'
370
+ - 'DOMAIN-SUFFIX,dueapp.com,赔钱机场'
371
+ - 'DOMAIN-SUFFIX,dytt8.net,赔钱机场'
372
+ - 'DOMAIN-SUFFIX,edgecastcdn.net,赔钱机场'
373
+ - 'DOMAIN-SUFFIX,edgekey.net,赔钱机场'
374
+ - 'DOMAIN-SUFFIX,edgesuite.net,赔钱机场'
375
+ - 'DOMAIN-SUFFIX,engadget.com,赔钱机场'
376
+ - 'DOMAIN-SUFFIX,entrust.net,赔钱机场'
377
+ - 'DOMAIN-SUFFIX,eurekavpt.com,赔钱机场'
378
+ - 'DOMAIN-SUFFIX,evernote.com,赔钱机场'
379
+ - 'DOMAIN-SUFFIX,fabric.io,赔钱机场'
380
+ - 'DOMAIN-SUFFIX,fast.com,赔钱机场'
381
+ - 'DOMAIN-SUFFIX,fastly.net,赔钱机场'
382
+ - 'DOMAIN-SUFFIX,fc2.com,赔钱机场'
383
+ - 'DOMAIN-SUFFIX,feedburner.com,赔钱机场'
384
+ - 'DOMAIN-SUFFIX,feedly.com,赔钱机场'
385
+ - 'DOMAIN-SUFFIX,feedsportal.com,赔钱机场'
386
+ - 'DOMAIN-SUFFIX,fiftythree.com,赔钱机场'
387
+ - 'DOMAIN-SUFFIX,firebaseio.com,赔钱机场'
388
+ - 'DOMAIN-SUFFIX,flexibits.com,赔钱机场'
389
+ - 'DOMAIN-SUFFIX,flickr.com,赔钱机场'
390
+ - 'DOMAIN-SUFFIX,flipboard.com,赔钱机场'
391
+ - 'DOMAIN-SUFFIX,g.co,赔钱机场'
392
+ - 'DOMAIN-SUFFIX,gabia.net,赔钱机场'
393
+ - 'DOMAIN-SUFFIX,geni.us,赔钱机场'
394
+ - 'DOMAIN-SUFFIX,gfx.ms,赔钱机场'
395
+ - 'DOMAIN-SUFFIX,ggpht.com,赔钱机场'
396
+ - 'DOMAIN-SUFFIX,ghostnoteapp.com,赔钱机场'
397
+ - 'DOMAIN-SUFFIX,git.io,赔钱机场'
398
+ - 'DOMAIN-KEYWORD,github,赔钱机场'
399
+ - 'DOMAIN-SUFFIX,globalsign.com,赔钱机场'
400
+ - 'DOMAIN-SUFFIX,gmodules.com,赔钱机场'
401
+ - 'DOMAIN-SUFFIX,godaddy.com,赔钱机场'
402
+ - 'DOMAIN-SUFFIX,golang.org,赔钱机场'
403
+ - 'DOMAIN-SUFFIX,gongm.in,赔钱机场'
404
+ - 'DOMAIN-SUFFIX,goo.gl,赔钱机场'
405
+ - 'DOMAIN-SUFFIX,goodreaders.com,赔钱机场'
406
+ - 'DOMAIN-SUFFIX,goodreads.com,赔钱机场'
407
+ - 'DOMAIN-SUFFIX,gravatar.com,赔钱机场'
408
+ - 'DOMAIN-SUFFIX,gstatic.com,赔钱机场'
409
+ - 'DOMAIN-SUFFIX,gvt0.com,赔钱机场'
410
+ - 'DOMAIN-SUFFIX,hockeyapp.net,赔钱机场'
411
+ - 'DOMAIN-SUFFIX,hotmail.com,赔钱机场'
412
+ - 'DOMAIN-SUFFIX,icons8.com,赔钱机场'
413
+ - 'DOMAIN-SUFFIX,ifixit.com,赔钱机场'
414
+ - 'DOMAIN-SUFFIX,ift.tt,赔钱机场'
415
+ - 'DOMAIN-SUFFIX,ifttt.com,赔钱机场'
416
+ - 'DOMAIN-SUFFIX,iherb.com,赔钱机场'
417
+ - 'DOMAIN-SUFFIX,imageshack.us,赔钱机场'
418
+ - 'DOMAIN-SUFFIX,img.ly,赔钱机场'
419
+ - 'DOMAIN-SUFFIX,imgur.com,赔钱机场'
420
+ - 'DOMAIN-SUFFIX,imore.com,赔钱机场'
421
+ - 'DOMAIN-SUFFIX,instapaper.com,赔钱机场'
422
+ - 'DOMAIN-SUFFIX,ipn.li,赔钱机场'
423
+ - 'DOMAIN-SUFFIX,is.gd,赔钱机场'
424
+ - 'DOMAIN-SUFFIX,issuu.com,赔钱机场'
425
+ - 'DOMAIN-SUFFIX,itgonglun.com,赔钱机场'
426
+ - 'DOMAIN-SUFFIX,itun.es,赔钱机场'
427
+ - 'DOMAIN-SUFFIX,ixquick.com,赔钱机场'
428
+ - 'DOMAIN-SUFFIX,j.mp,赔钱机场'
429
+ - 'DOMAIN-SUFFIX,js.revsci.net,赔钱机场'
430
+ - 'DOMAIN-SUFFIX,jshint.com,赔钱机场'
431
+ - 'DOMAIN-SUFFIX,jtvnw.net,赔钱机场'
432
+ - 'DOMAIN-SUFFIX,justgetflux.com,赔钱机场'
433
+ - 'DOMAIN-SUFFIX,kat.cr,赔钱机场'
434
+ - 'DOMAIN-SUFFIX,klip.me,赔钱机场'
435
+ - 'DOMAIN-SUFFIX,libsyn.com,赔钱机场'
436
+ - 'DOMAIN-SUFFIX,linkedin.com,赔钱机场'
437
+ - 'DOMAIN-SUFFIX,line-apps.com,赔钱机场'
438
+ - 'DOMAIN-SUFFIX,linode.com,赔钱机场'
439
+ - 'DOMAIN-SUFFIX,lithium.com,赔钱机场'
440
+ - 'DOMAIN-SUFFIX,littlehj.com,赔钱机场'
441
+ - 'DOMAIN-SUFFIX,live.com,赔钱机场'
442
+ - 'DOMAIN-SUFFIX,live.net,赔钱机场'
443
+ - 'DOMAIN-SUFFIX,livefilestore.com,赔钱机场'
444
+ - 'DOMAIN-SUFFIX,llnwd.net,赔钱机场'
445
+ - 'DOMAIN-SUFFIX,macid.co,赔钱机场'
446
+ - 'DOMAIN-SUFFIX,macromedia.com,赔钱机场'
447
+ - 'DOMAIN-SUFFIX,macrumors.com,赔钱机场'
448
+ - 'DOMAIN-SUFFIX,mashable.com,赔钱机场'
449
+ - 'DOMAIN-SUFFIX,mathjax.org,赔钱机场'
450
+ - 'DOMAIN-SUFFIX,medium.com,赔钱机场'
451
+ - 'DOMAIN-SUFFIX,mega.co.nz,赔钱机场'
452
+ - 'DOMAIN-SUFFIX,mega.nz,赔钱机场'
453
+ - 'DOMAIN-SUFFIX,megaupload.com,赔钱机场'
454
+ - 'DOMAIN-SUFFIX,microsofttranslator.com,赔钱机场'
455
+ - 'DOMAIN-SUFFIX,mindnode.com,赔钱机场'
456
+ - 'DOMAIN-SUFFIX,mobile01.com,赔钱机场'
457
+ - 'DOMAIN-SUFFIX,modmyi.com,赔钱机场'
458
+ - 'DOMAIN-SUFFIX,msedge.net,赔钱机场'
459
+ - 'DOMAIN-SUFFIX,myfontastic.com,赔钱机场'
460
+ - 'DOMAIN-SUFFIX,name.com,赔钱机场'
461
+ - 'DOMAIN-SUFFIX,nextmedia.com,赔钱机场'
462
+ - 'DOMAIN-SUFFIX,nsstatic.net,赔钱机场'
463
+ - 'DOMAIN-SUFFIX,nssurge.com,赔钱机场'
464
+ - 'DOMAIN-SUFFIX,nyt.com,赔钱机场'
465
+ - 'DOMAIN-SUFFIX,nytimes.com,赔钱机场'
466
+ - 'DOMAIN-SUFFIX,omnigroup.com,赔钱机场'
467
+ - 'DOMAIN-SUFFIX,onedrive.com,赔钱机场'
468
+ - 'DOMAIN-SUFFIX,onenote.com,赔钱机场'
469
+ - 'DOMAIN-SUFFIX,ooyala.com,赔钱机场'
470
+ - 'DOMAIN-SUFFIX,openvpn.net,赔钱机场'
471
+ - 'DOMAIN-SUFFIX,openwrt.org,赔钱机场'
472
+ - 'DOMAIN-SUFFIX,orkut.com,赔钱机场'
473
+ - 'DOMAIN-SUFFIX,osxdaily.com,赔钱机场'
474
+ - 'DOMAIN-SUFFIX,outlook.com,赔钱机场'
475
+ - 'DOMAIN-SUFFIX,ow.ly,赔钱机场'
476
+ - 'DOMAIN-SUFFIX,paddleapi.com,赔钱机场'
477
+ - 'DOMAIN-SUFFIX,parallels.com,赔钱机场'
478
+ - 'DOMAIN-SUFFIX,parse.com,赔钱机场'
479
+ - 'DOMAIN-SUFFIX,pdfexpert.com,赔钱机场'
480
+ - 'DOMAIN-SUFFIX,periscope.tv,赔钱机场'
481
+ - 'DOMAIN-SUFFIX,pinboard.in,赔钱机场'
482
+ - 'DOMAIN-SUFFIX,pinterest.com,赔钱机场'
483
+ - 'DOMAIN-SUFFIX,pixelmator.com,赔钱机场'
484
+ - 'DOMAIN-SUFFIX,pixiv.net,赔钱机场'
485
+ - 'DOMAIN-SUFFIX,playpcesor.com,赔钱机场'
486
+ - 'DOMAIN-SUFFIX,playstation.com,赔钱机场'
487
+ - 'DOMAIN-SUFFIX,playstation.com.hk,赔钱机场'
488
+ - 'DOMAIN-SUFFIX,playstation.net,赔钱机场'
489
+ - 'DOMAIN-SUFFIX,playstationnetwork.com,赔钱机场'
490
+ - 'DOMAIN-SUFFIX,pushwoosh.com,赔钱机场'
491
+ - 'DOMAIN-SUFFIX,rime.im,赔钱机场'
492
+ - 'DOMAIN-SUFFIX,servebom.com,赔钱机场'
493
+ - 'DOMAIN-SUFFIX,sfx.ms,赔钱机场'
494
+ - 'DOMAIN-SUFFIX,shadowsocks.org,赔钱机场'
495
+ - 'DOMAIN-SUFFIX,sharethis.com,赔钱机场'
496
+ - 'DOMAIN-SUFFIX,shazam.com,赔钱机场'
497
+ - 'DOMAIN-SUFFIX,skype.com,赔钱机场'
498
+ - 'DOMAIN-SUFFIX,smartdns赔钱机场.com,赔钱机场'
499
+ - 'DOMAIN-SUFFIX,smartmailcloud.com,赔钱机场'
500
+ - 'DOMAIN-SUFFIX,sndcdn.com,赔钱机场'
501
+ - 'DOMAIN-SUFFIX,sony.com,赔钱机场'
502
+ - 'DOMAIN-SUFFIX,soundcloud.com,赔钱机场'
503
+ - 'DOMAIN-SUFFIX,sourceforge.net,赔钱机场'
504
+ - 'DOMAIN-SUFFIX,spotify.com,赔钱机场'
505
+ - 'DOMAIN-SUFFIX,squarespace.com,赔钱机场'
506
+ - 'DOMAIN-SUFFIX,sstatic.net,赔钱机场'
507
+ - 'DOMAIN-SUFFIX,st.luluku.pw,赔钱机场'
508
+ - 'DOMAIN-SUFFIX,stackoverflow.com,赔钱机场'
509
+ - 'DOMAIN-SUFFIX,startpage.com,赔钱机场'
510
+ - 'DOMAIN-SUFFIX,staticflickr.com,赔钱机场'
511
+ - 'DOMAIN-SUFFIX,steamcommunity.com,赔钱机场'
512
+ - 'DOMAIN-SUFFIX,symauth.com,赔钱机场'
513
+ - 'DOMAIN-SUFFIX,symcb.com,赔钱机场'
514
+ - 'DOMAIN-SUFFIX,symcd.com,赔钱机场'
515
+ - 'DOMAIN-SUFFIX,tapbots.com,赔钱机场'
516
+ - 'DOMAIN-SUFFIX,tapbots.net,赔钱机场'
517
+ - 'DOMAIN-SUFFIX,tdesktop.com,赔钱机场'
518
+ - 'DOMAIN-SUFFIX,techcrunch.com,赔钱机场'
519
+ - 'DOMAIN-SUFFIX,techsmith.com,赔钱机场'
520
+ - 'DOMAIN-SUFFIX,thepiratebay.org,赔钱机场'
521
+ - 'DOMAIN-SUFFIX,theverge.com,赔钱机场'
522
+ - 'DOMAIN-SUFFIX,time.com,赔钱机场'
523
+ - 'DOMAIN-SUFFIX,timeinc.net,赔钱机场'
524
+ - 'DOMAIN-SUFFIX,tiny.cc,赔钱机场'
525
+ - 'DOMAIN-SUFFIX,tinypic.com,赔钱机场'
526
+ - 'DOMAIN-SUFFIX,tmblr.co,赔钱机场'
527
+ - 'DOMAIN-SUFFIX,todoist.com,赔钱机场'
528
+ - 'DOMAIN-SUFFIX,trello.com,赔钱机场'
529
+ - 'DOMAIN-SUFFIX,trustasiassl.com,赔钱机场'
530
+ - 'DOMAIN-SUFFIX,tumblr.co,赔钱机场'
531
+ - 'DOMAIN-SUFFIX,tumblr.com,赔钱机场'
532
+ - 'DOMAIN-SUFFIX,tweetdeck.com,赔钱机场'
533
+ - 'DOMAIN-SUFFIX,tweetmarker.net,赔钱机场'
534
+ - 'DOMAIN-SUFFIX,twitch.tv,赔钱机场'
535
+ - 'DOMAIN-SUFFIX,txmblr.com,赔钱机场'
536
+ - 'DOMAIN-SUFFIX,typekit.net,赔钱机场'
537
+ - 'DOMAIN-SUFFIX,ubertags.com,赔钱机场'
538
+ - 'DOMAIN-SUFFIX,ublock.org,赔钱机场'
539
+ - 'DOMAIN-SUFFIX,ubnt.com,赔钱机场'
540
+ - 'DOMAIN-SUFFIX,ulyssesapp.com,赔钱机场'
541
+ - 'DOMAIN-SUFFIX,urchin.com,赔钱机场'
542
+ - 'DOMAIN-SUFFIX,usertrust.com,赔钱机场'
543
+ - 'DOMAIN-SUFFIX,v.gd,赔钱机场'
544
+ - 'DOMAIN-SUFFIX,v2ex.com,赔钱机场'
545
+ - 'DOMAIN-SUFFIX,vimeo.com,赔钱机场'
546
+ - 'DOMAIN-SUFFIX,vimeocdn.com,赔钱机场'
547
+ - 'DOMAIN-SUFFIX,vine.co,赔钱机场'
548
+ - 'DOMAIN-SUFFIX,vivaldi.com,赔钱机场'
549
+ - 'DOMAIN-SUFFIX,vox-cdn.com,赔钱机场'
550
+ - 'DOMAIN-SUFFIX,vsco.co,赔钱机场'
551
+ - 'DOMAIN-SUFFIX,vultr.com,赔钱机场'
552
+ - 'DOMAIN-SUFFIX,w.org,赔钱机场'
553
+ - 'DOMAIN-SUFFIX,w3schools.com,赔钱机场'
554
+ - 'DOMAIN-SUFFIX,webtype.com,赔钱机场'
555
+ - 'DOMAIN-SUFFIX,wikiwand.com,赔钱机场'
556
+ - 'DOMAIN-SUFFIX,wikileaks.org,赔钱机场'
557
+ - 'DOMAIN-SUFFIX,wikimedia.org,赔钱机场'
558
+ - 'DOMAIN-SUFFIX,wikipedia.com,赔钱机场'
559
+ - 'DOMAIN-SUFFIX,wikipedia.org,赔钱机场'
560
+ - 'DOMAIN-SUFFIX,windows.com,赔钱机场'
561
+ - 'DOMAIN-SUFFIX,windows.net,赔钱机场'
562
+ - 'DOMAIN-SUFFIX,wire.com,赔钱机场'
563
+ - 'DOMAIN-SUFFIX,wordpress.com,赔钱机场'
564
+ - 'DOMAIN-SUFFIX,workflowy.com,赔钱机场'
565
+ - 'DOMAIN-SUFFIX,wp.com,赔钱机场'
566
+ - 'DOMAIN-SUFFIX,wsj.com,赔钱机场'
567
+ - 'DOMAIN-SUFFIX,wsj.net,赔钱机场'
568
+ - 'DOMAIN-SUFFIX,xda-developers.com,赔钱机场'
569
+ - 'DOMAIN-SUFFIX,xeeno.com,赔钱机场'
570
+ - 'DOMAIN-SUFFIX,xiti.com,赔钱机场'
571
+ - 'DOMAIN-SUFFIX,yahoo.com,赔钱机场'
572
+ - 'DOMAIN-SUFFIX,yimg.com,赔钱机场'
573
+ - 'DOMAIN-SUFFIX,ying.com,赔钱机场'
574
+ - 'DOMAIN-SUFFIX,yoyo.org,赔钱机场'
575
+ - 'DOMAIN-SUFFIX,ytimg.com,赔钱机场'
576
+ - 'DOMAIN-SUFFIX,telegra.ph,赔钱机场'
577
+ - 'DOMAIN-SUFFIX,telegram.org,赔钱机场'
578
+ - 'IP-CIDR,91.108.4.0/22,赔钱机场,no-resolve'
579
+ - 'IP-CIDR,91.108.8.0/21,赔钱机场,no-resolve'
580
+ - 'IP-CIDR,91.108.16.0/22,赔钱机场,no-resolve'
581
+ - 'IP-CIDR,91.108.56.0/22,赔钱机场,no-resolve'
582
+ - 'IP-CIDR,149.154.160.0/20,赔钱机场,no-resolve'
583
+ - 'IP-CIDR6,2001:67c:4e8::/48,赔钱机场,no-resolve'
584
+ - 'IP-CIDR6,2001:b28:f23d::/48,赔钱机场,no-resolve'
585
+ - 'IP-CIDR6,2001:b28:f23f::/48,赔钱机场,no-resolve'
586
+ - 'IP-CIDR,120.232.181.162/32,赔钱机场,no-resolve'
587
+ - 'IP-CIDR,120.241.147.226/32,赔钱机场,no-resolve'
588
+ - 'IP-CIDR,120.253.253.226/32,赔钱机场,no-resolve'
589
+ - 'IP-CIDR,120.253.255.162/32,赔钱机场,no-resolve'
590
+ - 'IP-CIDR,120.253.255.34/32,赔钱机场,no-resolve'
591
+ - 'IP-CIDR,120.253.255.98/32,赔钱机场,no-resolve'
592
+ - 'IP-CIDR,180.163.150.162/32,赔钱机场,no-resolve'
593
+ - 'IP-CIDR,180.163.150.34/32,赔钱机场,no-resolve'
594
+ - 'IP-CIDR,180.163.151.162/32,赔钱机场,no-resolve'
595
+ - 'IP-CIDR,180.163.151.34/32,赔钱机场,no-resolve'
596
+ - 'IP-CIDR,203.208.39.0/24,赔钱机场,no-resolve'
597
+ - 'IP-CIDR,203.208.40.0/24,赔钱机场,no-resolve'
598
+ - 'IP-CIDR,203.208.41.0/24,赔钱机场,no-resolve'
599
+ - 'IP-CIDR,203.208.43.0/24,赔钱机场,no-resolve'
600
+ - 'IP-CIDR,203.208.50.0/24,赔钱机场,no-resolve'
601
+ - 'IP-CIDR,220.181.174.162/32,赔钱机场,no-resolve'
602
+ - 'IP-CIDR,220.181.174.226/32,赔钱机场,no-resolve'
603
+ - 'IP-CIDR,220.181.174.34/32,赔钱机场,no-resolve'
604
+ - 'DOMAIN,injections.adguard.org,DIRECT'
605
+ - 'DOMAIN,local.adguard.org,DIRECT'
606
+ - 'DOMAIN-SUFFIX,local,DIRECT'
607
+ - 'IP-CIDR,127.0.0.0/8,DIRECT'
608
+ - 'IP-CIDR,172.16.0.0/12,DIRECT'
609
+ - 'IP-CIDR,192.168.0.0/16,DIRECT'
610
+ - 'IP-CIDR,10.0.0.0/8,DIRECT'
611
+ - 'IP-CIDR,17.0.0.0/8,DIRECT'
612
+ - 'IP-CIDR,100.64.0.0/10,DIRECT'
613
+ - 'IP-CIDR,224.0.0.0/4,DIRECT'
614
+ - 'IP-CIDR6,fe80::/10,DIRECT'
615
+ - 'DOMAIN-SUFFIX,cn,DIRECT'
616
+ - 'DOMAIN-KEYWORD,-cn,DIRECT'
617
+ - 'GEOIP,CN,DIRECT'
618
+ - 'MATCH,赔钱机场'
app/data/app/debug_tools.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 调试工具 - 用于分析 Clash Core 的运行环境
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import subprocess
11
+ import logging
12
+ import platform
13
+ import json
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ def check_binary(binary_path):
18
+ """检查二进制文件,并返回详细信息"""
19
+ results = {
20
+ "path": binary_path,
21
+ "exists": os.path.exists(binary_path),
22
+ "size": 0,
23
+ "permissions": "",
24
+ "file_type": "",
25
+ "ldd_output": "",
26
+ "system_info": get_system_info()
27
+ }
28
+
29
+ if results["exists"]:
30
+ try:
31
+ # 获取文件大小
32
+ results["size"] = os.path.getsize(binary_path)
33
+
34
+ # 获取文件权限
35
+ results["permissions"] = oct(os.stat(binary_path).st_mode)[-3:]
36
+
37
+ # 检查是否可执行
38
+ results["is_executable"] = os.access(binary_path, os.X_OK)
39
+
40
+ # 获取文件类型 (file 命令)
41
+ try:
42
+ file_output = subprocess.check_output(["file", binary_path], universal_newlines=True)
43
+ results["file_type"] = file_output.strip()
44
+ except Exception as e:
45
+ results["file_type_error"] = str(e)
46
+
47
+ # 获取库依赖 (ldd 命令)
48
+ try:
49
+ ldd_output = subprocess.check_output(["ldd", binary_path], universal_newlines=True)
50
+ results["ldd_output"] = ldd_output.strip()
51
+ except Exception as e:
52
+ results["ldd_error"] = str(e)
53
+
54
+ # 尝试直接执行获取版本或帮助
55
+ try:
56
+ version_output = subprocess.check_output([binary_path, "-v"], stderr=subprocess.STDOUT, universal_newlines=True, timeout=2)
57
+ results["version_output"] = version_output.strip()
58
+ except Exception as e:
59
+ results["version_error"] = str(e)
60
+
61
+ # 如果 -v 失败,尝试 --version
62
+ try:
63
+ version_output = subprocess.check_output([binary_path, "--version"], stderr=subprocess.STDOUT, universal_newlines=True, timeout=2)
64
+ results["version_output"] = version_output.strip()
65
+ except Exception as e2:
66
+ results["version_error_alt"] = str(e2)
67
+
68
+ except Exception as e:
69
+ results["error"] = str(e)
70
+
71
+ return results
72
+
73
+ def get_system_info():
74
+ """获取系统信息"""
75
+ info = {
76
+ "platform": platform.platform(),
77
+ "system": platform.system(),
78
+ "release": platform.release(),
79
+ "version": platform.version(),
80
+ "architecture": platform.machine(),
81
+ "python_version": sys.version,
82
+ "cwd": os.getcwd(),
83
+ "env": {key: value for key, value in os.environ.items()}
84
+ }
85
+ return info
86
+
87
+ def check_clash_environment(clash_path, config_path):
88
+ """检查 Clash 运行环境"""
89
+ results = {
90
+ "clash_binary": check_binary(clash_path),
91
+ "config_file": {
92
+ "path": config_path,
93
+ "exists": os.path.exists(config_path),
94
+ "size": 0,
95
+ "content_preview": ""
96
+ },
97
+ "directory_info": {}
98
+ }
99
+
100
+ # 检查配置文件
101
+ if results["config_file"]["exists"]:
102
+ try:
103
+ results["config_file"]["size"] = os.path.getsize(config_path)
104
+ with open(config_path, 'r', encoding='utf-8') as f:
105
+ content = f.read(1000) # 读取前1000个字符
106
+ results["config_file"]["content_preview"] = content
107
+ except Exception as e:
108
+ results["config_file"]["error"] = str(e)
109
+
110
+ # 检查目录结构
111
+ dirs_to_check = [
112
+ os.path.dirname(clash_path),
113
+ os.path.dirname(config_path),
114
+ ".",
115
+ "./data",
116
+ "./clash_core",
117
+ "./subconverter"
118
+ ]
119
+
120
+ for dir_path in dirs_to_check:
121
+ try:
122
+ if os.path.exists(dir_path):
123
+ files = os.listdir(dir_path)
124
+ results["directory_info"][dir_path] = {
125
+ "exists": True,
126
+ "files": files,
127
+ "permissions": oct(os.stat(dir_path).st_mode)[-3:]
128
+ }
129
+ else:
130
+ results["directory_info"][dir_path] = {
131
+ "exists": False
132
+ }
133
+ except Exception as e:
134
+ results["directory_info"][dir_path] = {
135
+ "error": str(e)
136
+ }
137
+
138
+ return results
139
+
140
+ def run_debug_diagnostics(clash_path, config_path):
141
+ """运行调试诊断,并记录结果"""
142
+ logger.info("运行调试诊断...")
143
+
144
+ try:
145
+ results = check_clash_environment(clash_path, config_path)
146
+ logger.info("诊断完成")
147
+
148
+ # 将结果保存到文件
149
+ diagnostics_file = "clash_diagnostics.json"
150
+ with open(diagnostics_file, 'w', encoding='utf-8') as f:
151
+ json.dump(results, f, indent=2, ensure_ascii=False)
152
+ logger.info(f"诊断结果已保存到 {diagnostics_file}")
153
+
154
+ # 打印关键信息
155
+ if results["clash_binary"]["exists"]:
156
+ logger.info(f"Clash 二进制文件: {results['clash_binary']['path']}")
157
+ logger.info(f" - 大小: {results['clash_binary']['size']} 字节")
158
+ logger.info(f" - 权限: {results['clash_binary']['permissions']}")
159
+ logger.info(f" - 文件类型: {results['clash_binary']['file_type']}")
160
+ logger.info(f" - 可执行: {results['clash_binary'].get('is_executable', False)}")
161
+ else:
162
+ logger.error(f"Clash 二进制文件不存在: {results['clash_binary']['path']}")
163
+
164
+ if results["config_file"]["exists"]:
165
+ logger.info(f"配置文件: {results['config_file']['path']}")
166
+ logger.info(f" - 大小: {results['config_file']['size']} 字节")
167
+ preview = results["config_file"].get("content_preview", "")
168
+ if preview:
169
+ logger.info(f" - 内容预览: {preview[:100]}...")
170
+ else:
171
+ logger.error(f"配置文件不存在: {results['config_file']['path']}")
172
+
173
+ return results
174
+
175
+ except Exception as e:
176
+ logger.error(f"诊断过程中出错: {str(e)}")
177
+ return {"error": str(e)}
app/data/app/main.py ADDED
@@ -0,0 +1,521 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Simple Clash Relay - Flask 应用入口
6
+ """
7
+
8
+ import os
9
+ import logging
10
+ from flask import Flask, request, jsonify, Response, redirect, send_from_directory
11
+ from .clash_manager import ClashManager
12
+ from .sub_manager import SubscriptionManager
13
+ from .auth import authenticate
14
+ import requests
15
+ from functools import wraps
16
+
17
+ # 配置日志
18
+ logging.basicConfig(
19
+ level=logging.INFO,
20
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
21
+ )
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # 从环境变量加载配置
25
+ SUB_URL = os.environ.get("SUB_URL")
26
+ API_KEY = os.environ.get("API_KEY", "changeme")
27
+ FLASK_PORT = int(os.environ.get("FLASK_PORT", 7860)) # 默认端口改为7860
28
+ CLASH_PROXY_PORT = int(os.environ.get("CLASH_PROXY_PORT", 7890))
29
+ CLASH_API_PORT = int(os.environ.get("CLASH_API_PORT", 9090))
30
+
31
+ # 初始化Flask应用
32
+ app = Flask(__name__, static_folder='static')
33
+
34
+ # 初始化管理器
35
+ clash_manager = None
36
+ sub_manager = None
37
+ initialization_error = None
38
+
39
+ @app.before_request
40
+ def initialize_once():
41
+ """应用首次请求前的初始化"""
42
+ global clash_manager, sub_manager, initialization_error
43
+
44
+ # 使用类变量确保只初始化一次
45
+ if not getattr(initialize_once, '_initialized', False):
46
+ logger.info("正在初始化应用 (首次请求)...")
47
+
48
+ config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "config.yaml")
49
+
50
+ # 首先检查是否存在有效的本地配置文件
51
+ if os.path.exists(config_path) and os.path.getsize(config_path) > 0:
52
+ logger.info("检测到有效的本地配置文件,直接使用它启动Clash")
53
+ try:
54
+ # 初始化Clash管理器并使用本地配置文件
55
+ clash_manager = ClashManager(
56
+ config_path=config_path,
57
+ clash_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "clash_core", "clash.meta-linux-amd64"),
58
+ api_port=CLASH_API_PORT,
59
+ proxy_port=CLASH_PROXY_PORT
60
+ )
61
+
62
+ # 启动Clash Core
63
+ clash_manager.start_clash()
64
+ logger.info("成功使用本地配置文件启动Clash Core")
65
+ initialize_once._initialized = True
66
+ return
67
+ except Exception as e:
68
+ logger.warning(f"使用本地配置文件启动失败,将尝试从订阅加载: {str(e)}")
69
+ # 继续尝试从订阅加载
70
+
71
+ # 初始化订阅管理器
72
+ sub_manager = SubscriptionManager(
73
+ sub_url=SUB_URL,
74
+ config_path=config_path
75
+ )
76
+
77
+ # 加载订阅并转换为Clash配置
78
+ try:
79
+ sub_manager.load_and_convert_sub()
80
+ logger.info("成功加载并转换订阅")
81
+ except Exception as e:
82
+ err_msg = f"加载订阅失败: {str(e)}"
83
+ logger.error(err_msg)
84
+ initialization_error = err_msg
85
+ initialize_once._initialized = True # 标记已初始化,即使失败
86
+ return
87
+
88
+ # 初始化Clash管理器
89
+ clash_manager = ClashManager(
90
+ config_path=config_path,
91
+ clash_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "clash_core", "clash.meta-linux-amd64"),
92
+ api_port=CLASH_API_PORT,
93
+ proxy_port=CLASH_PROXY_PORT
94
+ )
95
+
96
+ # 启动Clash Core
97
+ try:
98
+ clash_manager.start_clash()
99
+ logger.info("成功启动Clash Core")
100
+ except Exception as e:
101
+ err_msg = f"启动Clash Core失败: {str(e)}"
102
+ logger.error(err_msg)
103
+ initialization_error = err_msg
104
+
105
+ initialize_once._initialized = True # 标记已初始化
106
+
107
+ @app.route("/api/nodes", methods=["GET"])
108
+ @authenticate
109
+ def get_nodes():
110
+ """获取可用节点列表"""
111
+ global clash_manager, initialization_error
112
+
113
+ if clash_manager is None:
114
+ return jsonify({
115
+ "success": False,
116
+ "error": f"Clash未启动: {initialization_error or '未知错误'}"
117
+ }), 503
118
+
119
+ try:
120
+ nodes = clash_manager.get_nodes()
121
+ return jsonify({"success": True, "nodes": nodes})
122
+ except Exception as e:
123
+ logger.error(f"获取节点列表失败: {str(e)}")
124
+ return jsonify({"success": False, "error": str(e)}), 500
125
+
126
+ @app.route("/api/switch", methods=["PUT"])
127
+ @authenticate
128
+ def switch_node():
129
+ """切换到指定节点"""
130
+ global clash_manager, initialization_error
131
+
132
+ if clash_manager is None:
133
+ return jsonify({
134
+ "success": False,
135
+ "error": f"Clash未启动: {initialization_error or '未知错误'}"
136
+ }), 503
137
+
138
+ data = request.get_json()
139
+ if not data or "node" not in data:
140
+ return jsonify({"success": False, "error": "缺少'node'参数"}), 400
141
+
142
+ node_name = data["node"]
143
+ try:
144
+ clash_manager.switch_node(node_name)
145
+ return jsonify({"success": True, "message": f"已切换到节点: {node_name}"})
146
+ except Exception as e:
147
+ logger.error(f"切换到节点 {node_name} 失败: {str(e)}")
148
+ return jsonify({"success": False, "error": str(e)}), 500
149
+
150
+ @app.route("/api/current", methods=["GET"])
151
+ @authenticate
152
+ def get_current_node():
153
+ """获取当前使用的节点"""
154
+ global clash_manager, initialization_error
155
+
156
+ if clash_manager is None:
157
+ return jsonify({
158
+ "success": False,
159
+ "error": f"Clash未启动: {initialization_error or '未知错误'}"
160
+ }), 503
161
+
162
+ try:
163
+ current_node = clash_manager.get_current_node()
164
+ return jsonify({"success": True, "current_node": current_node})
165
+ except Exception as e:
166
+ logger.error(f"获取当前节点失败: {str(e)}")
167
+ return jsonify({"success": False, "error": str(e)}), 500
168
+
169
+ @app.route("/api/refresh", methods=["POST"])
170
+ @authenticate
171
+ def refresh_subscription():
172
+ """刷新订阅并重新加载Clash配置"""
173
+ global clash_manager, sub_manager, initialization_error
174
+
175
+ try:
176
+ # 尝试重新加载订阅
177
+ if sub_manager is None:
178
+ sub_manager = SubscriptionManager(
179
+ sub_url=SUB_URL,
180
+ config_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "config.yaml")
181
+ )
182
+
183
+ sub_manager.load_and_convert_sub()
184
+
185
+ if clash_manager is None:
186
+ clash_manager = ClashManager(
187
+ config_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "config.yaml"),
188
+ clash_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "clash_core", "clash.meta-linux-amd64"),
189
+ api_port=CLASH_API_PORT,
190
+ proxy_port=CLASH_PROXY_PORT
191
+ )
192
+ clash_manager.start_clash()
193
+ initialization_error = None
194
+ return jsonify({"success": True, "message": "订阅已刷新,Clash已启动"})
195
+ else:
196
+ clash_manager.restart_clash()
197
+ initialization_error = None
198
+ return jsonify({"success": True, "message": "订阅已刷新,Clash已重启"})
199
+ except Exception as e:
200
+ error_msg = f"刷新订阅失败: {str(e)}"
201
+ logger.error(error_msg)
202
+ initialization_error = error_msg
203
+ return jsonify({"success": False, "error": error_msg}), 500
204
+
205
+ @app.route("/health", methods=["GET"])
206
+ def health_check():
207
+ """健康检查接口"""
208
+ return jsonify({"status": "ok"})
209
+
210
+ # --- 代理路由 ---
211
+
212
+ # 弃用/proxy路由,Clash的代理端口由外部直接访问
213
+ # 如果需要在Hugging Face部署,代理通常通过Clash API的节点选择完成
214
+
215
+ # --- 调试路由 ---
216
+
217
+ @app.route("/debug/clean", methods=["POST"])
218
+ @authenticate
219
+ def debug_clean():
220
+ """清理并重新初始化配置"""
221
+ global clash_manager, sub_manager, initialization_error
222
+
223
+ try:
224
+ if clash_manager is not None:
225
+ clash_manager.stop_clash()
226
+ clash_manager = None
227
+
228
+ config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "config.yaml")
229
+ raw_config_path = f"{config_path}.raw"
230
+
231
+ files_to_delete = [config_path, raw_config_path]
232
+ deleted_files = []
233
+
234
+ for file_path in files_to_delete:
235
+ if os.path.exists(file_path):
236
+ try:
237
+ os.remove(file_path)
238
+ deleted_files.append(os.path.basename(file_path))
239
+ except OSError as e:
240
+ logger.warning(f"删除文件失败: {file_path}, 错误: {e}")
241
+
242
+ # 重新初始化并启动
243
+ initialize_once._initialized = False # 强制下次请求时重新初始化
244
+ initialize_once()
245
+
246
+ status_msg = "已清理并重新初始化" if initialization_error is None else f"已清理但初始化失败: {initialization_error}"
247
+ return jsonify({"success": True, "message": status_msg, "deleted_files": deleted_files})
248
+
249
+ except Exception as e:
250
+ logger.error(f"清理配置时出错: {str(e)}")
251
+ return jsonify({"success": False, "error": str(e)}), 500
252
+
253
+ @app.route("/debug/config", methods=["GET"])
254
+ @authenticate
255
+ def debug_show_config():
256
+ """获取并显示当前Clash配置文件内容"""
257
+ config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "config.yaml")
258
+
259
+ if not os.path.exists(config_path):
260
+ return jsonify({"success": False, "error": "配置文件不存在"}), 404
261
+
262
+ try:
263
+ with open(config_path, "r", encoding="utf-8") as f:
264
+ content = f.read()
265
+ return jsonify({"success": True, "content": content})
266
+ except Exception as e:
267
+ logger.error(f"读取配置文件时出错: {str(e)}")
268
+ return jsonify({"success": False, "error": str(e)}), 500
269
+
270
+ # --- Yacd UI & Clash API 反向代理路由 ---
271
+
272
+ @app.route('/ui/')
273
+ def yacd_index():
274
+ """提供Yacd UI的入口文件"""
275
+ # return send_from_directory('static/yacd', 'index.html')
276
+ # 重定向到包含 index.html 的目录,确保相对路径正确加载
277
+ return redirect('/ui/index.html', code=301)
278
+
279
+ @app.route('/ui/<path:path>')
280
+ def yacd_static(path):
281
+ """提供Yacd UI的静态文件 (CSS, JS, images)"""
282
+ return send_from_directory(os.path.join(app.static_folder, 'yacd'), path)
283
+
284
+ @app.route('/clashapi/<path:subpath>', methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH'])
285
+ def clash_api_proxy(subpath):
286
+ """反向代理请求到内部的Clash API"""
287
+ global clash_manager, initialization_error, API_KEY
288
+
289
+ # 1. 认证检查 (Yacd 使用 Authorization: Bearer <key> 或 ?token=<key>)
290
+ auth_header = request.headers.get('Authorization')
291
+ token = request.args.get('token')
292
+ provided_key = None
293
+
294
+ if auth_header and auth_header.startswith('Bearer '):
295
+ provided_key = auth_header.split(' ', 1)[1]
296
+ elif token:
297
+ provided_key = token
298
+
299
+ if provided_key != API_KEY:
300
+ logger.warning(f"Clash API代理认证失败,路径: {subpath}")
301
+ return jsonify({"message": "Authentication required"}), 401
302
+
303
+ # 2. 检查Clash是否运行
304
+ if clash_manager is None or clash_manager.clash_process is None or clash_manager.clash_process.poll() is not None:
305
+ logger.error(f"Clash API不可用,无法代理请求: {subpath}")
306
+ return jsonify({
307
+ "success": False,
308
+ "error": f"Clash API未运行: {initialization_error or '内部错误'}"
309
+ }), 503
310
+
311
+ # 3. 构建目标URL
312
+ target_url = f"http://127.0.0.1:{CLASH_API_PORT}/{subpath}"
313
+ if request.query_string:
314
+ target_url += '?' + request.query_string.decode('utf-8')
315
+
316
+ logger.debug(f"代理Clash API请求到: {target_url}")
317
+
318
+ # 4. 转发请求
319
+ try:
320
+ # 准备请求头,移除Host,保留Yacd可能发送的认证头
321
+ req_headers = {key: value for key, value in request.headers if key.lower() != 'host'}
322
+
323
+ # 对于WebSocket连接 (Clash API 日志/流量)
324
+ if request.headers.get('Upgrade', '').lower() == 'websocket':
325
+ # 这里需要一个更复杂的WebSocket代理实现,暂时返回错误
326
+ logger.warning("不支持的WebSocket代理请求")
327
+ return jsonify({"error": "WebSocket proxy not supported"}), 501
328
+
329
+ # 普通HTTP请求
330
+ resp = requests.request(
331
+ method=request.method,
332
+ url=target_url,
333
+ headers=req_headers,
334
+ data=request.get_data(),
335
+ cookies=request.cookies,
336
+ allow_redirects=False,
337
+ stream=True, # 对于大响应或流式响应
338
+ timeout=30 # 添加超时
339
+ )
340
+
341
+ # 5. 构建并返回响应
342
+ excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
343
+ resp_headers = [(name, value) for name, value in resp.raw.headers.items()
344
+ if name.lower() not in excluded_headers]
345
+
346
+ # 使用iter_content流式传输响应体,以支持大数据和流
347
+ response = Response(resp.iter_content(chunk_size=8192), resp.status_code, resp_headers)
348
+ return response
349
+
350
+ except requests.exceptions.ConnectionError as e:
351
+ logger.error(f"连接Clash API失败 ({target_url}): {str(e)}")
352
+ return jsonify({"error": f"无法连接到内部Clash API: {str(e)}"}), 502 # Bad Gateway
353
+ except requests.exceptions.Timeout as e:
354
+ logger.error(f"请求Clash API超时 ({target_url}): {str(e)}")
355
+ return jsonify({"error": f"请求内部Clash API超时: {str(e)}"}), 504 # Gateway Timeout
356
+ except Exception as e:
357
+ logger.error(f"代理Clash API请求时发生意外错误: {str(e)}")
358
+ return jsonify({"error": f"代理请求时发生意外错误: {str(e)}"}), 500
359
+
360
+ # --- 基础路由 ---
361
+
362
+ @app.route('/', methods=['GET'])
363
+ def index():
364
+ """首页 - 提供简单说明和调试链接"""
365
+ global initialization_error
366
+
367
+ status = "运行中" if initialization_error is None else "初始化失败"
368
+ error_msg = "" if initialization_error is None else f"<p style='color:red'>错误: {initialization_error}</p>"
369
+
370
+ # 检查Yacd UI是否可用
371
+ yacd_link = "<a href='/ui/' class='button'>访问高级控制面板 (Yacd)</a>"
372
+
373
+ return f"""
374
+ <html>
375
+ <head>
376
+ <title>Simple Clash Relay</title>
377
+ <style>
378
+ body {{ font-family: Arial, sans-serif; padding: 20px; }}
379
+ h1 {{ color: #333; }}
380
+ .status {{ padding: 10px; border-radius: 5px; display: inline-block; }}
381
+ .running {{ background-color: #dff0d8; color: #3c763d; }}
382
+ .error {{ background-color: #f2dede; color: #a94442; }}
383
+ .container {{ max-width: 800px; margin: 0 auto; }}
384
+ .button {{
385
+ display: inline-block;
386
+ padding: 8px 16px;
387
+ background-color: #337ab7;
388
+ color: white;
389
+ text-decoration: none;
390
+ border-radius: 4px;
391
+ margin-right: 10px;
392
+ margin-bottom: 10px; /* 添加底部间距 */
393
+ }}
394
+ .button.danger {{ background-color: #d9534f; }}
395
+ .button.warning {{ background-color: #f0ad4e; }}
396
+ .debug-section {{
397
+ margin-top: 20px;
398
+ padding: 15px;
399
+ border: 1px dashed #ccc;
400
+ background-color: #f9f9f9;
401
+ }}
402
+ pre {{ max-height: 300px; overflow: auto; background-color: #eee; padding: 10px; border-radius: 4px; }}
403
+ </style>
404
+ <script>
405
+ function requestWithApiKey(url, method = 'POST') {{
406
+ const apiKey = prompt('请输入API密钥 (默认为 changeme)', 'changeme');
407
+ if (apiKey === null) return; // 用户取消
408
+
409
+ fetch(url, {{
410
+ method: method,
411
+ headers: {{ 'X-API-Key': apiKey }}
412
+ }})
413
+ .then(response => response.json())
414
+ .then(data => {{
415
+ alert(data.success ? data.message : '失败: ' + data.error);
416
+ if(data.success) location.reload();
417
+ }})
418
+ .catch(error => alert('请求失败: ' + error));
419
+ }}
420
+
421
+ function viewConfig() {{
422
+ const apiKey = prompt('请输入API密钥 (默认为 changeme)', 'changeme');
423
+ if (apiKey === null) return;
424
+
425
+ fetch('/debug/config', {{
426
+ headers: {{ 'X-API-Key': apiKey }}
427
+ }})
428
+ .then(response => response.json())
429
+ .then(data => {{
430
+ const configDiv = document.getElementById('config-content');
431
+ configDiv.innerHTML = ''; // 清空之前的内容
432
+ if (data.success) {{
433
+ const pre = document.createElement('pre');
434
+ pre.textContent = data.content;
435
+ configDiv.appendChild(pre);
436
+ }} else {{
437
+ const errorP = document.createElement('p');
438
+ errorP.style.color = 'red';
439
+ errorP.textContent = '获取配置失败: ' + data.error;
440
+ configDiv.appendChild(errorP);
441
+ }}
442
+ }})
443
+ .catch(error => {{
444
+ const configDiv = document.getElementById('config-content');
445
+ configDiv.innerHTML = '<p style="color:red">请求失败: ' + error + '</p>';
446
+ }});
447
+ }}
448
+ </script>
449
+ </head>
450
+ <body>
451
+ <div class='container'>
452
+ <h1>Simple Clash Relay</h1>
453
+ <p>状态: <span class='status {"running" if initialization_error is None else "error"}'>{status}</span></p>
454
+ {error_msg}
455
+
456
+ <h2>控制面板</h2>
457
+ {yacd_link}
458
+
459
+ <h2>基本操作</h2>
460
+ <button class="button warning" onclick="requestWithApiKey('/api/refresh')">刷新订阅并重启Clash</button>
461
+
462
+ <div class="debug-section">
463
+ <h3>调试选项</h3>
464
+ <button class="button" onclick="viewConfig()">查看当前配置文件</button>
465
+ <button class="button danger" onclick="if(confirm('确定要清理配置并重启服务吗?此操作不可逆!')) requestWithApiKey('/debug/clean')">清理配置并重启</button>
466
+ <div id="config-content" style="margin-top: 15px;"></div>
467
+ </div>
468
+
469
+ <h2>帮助</h2>
470
+ <p>API密钥可在 .env 文件或环境变量中设置 (API_KEY)。默认为 'changeme'。</p>
471
+ <p>高级控制面板 (Yacd) 需要在设置中配置 API 地址为 /clashapi 并提供密钥。</p>
472
+ <p>更多信息请参考项目文档或README。</p>
473
+ </div>
474
+ </body>
475
+ </html>
476
+ """
477
+
478
+ @app.route("/api/use_local_config", methods=["POST"])
479
+ @authenticate
480
+ def use_local_config():
481
+ """直接使用本地的config.yaml文件,跳过订阅转换"""
482
+ global clash_manager, sub_manager, initialization_error
483
+
484
+ try:
485
+ config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "config.yaml")
486
+
487
+ # 检查文件是否存在
488
+ if not os.path.exists(config_path):
489
+ return jsonify({"success": False, "error": "本地配置文件不存在"}), 404
490
+
491
+ # 停止当前的Clash服务(如果正在运行)
492
+ if clash_manager is not None:
493
+ clash_manager.stop_clash()
494
+
495
+ # 重新初始化Clash管理器并使用本地配置文件启动
496
+ clash_manager = ClashManager(
497
+ config_path=config_path,
498
+ clash_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "clash_core", "clash.meta-linux-amd64"),
499
+ api_port=CLASH_API_PORT,
500
+ proxy_port=CLASH_PROXY_PORT
501
+ )
502
+
503
+ # 启动Clash Core
504
+ clash_manager.start_clash()
505
+ initialization_error = None
506
+
507
+ logger.info("成功使用本地配置文件启动Clash Core")
508
+ return jsonify({"success": True, "message": "已使用本地配置文件启动服务"})
509
+
510
+ except Exception as e:
511
+ error_msg = f"使用本地配置文件失败: {str(e)}"
512
+ logger.error(error_msg)
513
+ initialization_error = error_msg
514
+ return jsonify({"success": False, "error": error_msg}), 500
515
+
516
+ if __name__ == "__main__":
517
+ # 如果直接运行此文件,将初始化应用并启动Flask服务器
518
+ initialize_once()
519
+ logger.info(f"启动Flask服务器,监听端口: {FLASK_PORT}")
520
+ app.run(host="0.0.0.0", port=FLASK_PORT)
521
+ app.run(host="0.0.0.0", port=FLASK_PORT)
app/data/app/sub_manager.py ADDED
@@ -0,0 +1,462 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 订阅管理器 - 负责下载订阅内容并转换为Clash配置
6
+ """
7
+
8
+ import os
9
+ import logging
10
+ import subprocess
11
+ import requests
12
+ import time
13
+ from urllib.parse import urlparse
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ class SubscriptionManager:
18
+ """管理订阅链接的下载和配置转换"""
19
+
20
+ def __init__(self, sub_url, config_path):
21
+ """
22
+ 初始化订阅管理器
23
+
24
+ Args:
25
+ sub_url: 订阅链接URL
26
+ config_path: 生成的Clash配置文件保存路径
27
+ """
28
+ self.sub_url = sub_url
29
+ self.config_path = os.path.abspath(config_path)
30
+ self.subconverter_path = os.path.join(
31
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
32
+ "subconverter", "subconverter"
33
+ )
34
+
35
+ # 检查是否设置了订阅链接
36
+ if not sub_url:
37
+ raise ValueError("未设置订阅链接 (SUB_URL)")
38
+
39
+ # 确保配置目录存在
40
+ os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
41
+
42
+ # 检查subconverter可执行文件是否存在
43
+ if not os.path.exists(self.subconverter_path):
44
+ raise FileNotFoundError(f"subconverter可执行文件未找到: {self.subconverter_path}")
45
+
46
+ def load_and_convert_sub(self):
47
+ """
48
+ 下载订阅内容并转换为Clash配置
49
+
50
+ Returns:
51
+ str: 生成的Clash配置文件路径
52
+
53
+ Raises:
54
+ RuntimeError: 如果下载或转换失败
55
+ """
56
+ # 下载订阅内容
57
+ sub_content = self._download_subscription()
58
+
59
+ # 保存订阅内容到临时文件
60
+ temp_file = f"{self.config_path}.raw"
61
+ with open(temp_file, "w", encoding="utf-8") as f:
62
+ f.write(sub_content)
63
+
64
+ # 使用subconverter转换为Clash配置
65
+ self._convert_to_clash(temp_file)
66
+
67
+ # 修改配置文件以确保端口设置正确
68
+ self._patch_config()
69
+
70
+ # 清理临时文件
71
+ try:
72
+ os.remove(temp_file)
73
+ except OSError:
74
+ pass
75
+
76
+ return self.config_path
77
+
78
+ def _download_subscription(self):
79
+ """
80
+ 下载订阅内容
81
+
82
+ Returns:
83
+ str: 订阅内容文本
84
+
85
+ Raises:
86
+ RuntimeError: 如果下载失败
87
+ """
88
+ logger.info(f"正在下载订阅: {self._mask_url(self.sub_url)}")
89
+
90
+ try:
91
+ headers = {
92
+ "User-Agent": "ClashforWindows/0.19.0",
93
+ "Accept": "*/*",
94
+ }
95
+ response = requests.get(self.sub_url, headers=headers, timeout=30)
96
+ response.raise_for_status()
97
+ content = response.text
98
+
99
+ if not content or len(content) < 10:
100
+ raise RuntimeError("下载的订阅内容为空或过短")
101
+
102
+ logger.info(f"成功下载订阅,大小: {len(content)} 字节")
103
+ return content
104
+
105
+ except requests.RequestException as e:
106
+ logger.error(f"下载订阅失败: {str(e)}")
107
+ raise RuntimeError(f"下载订阅失败: {str(e)}")
108
+
109
+ def _convert_to_clash(self, input_file):
110
+ """
111
+ 使用subconverter将订阅内容转换为Clash配置
112
+
113
+ Args:
114
+ input_file: 包含订阅内容的文件路径
115
+
116
+ Raises:
117
+ RuntimeError: 如果转换失败
118
+ """
119
+ logger.info(f"正在将订阅转换为Clash配置")
120
+ logger.info(f"输入文件: {input_file}, 配置路径: {self.config_path}")
121
+
122
+ # 确保数据目录存在
123
+ data_dir = os.path.dirname(self.config_path)
124
+ os.makedirs(data_dir, exist_ok=True)
125
+
126
+ # 准备subconverter命令 - 使用 -i/-o 并强制指定空白配置
127
+ minimal_pref_path = os.path.join(os.path.dirname(self.subconverter_path), "minimal_pref.yml")
128
+
129
+ cmd = [
130
+ self.subconverter_path,
131
+ "-i", input_file, # 输入文件
132
+ "-o", self.config_path, # 输出文件
133
+ "-t", "clash", # 目标格式
134
+ "--config", minimal_pref_path # 强制使用空白配置,避免默认行为
135
+ ]
136
+
137
+ logger.info(f"执行命令: {' '.join(cmd)}")
138
+
139
+ # 检查 subconverter 是否存在 (之前的代码已修复)
140
+ if not os.path.exists(self.subconverter_path):
141
+ # ... (省略 subconverter 未找到的回退逻辑) ...
142
+ logger.error(f"subconverter未找到: {self.subconverter_path}. 无法转换配置。")
143
+ raise RuntimeError(f"无法转换配置 (subconverter未找到)")
144
+
145
+ # --- 正式执行 subconverter ---
146
+ try:
147
+ # 执行subconverter
148
+ process = subprocess.Popen(
149
+ cmd,
150
+ stdout=subprocess.PIPE,
151
+ stderr=subprocess.PIPE,
152
+ universal_newlines=True,
153
+ cwd=os.path.dirname(self.subconverter_path) # 尝试在subconverter目录下运行
154
+ )
155
+ stdout, stderr = process.communicate(timeout=60)
156
+
157
+ # 记录完整的 subconverter 输出用于调试
158
+ logger.info(f"subconverter STDOUT:\n---\n{stdout}\n---")
159
+ if stderr:
160
+ logger.warning(f"subconverter STDERR:\n---\n{stderr}\n---")
161
+
162
+ # 检查 subconverter 返回码
163
+ if process.returncode != 0:
164
+ logger.error(f"subconverter执行失败,返回码: {process.returncode}.")
165
+ # ... (省略 subconverter 失败的回退逻辑) ...
166
+ raise RuntimeError(f"subconverter转换失败(code {process.returncode})。 Subconverter STDERR: {stderr[:200]}...")
167
+ else:
168
+ # subconverter 返回码为 0,表示理论上成功
169
+ logger.info("subconverter成功执行 (返回码 0)")
170
+ # 验证输出文件是否存在且非空
171
+ if os.path.exists(self.config_path) and os.path.getsize(self.config_path) > 0:
172
+ logger.info(f"配置文件已生成,路径: {self.config_path},大小: {os.path.getsize(self.config_path)} 字节")
173
+ else:
174
+ logger.error(f"subconverter执行成功,但配置文件不存在或为空: {self.config_path}. 请检查subconverter日志了解详情。")
175
+ # 检查STDERR中是否有线索
176
+ error_detail = stderr if stderr else "(无错误输出)"
177
+ raise RuntimeError(f"subconverter执行成功但未生成有效配置文件。 Subconverter STDERR: {error_detail[:500]}...")
178
+
179
+ except subprocess.TimeoutExpired:
180
+ logger.error(f"执行subconverter超时 (超过60秒)")
181
+ raise RuntimeError("subconverter执行超时")
182
+ except (subprocess.SubprocessError, OSError) as e:
183
+ logger.error(f"执行subconverter时发生错误: {str(e)}")
184
+ raise RuntimeError(f"配置转换失败: {str(e)}")
185
+
186
+ def _add_clash_headers(self):
187
+ """添加基本的Clash配置头"""
188
+ return """# 自动生成的Clash配置
189
+ port: 7890
190
+ socks-port: 7891
191
+ mixed-port: 7890
192
+ allow-lan: true
193
+ mode: Rule
194
+ log-level: info
195
+ external-controller: 127.0.0.1:9090
196
+ secret: ""
197
+
198
+ """
199
+
200
+ def _patch_config(self):
201
+ """
202
+ 修改配置文件以确保端口设置正确,并兼容Clash Meta
203
+ """
204
+ # 检查配置文件是否存在
205
+ if not os.path.exists(self.config_path):
206
+ logger.warning(f"配置文件不存在,无法修补: {self.config_path}")
207
+ return
208
+
209
+ try:
210
+ # 读取配置内容
211
+ with open(self.config_path, "r", encoding="utf-8") as f:
212
+ config_content = f.read()
213
+
214
+ # 确保配置包含必要的端口设置
215
+ has_patch = False
216
+
217
+ # 这里需要检查配置是否为有效的YAML并进行适当修补
218
+ # 为简单起见,我们只检查和添加一些基本端口配置
219
+
220
+ if "mixed-port:" not in config_content and "port:" not in config_content:
221
+ # 添加混合端口配置
222
+ config_content = "mixed-port: 7890\n" + config_content
223
+ has_patch = True
224
+
225
+ # 不要添加重复的external-controller配置
226
+ if "external-controller:" not in config_content:
227
+ # 添加API控制器配置 (兼容Clash Meta)
228
+ config_content = "external-controller: 127.0.0.1:9090\n" + config_content
229
+ has_patch = True
230
+
231
+ # Clash Meta特定配置
232
+ if "find-process-mode:" not in config_content:
233
+ config_content = "find-process-mode: strict\n" + config_content
234
+ has_patch = True
235
+
236
+ # 确保启用了API
237
+ if "secret:" not in config_content:
238
+ config_content = "secret: ''\n" + config_content
239
+ has_patch = True
240
+
241
+ # 尝试解析YAML并修复代理组引用问题
242
+ try:
243
+ import yaml
244
+ logger.info("正在使用PyYAML解析和修复配置")
245
+
246
+ # 解析配置
247
+ config_yaml = yaml.safe_load(config_content)
248
+
249
+ # 检查解析结果是否为有效字典
250
+ if not isinstance(config_yaml, dict):
251
+ logger.error("配置文件解析结果不是有效的YAML字典,无法进行修复")
252
+ # 使用简单的文本分析,避免添加重复的配置
253
+ has_proxy_groups = "proxy-groups:" in config_content
254
+ has_global_group = "GLOBAL" in config_content or "- name: GLOBAL" in config_content
255
+ if not has_proxy_groups and not has_global_group:
256
+ logger.info("未检测到proxy-groups或GLOBAL策略组,添加基本的GLOBAL策略组")
257
+ config_content += "\nproxy-groups:\n - name: GLOBAL\n type: select\n proxies:\n - DIRECT\n"
258
+ has_patch = True
259
+ else:
260
+ logger.info("检测到已有proxy-groups或GLOBAL配置,跳过添加")
261
+ else:
262
+ # --- 开始修复逻辑 ---
263
+ groups_fixed = False
264
+ proxies_list = config_yaml.get("proxies")
265
+
266
+ # 1. 检查代理列表是否有效
267
+ if not proxies_list or not isinstance(proxies_list, list):
268
+ logger.error("配置文件缺少有效的 'proxies' 列表或列表为空!将仅使用 DIRECT 代理。请检查原始订阅内容。")
269
+ # 设置一个最小化的 proxies 和 proxy-groups
270
+ # 不要在 proxies 中显式定义 DIRECT,Clash 内置了它
271
+ config_yaml["proxies"] = []
272
+ config_yaml["proxy-groups"] = [{
273
+ "name": "GLOBAL",
274
+ "type": "select",
275
+ "proxies": ["DIRECT"] # 直接引用内置的 DIRECT
276
+ }]
277
+ proxy_names = {"DIRECT", "REJECT"} # 仍然需要知道内置名称
278
+ groups_fixed = True # 标记需要重新生成配置内容
279
+ has_patch = True
280
+ else:
281
+ # 2. 代理列表有效,获取所有代理节点名称
282
+ proxy_names = set()
283
+ valid_proxies_list = []
284
+ for proxy in proxies_list:
285
+ if isinstance(proxy, dict) and "name" in proxy:
286
+ proxy_names.add(proxy["name"])
287
+ valid_proxies_list.append(proxy) # 只保留有效的代理定义
288
+ else:
289
+ logger.warning(f"配置文件中的 'proxies' 列表包含无效条目: {proxy}")
290
+
291
+ # 如果所有条目都无效,则回退
292
+ if not valid_proxies_list:
293
+ logger.error("配置文件中的 'proxies' 列表所有条目均无效!将仅使用 DIRECT 代理。")
294
+ config_yaml["proxies"] = [{"name": "DIRECT", "type": "direct"}]
295
+ config_yaml["proxy-groups"] = [{
296
+ "name": "GLOBAL",
297
+ "type": "select",
298
+ "proxies": ["DIRECT"]
299
+ }]
300
+ proxy_names = {"DIRECT", "REJECT"}
301
+ groups_fixed = True
302
+ has_patch = True
303
+ else:
304
+ config_yaml["proxies"] = valid_proxies_list # 更新为仅包含有效代理的列表
305
+ # 添加内置代理
306
+ proxy_names.add("DIRECT")
307
+ proxy_names.add("REJECT")
308
+
309
+ # 3. 修复或添加代理组
310
+ proxy_groups = config_yaml.get("proxy-groups", [])
311
+ if not isinstance(proxy_groups, list):
312
+ logger.warning("'proxy-groups' 不是一个列表,将重新创建")
313
+ proxy_groups = []
314
+ config_yaml["proxy-groups"] = proxy_groups
315
+ groups_fixed = True
316
+
317
+ has_global = False
318
+ valid_groups = []
319
+ for group in proxy_groups:
320
+ if not isinstance(group, dict) or "name" not in group or "type" not in group:
321
+ logger.warning(f"'proxy-groups' 中包含无效组定义: {group}")
322
+ groups_fixed = True
323
+ continue # 跳过无效组
324
+
325
+ if "proxies" in group:
326
+ if not isinstance(group["proxies"], list):
327
+ logger.warning(f"代理组 '{group['name']}' 的 'proxies' 不是列表,设置为 ['DIRECT']")
328
+ group["proxies"] = ["DIRECT"]
329
+ groups_fixed = True
330
+ else:
331
+ # 删除组中引用的不存在的代理
332
+ original_group_proxies = group["proxies"].copy()
333
+ group["proxies"] = [p for p in group["proxies"] if p in proxy_names or p == group["name"]]
334
+ if len(group["proxies"]) != len(original_group_proxies):
335
+ removed = set(original_group_proxies) - set(group["proxies"])
336
+ if removed:
337
+ logger.warning(f"删除代理组 '{group['name']}' 中的无效引用: {', '.join(removed)}")
338
+ groups_fixed = True
339
+
340
+ # 确保代理组至少有一个有效代理
341
+ if not group["proxies"]:
342
+ logger.warning(f"为空代理组 '{group['name']}' 添加默认代理: DIRECT")
343
+ group["proxies"].append("DIRECT")
344
+ groups_fixed = True
345
+ else:
346
+ # 如果组定义没有proxies列表(例如 url-test),通常是正常的
347
+ pass
348
+
349
+ valid_groups.append(group)
350
+ if group["name"] == "GLOBAL":
351
+ has_global = True
352
+
353
+ # 更新为只包含有效组定义的列表
354
+ config_yaml["proxy-groups"] = valid_groups
355
+
356
+ # 4. 如果没有 GLOBAL 组,添加一个
357
+ if not has_global:
358
+ logger.info("添加 GLOBAL 策略组")
359
+ # 确定要放入 GLOBAL 组的代理 (所有已知真实代理 + DIRECT)
360
+ global_proxies = [p for p in proxy_names if p not in ("DIRECT", "REJECT")]
361
+ global_proxies.sort() # 可选:排序
362
+ global_proxies.insert(0, "DIRECT") # 将 DIRECT 放前面
363
+
364
+ config_yaml["proxy-groups"].insert(0, {
365
+ "name": "GLOBAL",
366
+ "type": "select",
367
+ "proxies": global_proxies
368
+ })
369
+ groups_fixed = True
370
+ has_patch = True
371
+
372
+ # 5. 如果进行了修复,重新序列化 YAML
373
+ if groups_fixed:
374
+ try:
375
+ config_content = yaml.dump(config_yaml, sort_keys=False, allow_unicode=True, default_flow_style=None)
376
+ has_patch = True # 确保标记为已修改
377
+ except Exception as dump_err:
378
+ logger.error(f"重新序列化 YAML 失败: {dump_err}")
379
+ # 序列化失败,放弃修复,依赖原始 content + 基础 patch
380
+ # 重新读取原始content,只应用基础patch
381
+ with open(self.config_path, "r", encoding="utf-8") as f:
382
+ config_content = f.read()
383
+ if "mixed-port:" not in config_content and "port:" not in config_content: config_content = "mixed-port: 7890\n" + config_content
384
+ if "external-controller:" not in config_content: config_content = "external-controller: 127.0.0.1:9090\n" + config_content
385
+ if "find-process-mode:" not in config_content: config_content = "find-process-mode: strict\n" + config_content
386
+ if "secret:" not in config_content: config_content = "secret: ''\n" + config_content
387
+ has_patch = True # 标记基础 patch 已应用
388
+
389
+ # --- 处理 YAML 导入或解析错误的回退逻辑 ---
390
+ except ImportError as e:
391
+ logger.error(f"导入PyYAML模块失败: {str(e)}. 无法执行详细配置修复。")
392
+ # 使用简单的文本分析,避免添加重复的配置
393
+ has_proxy_groups = "proxy-groups:" in config_content
394
+ has_global_group = "GLOBAL" in config_content or "- name: GLOBAL" in config_content
395
+ if not has_proxy_groups and not has_global_group:
396
+ logger.info("未检测到proxy-groups或GLOBAL策略组,添加基本的GLOBAL策略组")
397
+ config_content += "\nproxy-groups:\n - name: GLOBAL\n type: select\n proxies:\n - DIRECT\n"
398
+ has_patch = True
399
+ else:
400
+ logger.info("检测到已有proxy-groups或GLOBAL配置,跳过添加")
401
+ except Exception as yaml_err:
402
+ logger.error(f"处理YAML配置时出错: {str(yaml_err)}. 无法执行详细配置修复。")
403
+ # 使用简单的文本分析,避免添加重复的配置
404
+ has_proxy_groups = "proxy-groups:" in config_content
405
+ has_global_group = "GLOBAL" in config_content or "- name: GLOBAL" in config_content
406
+ if not has_proxy_groups and not has_global_group:
407
+ logger.info("未检测到proxy-groups或GLOBAL策略组,添加基本的GLOBAL策略组")
408
+ config_content += "\nproxy-groups:\n - name: GLOBAL\n type: select\n proxies:\n - DIRECT\n"
409
+ has_patch = True
410
+ else:
411
+ logger.info("检测到已有proxy-groups或GLOBAL配置,跳过添加")
412
+
413
+ # --- 写回文件 ---
414
+ if has_patch:
415
+ logger.info("检测到配置更改,正在写回文件...")
416
+ try:
417
+ with open(self.config_path, "w", encoding="utf-8") as f:
418
+ f.write(config_content)
419
+ logger.info("已修补配置文件并成功保存")
420
+ except Exception as write_err:
421
+ logger.error(f"写回配置文件失败: {write_err}")
422
+ # 记录错误,但允许程序继续
423
+ else:
424
+ logger.info("配置文件无需修补")
425
+
426
+ # 这个 except 对应最外层的 try
427
+ except Exception as e:
428
+ logger.error(f"修补配置文件过程中发生意外错误: {str(e)}")
429
+
430
+ def _mask_url(self, url):
431
+ """
432
+ 遮蔽URL中的敏感信息用于日志记录
433
+
434
+ Args:
435
+ url: 原始URL
436
+
437
+ Returns:
438
+ str: 遮蔽后的URL
439
+ """
440
+ try:
441
+ parsed = urlparse(url)
442
+ netloc = parsed.netloc
443
+
444
+ # 如果URL包含用户名和密码,则遮蔽密码
445
+ if "@" in netloc:
446
+ userpass, host = netloc.split("@", 1)
447
+ if ":" in userpass:
448
+ user, _ = userpass.split(":", 1)
449
+ netloc = f"{user}:***@{host}"
450
+
451
+ masked_url = url.replace(parsed.netloc, netloc)
452
+
453
+ # 确保不显示完整的token或密钥
454
+ if "?" in masked_url:
455
+ base, query = masked_url.split("?", 1)
456
+ masked_url = f"{base}?****"
457
+
458
+ return masked_url
459
+
460
+ except Exception:
461
+ # 如果解析失败,返回更简单的遮蔽
462
+ return f"{url[:10]}...{url[-5:]}" if len(url) > 15 else "***"