Spaces:
Paused
Paused
Upload 14 files
Browse files- app/data/Dockerfile +133 -0
- app/data/app/__init__.py +2 -0
- app/data/app/__pycache__/__init__.cpython-313.pyc +0 -0
- app/data/app/__pycache__/auth.cpython-313.pyc +0 -0
- app/data/app/__pycache__/clash_manager.cpython-313.pyc +0 -0
- app/data/app/__pycache__/debug_tools.cpython-313.pyc +0 -0
- app/data/app/__pycache__/main.cpython-313.pyc +0 -0
- app/data/app/__pycache__/sub_manager.cpython-313.pyc +0 -0
- app/data/app/auth.py +44 -0
- app/data/app/clash_manager.py +377 -0
- app/data/app/data/config.yaml +618 -0
- app/data/app/debug_tools.py +177 -0
- app/data/app/main.py +521 -0
- app/data/app/sub_manager.py +462 -0
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 "***"
|