pymecli 0.5.1__tar.gz → 0.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pymecli-0.5.1 → pymecli-0.5.3}/PKG-INFO +1 -1
- {pymecli-0.5.1 → pymecli-0.5.3}/cli/dst.py +9 -9
- {pymecli-0.5.1 → pymecli-0.5.3}/cli/util.py +4 -8
- pymecli-0.5.3/core/clash.py +368 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/core/config.py +1 -1
- pymecli-0.5.3/data/template.yaml +180 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/pyproject.toml +1 -1
- pymecli-0.5.1/core/clash.py +0 -664
- pymecli-0.5.1/data/template.yaml +0 -70
- {pymecli-0.5.1 → pymecli-0.5.3}/.gitignore +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/README.md +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/api/__init__.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/api/v1/__init__.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/api/v1/clash.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/api/v1/redis.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/cli/__init__.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/cli/bitget.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/cli/example.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/cli/fast.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/cli/gate.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/cli/redis_csv.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/core/__init__.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/core/redis_client.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/crypto/__init__.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/crypto/bitget.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/crypto/gate.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/data/__init__.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/data/dou_dict.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/data/dou_list.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/data/main.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/models/__init__.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/models/douzero_model.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/models/ocr_model.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/models/response.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/utils/__init__.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/utils/elapsed.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/utils/helper.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/utils/logger.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/utils/mysql.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/utils/path.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/utils/pd.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/utils/pyredis.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/utils/sleep.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/utils/text.py +0 -0
- {pymecli-0.5.1 → pymecli-0.5.3}/utils/toml.py +0 -0
|
@@ -149,13 +149,13 @@ def convert_update(
|
|
|
149
149
|
continue
|
|
150
150
|
mod_data = versions.get(wid)
|
|
151
151
|
if mod_data is None:
|
|
152
|
-
typer.secho(f"
|
|
152
|
+
typer.secho(f"✗ {mod_key}: 未获取到版本信息", fg=typer.colors.YELLOW)
|
|
153
153
|
continue
|
|
154
154
|
new_version = mod_data.get("version")
|
|
155
155
|
title = mod_data.get("title", "")
|
|
156
156
|
if new_version is None:
|
|
157
157
|
typer.secho(
|
|
158
|
-
f"
|
|
158
|
+
f"✗ {mod_key} ({title}): 未获取到版本信息", fg=typer.colors.YELLOW
|
|
159
159
|
)
|
|
160
160
|
continue
|
|
161
161
|
old_version = mod_info.get("version")
|
|
@@ -163,7 +163,7 @@ def convert_update(
|
|
|
163
163
|
mod_info["version"] = new_version
|
|
164
164
|
updated_count += 1
|
|
165
165
|
typer.secho(
|
|
166
|
-
f"
|
|
166
|
+
f"✓ {mod_key} ({title}): {old_version} -> {new_version}",
|
|
167
167
|
fg=typer.colors.GREEN,
|
|
168
168
|
)
|
|
169
169
|
else:
|
|
@@ -185,9 +185,9 @@ def convert_update(
|
|
|
185
185
|
raise typer.Exit(code=1)
|
|
186
186
|
|
|
187
187
|
if updated_count > 0:
|
|
188
|
-
typer.secho(f"
|
|
188
|
+
typer.secho(f"🎉 成功更新 {updated_count} 个 mod 版本", fg=typer.colors.GREEN)
|
|
189
189
|
else:
|
|
190
|
-
typer.secho("
|
|
190
|
+
typer.secho("✓ 所有MOD已是最新版本", fg=typer.colors.YELLOW)
|
|
191
191
|
|
|
192
192
|
|
|
193
193
|
@app.command()
|
|
@@ -255,12 +255,12 @@ def mod_setup(
|
|
|
255
255
|
title = mod_data.get("title", "")
|
|
256
256
|
|
|
257
257
|
typer.secho(
|
|
258
|
-
f"
|
|
258
|
+
f"{item} ({title}): {new_version}",
|
|
259
259
|
fg=typer.colors.WHITE,
|
|
260
260
|
)
|
|
261
261
|
|
|
262
262
|
typer.secho(
|
|
263
|
-
f"
|
|
263
|
+
f"📢 client_mods({len(client_ids)}):",
|
|
264
264
|
fg=typer.colors.GREEN,
|
|
265
265
|
)
|
|
266
266
|
|
|
@@ -277,14 +277,14 @@ def mod_setup(
|
|
|
277
277
|
title = mod_data.get("title", "")
|
|
278
278
|
|
|
279
279
|
typer.secho(
|
|
280
|
-
f"
|
|
280
|
+
f"{item} ({title}): {new_version}",
|
|
281
281
|
fg=typer.colors.WHITE,
|
|
282
282
|
)
|
|
283
283
|
|
|
284
284
|
for p in output:
|
|
285
285
|
with open(p, "w", encoding="utf-8") as f:
|
|
286
286
|
f.write("\n".join(setup_list))
|
|
287
|
-
typer.secho(f"
|
|
287
|
+
typer.secho(f"💾 <setup_list>已保存到: {p}", fg=typer.colors.CYAN)
|
|
288
288
|
|
|
289
289
|
|
|
290
290
|
if __name__ == "__main__":
|
|
@@ -68,12 +68,8 @@ def emoji():
|
|
|
68
68
|
emoji_list = [
|
|
69
69
|
"『",
|
|
70
70
|
"』",
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"❗",
|
|
74
|
-
"⭕",
|
|
75
|
-
"❓",
|
|
76
|
-
"❌",
|
|
71
|
+
"✓",
|
|
72
|
+
"✗",
|
|
77
73
|
"⤴︎",
|
|
78
74
|
"⤵︎",
|
|
79
75
|
"⇡",
|
|
@@ -147,7 +143,7 @@ def say():
|
|
|
147
143
|
@app.command()
|
|
148
144
|
def ipv6():
|
|
149
145
|
"""
|
|
150
|
-
获取本地
|
|
146
|
+
获取本地 IPv6 稳定地址
|
|
151
147
|
"""
|
|
152
148
|
ips = []
|
|
153
149
|
adapters = ifaddr.get_adapters(include_unconfigured=False)
|
|
@@ -178,7 +174,7 @@ def ipv6():
|
|
|
178
174
|
print("未找到稳定 IPV6 地址")
|
|
179
175
|
return ips
|
|
180
176
|
|
|
181
|
-
print(f"
|
|
177
|
+
print(f"IPv6 稳定地址: {ips}")
|
|
182
178
|
|
|
183
179
|
r = get_redis_client_sync()
|
|
184
180
|
r.set("local.IPv6", ips[0])
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
import yaml
|
|
6
|
+
from fastapi import Depends
|
|
7
|
+
|
|
8
|
+
module_dir = Path(__file__).resolve().parent.parent
|
|
9
|
+
|
|
10
|
+
TEMPLATE_URL = "https://raw.githubusercontent.com/meme2046/data/main/clash/template.yaml"
|
|
11
|
+
|
|
12
|
+
# 白名单模式最终规则
|
|
13
|
+
WHITELIST_RULES = [
|
|
14
|
+
"RULE-SET,applications,DIRECT",
|
|
15
|
+
"RULE-SET,private,DIRECT",
|
|
16
|
+
"RULE-SET,icloud,DIRECT",
|
|
17
|
+
"RULE-SET,apple,DIRECT",
|
|
18
|
+
"RULE-SET,google,全局选择",
|
|
19
|
+
"RULE-SET,proxy,全局选择",
|
|
20
|
+
"RULE-SET,direct,DIRECT",
|
|
21
|
+
"RULE-SET,lancidr,DIRECT",
|
|
22
|
+
"RULE-SET,cncidr,DIRECT",
|
|
23
|
+
"RULE-SET,telegramcidr,全局选择",
|
|
24
|
+
"GEOIP,LAN,DIRECT",
|
|
25
|
+
"GEOIP,CN,DIRECT",
|
|
26
|
+
"MATCH,全局选择",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
# 黑名单模式最终规则
|
|
30
|
+
BLACKLIST_RULES = [
|
|
31
|
+
"RULE-SET,applications,DIRECT",
|
|
32
|
+
"RULE-SET,private,DIRECT",
|
|
33
|
+
"RULE-SET,tld-not-cn,全局选择",
|
|
34
|
+
"RULE-SET,gfw,全局选择",
|
|
35
|
+
"RULE-SET,telegramcidr,全局选择",
|
|
36
|
+
"MATCH,DIRECT",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ClashConfig:
|
|
41
|
+
def __init__(self, rule_base_url: str, my_rule_base_url: str, request_proxy: str):
|
|
42
|
+
self.rule_base_url = rule_base_url.rstrip("/")
|
|
43
|
+
self.my_rule_base_url = my_rule_base_url.rstrip("/")
|
|
44
|
+
self.request_proxy = request_proxy
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ClashYamlGenerator:
|
|
48
|
+
def __init__(self, config: ClashConfig):
|
|
49
|
+
self.rule_base_url = config.rule_base_url
|
|
50
|
+
self.my_rule_base_url = config.my_rule_base_url
|
|
51
|
+
self.request_proxy = config.request_proxy
|
|
52
|
+
|
|
53
|
+
# ---------- 通用 helpers ----------
|
|
54
|
+
|
|
55
|
+
def _get_proxies(self):
|
|
56
|
+
if not self.request_proxy:
|
|
57
|
+
return None
|
|
58
|
+
return {"http": self.request_proxy, "https": self.request_proxy}
|
|
59
|
+
|
|
60
|
+
def _load_template(self, proxies=None):
|
|
61
|
+
"""从远程获取 template.yaml,失败时回退到本地文件"""
|
|
62
|
+
try:
|
|
63
|
+
response = requests.get(TEMPLATE_URL, proxies=proxies)
|
|
64
|
+
response.raise_for_status()
|
|
65
|
+
template = yaml.safe_load(response.text)
|
|
66
|
+
if template:
|
|
67
|
+
return template
|
|
68
|
+
except Exception:
|
|
69
|
+
pass
|
|
70
|
+
with open(str(module_dir / "data/template.yaml"), "r", encoding="utf-8") as f:
|
|
71
|
+
return yaml.safe_load(f)
|
|
72
|
+
|
|
73
|
+
def _fetch_subscriptions(self, sub_list, proxies):
|
|
74
|
+
"""抓取并校验所有订阅,返回 [(item, ps), ...] 和 userinfo"""
|
|
75
|
+
userinfo = ""
|
|
76
|
+
results = []
|
|
77
|
+
for item in sub_list:
|
|
78
|
+
headers = {"User-Agent": item["user_agent"]} if item["user_agent"] else {}
|
|
79
|
+
|
|
80
|
+
if not item["url"]:
|
|
81
|
+
raise ValueError("Invalid subscription URL.")
|
|
82
|
+
response = requests.get(item["url"], headers=headers, proxies=proxies)
|
|
83
|
+
response.raise_for_status()
|
|
84
|
+
if not userinfo:
|
|
85
|
+
userinfo = response.headers["Subscription-Userinfo"]
|
|
86
|
+
remote_config = yaml.safe_load(response.text)
|
|
87
|
+
|
|
88
|
+
# 检查 remote_config 是否为 None
|
|
89
|
+
if remote_config is None:
|
|
90
|
+
raise ValueError("Invalid subscription content: empty or invalid YAML.")
|
|
91
|
+
|
|
92
|
+
ps = remote_config.get("proxies", [])
|
|
93
|
+
if not ps:
|
|
94
|
+
raise ValueError("No proxies found in subscription.")
|
|
95
|
+
|
|
96
|
+
results.append((item, ps))
|
|
97
|
+
return results, userinfo
|
|
98
|
+
|
|
99
|
+
def _add_inline_rule_providers(self, template, proxies):
|
|
100
|
+
"""从 my_rule_base_url 拉取 inline 规则集并写入 template"""
|
|
101
|
+
rule_list = [
|
|
102
|
+
[f"{self.my_rule_base_url}/direct.yaml", "DIRECT"],
|
|
103
|
+
[f"{self.my_rule_base_url}/proxy.yaml", "全局选择"],
|
|
104
|
+
[f"{self.my_rule_base_url}/round.yaml", "轮询"],
|
|
105
|
+
[f"{self.my_rule_base_url}/reject.yaml", "REJECT"],
|
|
106
|
+
]
|
|
107
|
+
for url, target in rule_list:
|
|
108
|
+
response = requests.get(url, proxies=proxies)
|
|
109
|
+
response.raise_for_status()
|
|
110
|
+
remote = yaml.safe_load(response.text)
|
|
111
|
+
name = os.path.basename(url)
|
|
112
|
+
template["rule-providers"][name] = {
|
|
113
|
+
"type": "inline",
|
|
114
|
+
"behavior": "classical",
|
|
115
|
+
"payload": remote["payload"],
|
|
116
|
+
}
|
|
117
|
+
template["rules"].append(f"RULE-SET,{name},{target}")
|
|
118
|
+
|
|
119
|
+
def _http_rule_provider(self, name, behavior):
|
|
120
|
+
"""生成单个 http 类型 rule-provider"""
|
|
121
|
+
return {
|
|
122
|
+
"type": "http",
|
|
123
|
+
"format": "yaml",
|
|
124
|
+
"behavior": behavior,
|
|
125
|
+
"url": f"{self.rule_base_url}/{name}.txt",
|
|
126
|
+
"path": f"./ruleset/{name}.yaml",
|
|
127
|
+
"interval": 86400,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
def _whitelist_http_rule_providers(self):
|
|
131
|
+
return {
|
|
132
|
+
"applications": self._http_rule_provider("applications", "classical"),
|
|
133
|
+
"private": self._http_rule_provider("private", "domain"),
|
|
134
|
+
"icloud": self._http_rule_provider("icloud", "domain"),
|
|
135
|
+
"apple": self._http_rule_provider("apple", "domain"),
|
|
136
|
+
"google": self._http_rule_provider("google", "domain"),
|
|
137
|
+
"proxy": self._http_rule_provider("proxy", "domain"),
|
|
138
|
+
"direct": self._http_rule_provider("direct", "domain"),
|
|
139
|
+
"lancidr": self._http_rule_provider("lancidr", "ipcidr"),
|
|
140
|
+
"cncidr": self._http_rule_provider("cncidr", "ipcidr"),
|
|
141
|
+
"telegramcidr": self._http_rule_provider("telegramcidr", "ipcidr"),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
def _blacklist_http_rule_providers(self):
|
|
145
|
+
return {
|
|
146
|
+
"applications": self._http_rule_provider("applications", "classical"),
|
|
147
|
+
"private": self._http_rule_provider("private", "domain"),
|
|
148
|
+
"tld-not-cn": self._http_rule_provider("tld-not-cn", "domain"),
|
|
149
|
+
"telegramcidr": self._http_rule_provider("telegramcidr", "ipcidr"),
|
|
150
|
+
"gfw": self._http_rule_provider("gfw", "domain"),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
def _provider_proxy_groups(self, sub_list):
|
|
154
|
+
"""provider 风格的 proxy-groups(genPW / genPB)"""
|
|
155
|
+
use_list = [f"provider.{item['name']}" for item in sub_list]
|
|
156
|
+
return [
|
|
157
|
+
{
|
|
158
|
+
"name": "全局选择",
|
|
159
|
+
"type": "select",
|
|
160
|
+
"proxies": ["自动选择", "手动选择", "轮询"]
|
|
161
|
+
+ [item["name"] for item in sub_list],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "自动选择",
|
|
165
|
+
"type": "url-test",
|
|
166
|
+
"url": "https://www.gstatic.com/generate_204",
|
|
167
|
+
"interval": 300,
|
|
168
|
+
"tolerance": 11,
|
|
169
|
+
"lazy": True,
|
|
170
|
+
"use": use_list,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "手动选择",
|
|
174
|
+
"type": "select",
|
|
175
|
+
"use": use_list,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "轮询",
|
|
179
|
+
"type": "load-balance",
|
|
180
|
+
"url": "https://api.bitget.com/api/v2/public/time",
|
|
181
|
+
"interval": 300,
|
|
182
|
+
"lazy": True,
|
|
183
|
+
"strategy": "round-robin",
|
|
184
|
+
"use": use_list,
|
|
185
|
+
},
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
def _inline_proxy_groups(self, sub_list):
|
|
189
|
+
"""include-all 风格的 proxy-groups(genB)"""
|
|
190
|
+
return [
|
|
191
|
+
{
|
|
192
|
+
"name": "全局选择",
|
|
193
|
+
"type": "select",
|
|
194
|
+
"proxies": ["自动选择", "手动选择", "轮询"]
|
|
195
|
+
+ [item["name"] for item in sub_list],
|
|
196
|
+
},
|
|
197
|
+
{"name": "自动选择", "type": "url-test", "include-all": True},
|
|
198
|
+
{"name": "手动选择", "type": "select", "include-all": True},
|
|
199
|
+
{
|
|
200
|
+
"name": "轮询",
|
|
201
|
+
"type": "load-balance",
|
|
202
|
+
"url": "https://api.bitget.com/api/v2/public/time",
|
|
203
|
+
"strategy": "round-robin",
|
|
204
|
+
"include-all": True,
|
|
205
|
+
},
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
def _add_provider_sub(self, template, item, ps):
|
|
209
|
+
"""单个订阅写入 proxy-providers + url-test group(genPW / genPB)"""
|
|
210
|
+
template["proxy-providers"][f"provider.{item['name']}"] = {
|
|
211
|
+
"type": "inline",
|
|
212
|
+
"payload": ps,
|
|
213
|
+
}
|
|
214
|
+
template["proxy-groups"].append(
|
|
215
|
+
{
|
|
216
|
+
"name": item["name"],
|
|
217
|
+
"type": "url-test",
|
|
218
|
+
"url": "https://www.gstatic.com/generate_204",
|
|
219
|
+
"interval": 300,
|
|
220
|
+
"tolerance": 11,
|
|
221
|
+
"lazy": True,
|
|
222
|
+
"use": [f"provider.{item['name']}"],
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def _add_inline_sub(self, template, item, ps):
|
|
227
|
+
"""单个订阅直接写入 proxies + url-test group(genB)"""
|
|
228
|
+
template["proxies"].extend(ps)
|
|
229
|
+
template["proxy-groups"].append(
|
|
230
|
+
{
|
|
231
|
+
"name": item["name"],
|
|
232
|
+
"type": "url-test",
|
|
233
|
+
"proxies": [p["name"] for p in ps],
|
|
234
|
+
}
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
def _add_base_rules(self, template, with_dst_port=False):
|
|
238
|
+
"""IP-CIDR / DOMAIN 基础规则(genPB / genB),genB 含饥荒端口"""
|
|
239
|
+
template["rules"].extend(
|
|
240
|
+
[
|
|
241
|
+
"IP-CIDR,192.168.0.0/16,DIRECT,no-resolve",
|
|
242
|
+
"IP-CIDR,10.0.0.0/8,DIRECT,no-resolve",
|
|
243
|
+
"IP-CIDR,172.16.0.0/12,DIRECT,no-resolve",
|
|
244
|
+
"IP-CIDR,127.0.0.0/8,DIRECT,no-resolve",
|
|
245
|
+
]
|
|
246
|
+
)
|
|
247
|
+
if with_dst_port:
|
|
248
|
+
template["rules"].extend(
|
|
249
|
+
[
|
|
250
|
+
"DST-PORT,10999,DIRECT",
|
|
251
|
+
"DST-PORT,10998,DIRECT",
|
|
252
|
+
"DST-PORT,27016,DIRECT",
|
|
253
|
+
"DST-PORT,27017,DIRECT",
|
|
254
|
+
"DST-PORT,8766,DIRECT",
|
|
255
|
+
"DST-PORT,8767,DIRECT",
|
|
256
|
+
]
|
|
257
|
+
)
|
|
258
|
+
template["rules"].extend(
|
|
259
|
+
[
|
|
260
|
+
"DOMAIN,clash.razord.top,DIRECT",
|
|
261
|
+
"DOMAIN,yacd.haishan.me,DIRECT",
|
|
262
|
+
# "DOMAIN,accounts.klei.com,全局选择",
|
|
263
|
+
]
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# ---------- 三种生成模式 ----------
|
|
267
|
+
|
|
268
|
+
# 白名单模式 Rules 配置方式
|
|
269
|
+
def genPW(self, sub_list: list[dict]):
|
|
270
|
+
proxies = self._get_proxies()
|
|
271
|
+
template = self._load_template(proxies)
|
|
272
|
+
|
|
273
|
+
template["proxy-groups"].extend(self._provider_proxy_groups(sub_list))
|
|
274
|
+
|
|
275
|
+
subs, userinfo = self._fetch_subscriptions(sub_list, proxies)
|
|
276
|
+
for item, ps in subs:
|
|
277
|
+
self._add_provider_sub(template, item, ps)
|
|
278
|
+
|
|
279
|
+
self._add_inline_rule_providers(template, proxies)
|
|
280
|
+
template["rule-providers"].update(self._whitelist_http_rule_providers())
|
|
281
|
+
template["rules"].extend(WHITELIST_RULES)
|
|
282
|
+
|
|
283
|
+
return template, userinfo
|
|
284
|
+
|
|
285
|
+
# 黑名单模式 Rules 配置方式
|
|
286
|
+
def genPB(self, sub_list: list[dict]):
|
|
287
|
+
proxies = self._get_proxies()
|
|
288
|
+
template = self._load_template(proxies)
|
|
289
|
+
|
|
290
|
+
template["proxy-groups"].extend(self._provider_proxy_groups(sub_list))
|
|
291
|
+
|
|
292
|
+
subs, userinfo = self._fetch_subscriptions(sub_list, proxies)
|
|
293
|
+
for item, ps in subs:
|
|
294
|
+
self._add_provider_sub(template, item, ps)
|
|
295
|
+
|
|
296
|
+
self._add_base_rules(template, with_dst_port=False)
|
|
297
|
+
self._add_inline_rule_providers(template, proxies)
|
|
298
|
+
template["rule-providers"].update(self._blacklist_http_rule_providers())
|
|
299
|
+
template["rules"].extend(BLACKLIST_RULES)
|
|
300
|
+
|
|
301
|
+
return template, userinfo
|
|
302
|
+
|
|
303
|
+
def genB(self, sub_list: list[dict]):
|
|
304
|
+
proxies = self._get_proxies()
|
|
305
|
+
template = self._load_template(proxies)
|
|
306
|
+
|
|
307
|
+
template["proxy-groups"].extend(self._inline_proxy_groups(sub_list))
|
|
308
|
+
|
|
309
|
+
template["proxies"] = []
|
|
310
|
+
subs, userinfo = self._fetch_subscriptions(sub_list, proxies)
|
|
311
|
+
for item, ps in subs:
|
|
312
|
+
self._add_inline_sub(template, item, ps)
|
|
313
|
+
|
|
314
|
+
self._add_base_rules(template, with_dst_port=True)
|
|
315
|
+
self._add_inline_rule_providers(template, proxies)
|
|
316
|
+
template["rule-providers"].update(self._blacklist_http_rule_providers())
|
|
317
|
+
template["rules"].extend(BLACKLIST_RULES)
|
|
318
|
+
|
|
319
|
+
return template, userinfo
|
|
320
|
+
|
|
321
|
+
def query2sub(self, urls: str, agents: str, names: str):
|
|
322
|
+
url_list = urls.split(",") if urls and urls.strip() else []
|
|
323
|
+
agents_list = agents.split(",") if agents else []
|
|
324
|
+
name_list = names.split(",") if names else []
|
|
325
|
+
|
|
326
|
+
max_length = max(len(url_list), len(agents_list), len(name_list))
|
|
327
|
+
|
|
328
|
+
while len(url_list) < max_length:
|
|
329
|
+
url_list.append("")
|
|
330
|
+
while len(agents_list) < max_length:
|
|
331
|
+
agents_list.append("")
|
|
332
|
+
while len(name_list) < max_length:
|
|
333
|
+
name_list.append("")
|
|
334
|
+
|
|
335
|
+
sub_list = []
|
|
336
|
+
|
|
337
|
+
for i in range(max_length):
|
|
338
|
+
if not url_list[i]:
|
|
339
|
+
raise ValueError(f"Invalid subscription URL. #{i + 1}")
|
|
340
|
+
|
|
341
|
+
sub_list.append(
|
|
342
|
+
{
|
|
343
|
+
"url": url_list[i],
|
|
344
|
+
"user_agent": agents_list[i] if agents_list[i] else "",
|
|
345
|
+
"name": name_list[i] if name_list[i] else f"订阅{i}",
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
return sub_list
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
generator_instance = None
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def init_generator(config: ClashConfig):
|
|
356
|
+
global generator_instance
|
|
357
|
+
generator_instance = ClashYamlGenerator(config)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def get_generator():
|
|
361
|
+
global generator_instance
|
|
362
|
+
if generator_instance is None:
|
|
363
|
+
raise RuntimeError("ClashYamlGenerator not initialized")
|
|
364
|
+
return generator_instance
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def get_generator_dependency():
|
|
368
|
+
return Depends(get_generator)
|
|
@@ -19,7 +19,7 @@ class Settings(BaseSettings):
|
|
|
19
19
|
REDIS_PASSWORD: str = ""
|
|
20
20
|
|
|
21
21
|
PROJECT_DESCRIPTION: str = (
|
|
22
|
-
f"{metadata['Summary']}, FastAPI提供: clash订阅转换、
|
|
22
|
+
f"{metadata['Summary']}, FastAPI提供: clash订阅转换、redis API"
|
|
23
23
|
)
|
|
24
24
|
PROJECT_VERSION: str = metadata["Version"]
|
|
25
25
|
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
external-controller: ""
|
|
2
|
+
ipv6: true
|
|
3
|
+
mode: rule
|
|
4
|
+
mixed-port: 7897
|
|
5
|
+
socks-port: 0
|
|
6
|
+
port: 0
|
|
7
|
+
redir-port: 0
|
|
8
|
+
tproxy-port: 0
|
|
9
|
+
allow-lan: true
|
|
10
|
+
unified-delay: true
|
|
11
|
+
tcp-concurrent: false
|
|
12
|
+
log-level: warning
|
|
13
|
+
find-process-mode: strict
|
|
14
|
+
bind-address: "*"
|
|
15
|
+
lan-allowed-ips:
|
|
16
|
+
- 0.0.0.0/0
|
|
17
|
+
- ::/0
|
|
18
|
+
lan-disallowed-ips: []
|
|
19
|
+
authentication: []
|
|
20
|
+
skip-auth-prefixes:
|
|
21
|
+
- 127.0.0.1/32
|
|
22
|
+
- ::1/128
|
|
23
|
+
tun:
|
|
24
|
+
enable: false
|
|
25
|
+
stack: mixed
|
|
26
|
+
auto-route: true
|
|
27
|
+
auto-redirect: false
|
|
28
|
+
auto-detect-interface: true
|
|
29
|
+
dns-hijack:
|
|
30
|
+
- any:53
|
|
31
|
+
route-exclude-address: []
|
|
32
|
+
mtu: 1500
|
|
33
|
+
device: Mihomo
|
|
34
|
+
dns:
|
|
35
|
+
enable: true
|
|
36
|
+
ipv6: true
|
|
37
|
+
enhanced-mode: fake-ip
|
|
38
|
+
fake-ip-range: 198.18.0.1/16
|
|
39
|
+
fake-ip-filter:
|
|
40
|
+
- "*"
|
|
41
|
+
- +.lan
|
|
42
|
+
- +.local
|
|
43
|
+
- time.*.com
|
|
44
|
+
- ntp.*.com
|
|
45
|
+
- +.market.xiaomi.com
|
|
46
|
+
- api.memeniu.xyz
|
|
47
|
+
- meme.us.kg
|
|
48
|
+
- +.steamserver.net
|
|
49
|
+
- +.recaptcha.net
|
|
50
|
+
use-hosts: false
|
|
51
|
+
use-system-hosts: false
|
|
52
|
+
respect-rules: true
|
|
53
|
+
default-nameserver:
|
|
54
|
+
- https://223.5.5.5/dns-query
|
|
55
|
+
- https://223.6.6.6/dns-query
|
|
56
|
+
- https://8.8.8.8/dns-query
|
|
57
|
+
- https://1.1.1.1/dns-query
|
|
58
|
+
- 223.5.5.5
|
|
59
|
+
nameserver:
|
|
60
|
+
- https://dns.alidns.com/dns-query
|
|
61
|
+
- https://doh.pub/dns-query
|
|
62
|
+
proxy-server-nameserver:
|
|
63
|
+
- https://cloudflare-dns.com/dns-query
|
|
64
|
+
- https://dns.google/dns-query
|
|
65
|
+
- https://dns.alidns.com/dns-query
|
|
66
|
+
- https://doh.pub/dns-query
|
|
67
|
+
direct-nameserver:
|
|
68
|
+
- https://dns.alidns.com/dns-query
|
|
69
|
+
- https://doh.pub/dns-query
|
|
70
|
+
fallback:
|
|
71
|
+
- https://cloudflare-dns.com/dns-query
|
|
72
|
+
- https://dns.google/dns-query
|
|
73
|
+
fallback-filter:
|
|
74
|
+
geoip: true
|
|
75
|
+
geoip-code: CN
|
|
76
|
+
ipcidr:
|
|
77
|
+
- 240.0.0.0/4
|
|
78
|
+
- 0.0.0.0/32
|
|
79
|
+
- 127.0.0.0/8
|
|
80
|
+
- 10.0.0.0/8
|
|
81
|
+
- 192.168.0.0/16
|
|
82
|
+
domain:
|
|
83
|
+
- +.google.com
|
|
84
|
+
- +.facebook.com
|
|
85
|
+
- +.youtube.com
|
|
86
|
+
direct-nameserver-follow-policy: true
|
|
87
|
+
prefer-h3: false
|
|
88
|
+
nameserver-policy:
|
|
89
|
+
geosite:cn:
|
|
90
|
+
- https://dns.alidns.com/dns-query
|
|
91
|
+
- https://doh.pub/dns-query
|
|
92
|
+
geosite:geolocation-!cn:
|
|
93
|
+
- https://cloudflare-dns.com/dns-query
|
|
94
|
+
- https://dns.google/dns-query
|
|
95
|
+
+.jsdelivr.net:
|
|
96
|
+
- https://cloudflare-dns.com/dns-query
|
|
97
|
+
- https://dns.google/dns-query
|
|
98
|
+
+.github.com:
|
|
99
|
+
- https://cloudflare-dns.com/dns-query
|
|
100
|
+
- https://dns.google/dns-query
|
|
101
|
+
+.githubusercontent.com:
|
|
102
|
+
- https://cloudflare-dns.com/dns-query
|
|
103
|
+
- https://dns.google/dns-query
|
|
104
|
+
+.githubassets.com:
|
|
105
|
+
- https://cloudflare-dns.com/dns-query
|
|
106
|
+
- https://dns.google/dns-query
|
|
107
|
+
+.fastly.net:
|
|
108
|
+
- https://cloudflare-dns.com/dns-query
|
|
109
|
+
- https://dns.google/dns-query
|
|
110
|
+
api.memeniu.xyz:
|
|
111
|
+
- https://[2402:4e00::]/dns-query
|
|
112
|
+
- https://[2400:3200::1]/dns-query
|
|
113
|
+
- https://dns.alidns.com/dns-query
|
|
114
|
+
- https://doh.pub/dns-query
|
|
115
|
+
meme.us.kg:
|
|
116
|
+
- https://[2402:4e00::]/dns-query
|
|
117
|
+
- https://[2400:3200::1]/dns-query
|
|
118
|
+
- https://dns.alidns.com/dns-query
|
|
119
|
+
- https://doh.pub/dns-query
|
|
120
|
+
+.steamserver.net:
|
|
121
|
+
- https://[2402:4e00::]/dns-query
|
|
122
|
+
- https://[2400:3200::1]/dns-query
|
|
123
|
+
- https://dns.alidns.com/dns-query
|
|
124
|
+
- https://doh.pub/dns-query
|
|
125
|
+
+.recaptcha.net:
|
|
126
|
+
- https://[2402:4e00::]/dns-query
|
|
127
|
+
- https://[2400:3200::1]/dns-query
|
|
128
|
+
- https://dns.alidns.com/dns-query
|
|
129
|
+
- https://doh.pub/dns-query
|
|
130
|
+
sniffer:
|
|
131
|
+
enable: true
|
|
132
|
+
parse-pure-ip: true
|
|
133
|
+
force-dns-mapping: true
|
|
134
|
+
override-destination: true
|
|
135
|
+
sniff:
|
|
136
|
+
HTTP:
|
|
137
|
+
ports:
|
|
138
|
+
- "80"
|
|
139
|
+
- 8080-8880
|
|
140
|
+
override-destination: true
|
|
141
|
+
TLS:
|
|
142
|
+
ports:
|
|
143
|
+
- "443"
|
|
144
|
+
- "8443"
|
|
145
|
+
QUIC:
|
|
146
|
+
ports:
|
|
147
|
+
- "443"
|
|
148
|
+
- "8443"
|
|
149
|
+
- "4433"
|
|
150
|
+
skip-domain:
|
|
151
|
+
- +.push.apple.com
|
|
152
|
+
skip-dst-address:
|
|
153
|
+
- 91.105.192.0/23
|
|
154
|
+
- 91.108.4.0/22
|
|
155
|
+
- 91.108.8.0/21
|
|
156
|
+
- 91.108.16.0/21
|
|
157
|
+
- 91.108.56.0/22
|
|
158
|
+
- 95.161.64.0/20
|
|
159
|
+
- 149.154.160.0/20
|
|
160
|
+
- 185.76.151.0/24
|
|
161
|
+
- 2001:67c:4e8::/48
|
|
162
|
+
- 2001:b28:f23c::/47
|
|
163
|
+
- 2001:b28:f23f::/48
|
|
164
|
+
- 2a0a:f280:203::/48
|
|
165
|
+
profile:
|
|
166
|
+
store-selected: true
|
|
167
|
+
store-fake-ip: true
|
|
168
|
+
geo-auto-update: false
|
|
169
|
+
geo-update-interval: 24
|
|
170
|
+
geodata-mode: true
|
|
171
|
+
geox-url:
|
|
172
|
+
geoip: https://fastly.jsdelivr.net/gh/Loyalsoldier/geoip@release/geoip.dat
|
|
173
|
+
mmdb: https://fastly.jsdelivr.net/gh/Loyalsoldier/geoip@release/Country.mmdb
|
|
174
|
+
asn: https://fastly.jsdelivr.net/gh/Loyalsoldier/geoip@release/GeoLite2-ASN.mmdb
|
|
175
|
+
geosite: https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat
|
|
176
|
+
proxies: []
|
|
177
|
+
proxy-providers: {}
|
|
178
|
+
proxy-groups: []
|
|
179
|
+
rule-providers: {}
|
|
180
|
+
rules: []
|