Spaces:
Running
Running
File size: 7,850 Bytes
b2ab624 3d91208 7e7a5b0 3d91208 6eaf348 7e7a5b0 6eaf348 c746a96 28832ec 6eaf348 7e7a5b0 28832ec 3d91208 6eaf348 3d91208 6eaf348 3d91208 6eaf348 3d91208 1d668b6 3d91208 6eaf348 3d91208 6eaf348 3d91208 6eaf348 7e7a5b0 6eaf348 3d91208 6eaf348 3d91208 28832ec 6eaf348 28832ec 6eaf348 28832ec 6eaf348 28832ec 6eaf348 28832ec 6eaf348 28832ec 6eaf348 28832ec 6eaf348 28832ec c746a96 6eaf348 28832ec |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
import os
import re
import ast
import yaml
import httpx
import base64
import inspect
from dotenv import load_dotenv
from fastapi import FastAPI, Response, HTTPException, Request
HEADERS = {
'accept': 'application/json, text/plain, */*',
'accept-language': 'zh-CN',
'pragma': 'no-cache',
'sec-ch-ua': '"Not?A_Brand";v="8", "Chromium";v="108"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site',
'user-agent': 'ClashforWindows/0.20.39',
}
app = FastAPI()
# 从环境变量中获取密钥
load_dotenv()
API_KEY = os.environ.get('API_KEY')
SUBSCRIBE_URLS = os.environ.get('SUBSCRIBE_URLS') # 存储真实URL的变量
INJECT_SCRIPT = os.environ.get('INJECT_SCRIPT')
def validate_function_signature(code_string):
""" 使用 ast 验证函数签名是否符合要求。
"""
try:
module = ast.parse(code_string)
if not module.body:
print("错误:代码为空。")
return False
if len(module.body) != 1 or not isinstance(module.body[0], ast.FunctionDef):
print("错误:代码必须包含单个函数定义。")
return False
function_def = module.body[0]
# Step 01. 检查函数名
if function_def.name != "handle_mixin":
print(f"错误:函数名必须为 'handle_mixin',而不是 '{function_def.name}'。")
return False
# Step 02. 检查参数个数
args = function_def.args
if args.defaults or args.kw_defaults or args.vararg or args.kwarg or args.posonlyargs:
print("错误:函数不能有默认参数、可变位置参数、可变关键字参数、或者仅位置参数")
return False
if len(args.args) != 1:
print(f"错误:函数必须恰好接受一个参数,而不是 {len(args.args)} 个。")
return False
# Step 03. 检查参数名称
if args.args[0].arg != "content":
print(f"错误:参数名称必须为 'content',而不是 '{args.args[0].arg}'。")
return False
return True
except SyntaxError as e:
print(f"语法错误:{e}")
return False
except Exception as e:
print(f"发生错误:{e}")
return False
def load_mixin_function(code_string) -> callable:
if not validate_function_signature(code_string):
return
try:
code_obj = compile(code_string, '<string>', 'exec')
local_namespace = {}
exec(code_obj, local_namespace)
handle_mixin = local_namespace['handle_mixin']
return handle_mixin
except Exception as e:
print(f"解析函数时出错:{e}")
try:
handle_mixin = load_mixin_function(INJECT_SCRIPT)
except Exception as e:
handle_mixin = lambda x:x
print(f"无法加载 MINIX 函数, 错误信息: {e}")
def subscribe_mixin(content: str) -> str:
"""输入 YAML 字符串,输出转换后的 YAML 字符串
"""
try:
d = yaml.safe_load(content)
my_auto_group_name = "AI Unrestrict"
regex_list = [
re.compile(r"美国", re.IGNORECASE),
re.compile(r"america", re.IGNORECASE),
re.compile(r"us", re.IGNORECASE),
re.compile(r"新加坡", re.IGNORECASE),
re.compile(r"singapore", re.IGNORECASE),
re.compile(r"sg", re.IGNORECASE),
re.compile(r"加拿大", re.IGNORECASE),
re.compile(r"canada", re.IGNORECASE),
re.compile(r"ca", re.IGNORECASE),
]
matching_proxies = [] # 用于存储匹配的代理名称
# 1. 查找并保存符合正则表达式的 proxy name
if "proxies" in d and isinstance(d["proxies"], list):
for proxy in d["proxies"]:
if "name" in proxy:
for regex in regex_list:
if regex.search(proxy["name"]):
matching_proxies.append(proxy["name"])
break
# 2. 创建新的 proxy-group 对象
new_proxy_group = {
"name": my_auto_group_name,
"type": "url-test",
"proxies": matching_proxies,
"url": "http://www.gstatic.com/generate_204",
"interval": 7200
}
# 3. 将新的 proxy-group 添加到 proxy-groups 数组
if "proxy-groups" in d and isinstance(d["proxy-groups"], list):
d["proxy-groups"].append(new_proxy_group)
# 4. 将 myAutoGroupName 添加到第一个 proxy-group 的 "proxies" 列表的最前面
if d["proxy-groups"] and len(d["proxy-groups"]) > 0 and \
"proxies" in d["proxy-groups"][0] and isinstance(d["proxy-groups"][0]["proxies"], list):
d["proxy-groups"][0]["proxies"].insert(0, my_auto_group_name)
else:
d["proxy-groups"] = [new_proxy_group]
d.pop('socks-port', None)
# 在此处尝试调用 mixin 函数
try:
d = handle_mixin(d)
except Exception as e:
print(f"执行 Minix 函数时出错!")
modified_yaml = yaml.dump(d, allow_unicode=True, indent=2)
return modified_yaml
except yaml.YAMLError as e:
print(f"YAML 解析错误:{e}")
return ""
except Exception as e:
print(f"其他错误:{e}")
return ""
@app.get("/getsub")
async def read_subscribe(request: Request, key: str):
# 验证API Key
if key != API_KEY:
raise HTTPException(status_code=401, detail="Unauthorized")
# 从环境变量获取URL列表
if not SUBSCRIBE_URLS:
raise HTTPException(status_code=500, detail="SUBSCRIBE_URLS not configured")
urls = SUBSCRIBE_URLS.split('\n')
proxy_urls = [
f'{request.base_url}proxy?encoded_url={base64.b64encode(url.encode()).decode()}'
for url in urls
if url.strip()
]
merged_urls: str = '|'.join(proxy_urls)
args = {
'target': 'clash',
'url': merged_urls
}
async with httpx.AsyncClient() as client:
try:
resp = await client.get('http://127.0.0.1:25500/sub', params=args, headers=HEADERS)
resp.raise_for_status()
data = resp.text
data = subscribe_mixin(data)
return Response(content=data, media_type='text/yaml')
except httpx.RequestError as e:
raise HTTPException(status_code=500, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/proxy")
async def proxy_url(encoded_url: str):
'''
有一些订阅地址, 需要检测请求头.
然而 tindy2013/subconverter 项目并不支持请求头配置.
所以将请求重定向到本服务中, 然后通过自定义请求头绕过.
'''
try:
try:
decoded_bytes = base64.b64decode(encoded_url)
target_url = decoded_bytes.decode('utf-8')
except Exception as e:
raise HTTPException(status_code=400, detail="Invalid base64 encoding")
async with httpx.AsyncClient() as client:
response = await client.get(target_url, headers=HEADERS)
return Response(
content=response.content,
status_code=response.status_code,
headers=dict(response.headers),
media_type=response.headers.get("content-type")
)
except httpx.RequestError as e:
raise HTTPException(status_code=500, detail=f"Request failed: {str(e)}")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error: {str(e)}")
@app.get("/")
async def read_root():
return {"hello": 'world'}
|