pymecli 0.5.2__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.
Files changed (44) hide show
  1. {pymecli-0.5.2 → pymecli-0.5.3}/PKG-INFO +1 -1
  2. pymecli-0.5.3/core/clash.py +368 -0
  3. {pymecli-0.5.2 → pymecli-0.5.3}/data/template.yaml +25 -1
  4. {pymecli-0.5.2 → pymecli-0.5.3}/pyproject.toml +1 -1
  5. pymecli-0.5.2/core/clash.py +0 -664
  6. {pymecli-0.5.2 → pymecli-0.5.3}/.gitignore +0 -0
  7. {pymecli-0.5.2 → pymecli-0.5.3}/README.md +0 -0
  8. {pymecli-0.5.2 → pymecli-0.5.3}/api/__init__.py +0 -0
  9. {pymecli-0.5.2 → pymecli-0.5.3}/api/v1/__init__.py +0 -0
  10. {pymecli-0.5.2 → pymecli-0.5.3}/api/v1/clash.py +0 -0
  11. {pymecli-0.5.2 → pymecli-0.5.3}/api/v1/redis.py +0 -0
  12. {pymecli-0.5.2 → pymecli-0.5.3}/cli/__init__.py +0 -0
  13. {pymecli-0.5.2 → pymecli-0.5.3}/cli/bitget.py +0 -0
  14. {pymecli-0.5.2 → pymecli-0.5.3}/cli/dst.py +0 -0
  15. {pymecli-0.5.2 → pymecli-0.5.3}/cli/example.py +0 -0
  16. {pymecli-0.5.2 → pymecli-0.5.3}/cli/fast.py +0 -0
  17. {pymecli-0.5.2 → pymecli-0.5.3}/cli/gate.py +0 -0
  18. {pymecli-0.5.2 → pymecli-0.5.3}/cli/redis_csv.py +0 -0
  19. {pymecli-0.5.2 → pymecli-0.5.3}/cli/util.py +0 -0
  20. {pymecli-0.5.2 → pymecli-0.5.3}/core/__init__.py +0 -0
  21. {pymecli-0.5.2 → pymecli-0.5.3}/core/config.py +0 -0
  22. {pymecli-0.5.2 → pymecli-0.5.3}/core/redis_client.py +0 -0
  23. {pymecli-0.5.2 → pymecli-0.5.3}/crypto/__init__.py +0 -0
  24. {pymecli-0.5.2 → pymecli-0.5.3}/crypto/bitget.py +0 -0
  25. {pymecli-0.5.2 → pymecli-0.5.3}/crypto/gate.py +0 -0
  26. {pymecli-0.5.2 → pymecli-0.5.3}/data/__init__.py +0 -0
  27. {pymecli-0.5.2 → pymecli-0.5.3}/data/dou_dict.py +0 -0
  28. {pymecli-0.5.2 → pymecli-0.5.3}/data/dou_list.py +0 -0
  29. {pymecli-0.5.2 → pymecli-0.5.3}/data/main.py +0 -0
  30. {pymecli-0.5.2 → pymecli-0.5.3}/models/__init__.py +0 -0
  31. {pymecli-0.5.2 → pymecli-0.5.3}/models/douzero_model.py +0 -0
  32. {pymecli-0.5.2 → pymecli-0.5.3}/models/ocr_model.py +0 -0
  33. {pymecli-0.5.2 → pymecli-0.5.3}/models/response.py +0 -0
  34. {pymecli-0.5.2 → pymecli-0.5.3}/utils/__init__.py +0 -0
  35. {pymecli-0.5.2 → pymecli-0.5.3}/utils/elapsed.py +0 -0
  36. {pymecli-0.5.2 → pymecli-0.5.3}/utils/helper.py +0 -0
  37. {pymecli-0.5.2 → pymecli-0.5.3}/utils/logger.py +0 -0
  38. {pymecli-0.5.2 → pymecli-0.5.3}/utils/mysql.py +0 -0
  39. {pymecli-0.5.2 → pymecli-0.5.3}/utils/path.py +0 -0
  40. {pymecli-0.5.2 → pymecli-0.5.3}/utils/pd.py +0 -0
  41. {pymecli-0.5.2 → pymecli-0.5.3}/utils/pyredis.py +0 -0
  42. {pymecli-0.5.2 → pymecli-0.5.3}/utils/sleep.py +0 -0
  43. {pymecli-0.5.2 → pymecli-0.5.3}/utils/text.py +0 -0
  44. {pymecli-0.5.2 → pymecli-0.5.3}/utils/toml.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymecli
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: My CLI
5
5
  Project-URL: Homepage, https://pypi.org/project/pymecli
6
6
  Project-URL: Repository, https://github.com/meme2046/pymecli
@@ -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)
@@ -9,7 +9,7 @@ tproxy-port: 0
9
9
  allow-lan: true
10
10
  unified-delay: true
11
11
  tcp-concurrent: false
12
- log-level: info
12
+ log-level: warning
13
13
  find-process-mode: strict
14
14
  bind-address: "*"
15
15
  lan-allowed-ips:
@@ -43,6 +43,10 @@ dns:
43
43
  - time.*.com
44
44
  - ntp.*.com
45
45
  - +.market.xiaomi.com
46
+ - api.memeniu.xyz
47
+ - meme.us.kg
48
+ - +.steamserver.net
49
+ - +.recaptcha.net
46
50
  use-hosts: false
47
51
  use-system-hosts: false
48
52
  respect-rules: true
@@ -103,6 +107,26 @@ dns:
103
107
  +.fastly.net:
104
108
  - https://cloudflare-dns.com/dns-query
105
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
106
130
  sniffer:
107
131
  enable: true
108
132
  parse-pure-ip: true
@@ -35,7 +35,7 @@
35
35
  name = "pymecli"
36
36
  readme = "README.md"
37
37
  requires-python = ">=3.10"
38
- version = "0.5.2"
38
+ version = "0.5.3"
39
39
 
40
40
  [project.urls]
41
41
  Homepage = "https://pypi.org/project/pymecli"
@@ -1,664 +0,0 @@
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
-
11
- class ClashConfig:
12
- def __init__(self, rule_base_url: str, my_rule_base_url: str, request_proxy: str):
13
- self.rule_base_url = rule_base_url.rstrip("/")
14
- self.my_rule_base_url = my_rule_base_url.rstrip("/")
15
- self.request_proxy = request_proxy
16
-
17
-
18
- class ClashYamlGenerator:
19
- def __init__(self, config: ClashConfig):
20
- self.rule_base_url = config.rule_base_url
21
- self.my_rule_base_url = config.my_rule_base_url
22
- self.request_proxy = config.request_proxy
23
-
24
- # 白名单模式 Rules 配置方式
25
- def genPW(self, sub_list: list[dict]):
26
- proxies = None
27
- if self.request_proxy:
28
- proxies = {
29
- "http": self.request_proxy,
30
- "https": self.request_proxy,
31
- }
32
-
33
- with open(str(module_dir / "data/template.yaml"), "r", encoding="utf-8") as f:
34
- template = yaml.safe_load(f)
35
-
36
- template["proxy-groups"].extend(
37
- [
38
- {
39
- "name": "全局选择",
40
- "type": "select",
41
- "proxies": ["自动选择", "手动选择", "轮询"]
42
- + [item["name"] for item in sub_list],
43
- },
44
- {
45
- "name": "自动选择",
46
- "type": "url-test",
47
- "url": "https://www.gstatic.com/generate_204",
48
- "interval": 300,
49
- "tolerance": 11,
50
- "lazy": True,
51
- "use": [f"provider.{item['name']}" for item in sub_list],
52
- },
53
- {
54
- "name": "手动选择",
55
- "type": "select",
56
- "use": [f"provider.{item['name']}" for item in sub_list],
57
- },
58
- {
59
- "name": "轮询",
60
- "type": "load-balance",
61
- "url": "https://api.bitget.com/api/v2/public/time",
62
- "interval": 300,
63
- "lazy": True,
64
- "strategy": "round-robin",
65
- "use": [f"provider.{item['name']}" for item in sub_list],
66
- },
67
- ]
68
- )
69
- userinfo = ""
70
- for item in sub_list:
71
- headers = {"User-Agent": item["user_agent"]} if item["user_agent"] else {}
72
-
73
- if not item["url"]:
74
- raise ValueError("Invalid subscription URL.")
75
- response = requests.get(item["url"], headers=headers, proxies=proxies)
76
- response.raise_for_status()
77
- if not userinfo:
78
- userinfo = response.headers["Subscription-Userinfo"]
79
- remote_config = yaml.safe_load(response.text)
80
-
81
- # 检查 remote_config 是否为 None
82
- if remote_config is None:
83
- raise ValueError("Invalid subscription content: empty or invalid YAML.")
84
-
85
- ps = remote_config.get("proxies", [])
86
- if not ps:
87
- raise ValueError("No proxies found in subscription.")
88
-
89
- template["proxy-providers"][f"provider.{item['name']}"] = {
90
- "type": "inline",
91
- "payload": ps,
92
- }
93
-
94
- template["proxy-groups"].append(
95
- {
96
- "name": item["name"],
97
- "type": "url-test",
98
- "url": "https://www.gstatic.com/generate_204",
99
- "interval": 300,
100
- "tolerance": 11,
101
- "lazy": True,
102
- "use": [f"provider.{item['name']}"],
103
- },
104
- )
105
-
106
- # 获取rules
107
- rule_list = [
108
- [f"{self.my_rule_base_url}/direct.yaml", "DIRECT"],
109
- [f"{self.my_rule_base_url}/proxy.yaml", "全局选择"],
110
- [
111
- f"{self.my_rule_base_url}/round.yaml",
112
- "轮询",
113
- ],
114
- [
115
- f"{self.my_rule_base_url}/reject.yaml",
116
- "REJECT",
117
- ],
118
- ]
119
-
120
- for item in rule_list:
121
- response = requests.get(item[0], proxies=proxies)
122
- response.raise_for_status()
123
- remote = yaml.safe_load(response.text)
124
- template["rule-providers"][os.path.basename(item[0])] = {
125
- "type": "inline",
126
- "behavior": "classical",
127
- "payload": remote["payload"],
128
- }
129
-
130
- template["rules"].append(f"RULE-SET,{os.path.basename(item[0])},{item[1]}")
131
-
132
- template["rule-providers"].update(
133
- {
134
- "applications": {
135
- "type": "http",
136
- "format": "yaml",
137
- "behavior": "classical",
138
- "url": f"{self.rule_base_url}/applications.txt",
139
- "path": "./ruleset/applications.yaml",
140
- "interval": 86400,
141
- },
142
- "private": {
143
- "type": "http",
144
- "format": "yaml",
145
- "behavior": "domain",
146
- "url": f"{self.rule_base_url}/private.txt",
147
- "path": "./ruleset/private.yaml",
148
- "interval": 86400,
149
- },
150
- "icloud": {
151
- "type": "http",
152
- "format": "yaml",
153
- "behavior": "domain",
154
- "url": f"{self.rule_base_url}/icloud.txt",
155
- "path": "./ruleset/icloud.yaml",
156
- "interval": 86400,
157
- },
158
- "apple": {
159
- "type": "http",
160
- "format": "yaml",
161
- "behavior": "domain",
162
- "url": f"{self.rule_base_url}/apple.txt",
163
- "path": "./ruleset/apple.yaml",
164
- "interval": 86400,
165
- },
166
- "google": {
167
- "type": "http",
168
- "format": "yaml",
169
- "behavior": "domain",
170
- "url": f"{self.rule_base_url}/google.txt",
171
- "path": "./ruleset/google.yaml",
172
- "interval": 86400,
173
- },
174
- "proxy": {
175
- "type": "http",
176
- "format": "yaml",
177
- "behavior": "domain",
178
- "url": f"{self.rule_base_url}/proxy.txt",
179
- "path": "./ruleset/proxy.yaml",
180
- "interval": 86400,
181
- },
182
- "direct": {
183
- "type": "http",
184
- "format": "yaml",
185
- "behavior": "domain",
186
- "url": f"{self.rule_base_url}/direct.txt",
187
- "path": "./ruleset/direct.yaml",
188
- "interval": 86400,
189
- },
190
- "lancidr": {
191
- "type": "http",
192
- "format": "yaml",
193
- "behavior": "ipcidr",
194
- "url": f"{self.rule_base_url}/lancidr.txt",
195
- "path": "./ruleset/lancidr.yaml",
196
- "interval": 86400,
197
- },
198
- "cncidr": {
199
- "type": "http",
200
- "format": "yaml",
201
- "behavior": "ipcidr",
202
- "url": f"{self.rule_base_url}/cncidr.txt",
203
- "path": "./ruleset/cncidr.yaml",
204
- "interval": 86400,
205
- },
206
- "telegramcidr": {
207
- "type": "http",
208
- "format": "yaml",
209
- "behavior": "ipcidr",
210
- "url": f"{self.rule_base_url}/telegramcidr.txt",
211
- "path": "./ruleset/telegramcidr.yaml",
212
- "interval": 86400,
213
- },
214
- # "reject": {
215
- # "type": "http",
216
- # "format": "yaml",
217
- # "behavior": "domain",
218
- # "url": f"{self.rule_base_url}/reject.txt",
219
- # "path": "./ruleset/reject.yaml",
220
- # "interval": 86400,
221
- # },
222
- },
223
- )
224
-
225
- template["rules"].extend(
226
- [
227
- "RULE-SET,applications,DIRECT",
228
- "RULE-SET,private,DIRECT",
229
- # "RULE-SET,reject,REJECT",
230
- "RULE-SET,icloud,DIRECT",
231
- "RULE-SET,apple,DIRECT",
232
- "RULE-SET,google,全局选择",
233
- "RULE-SET,proxy,全局选择",
234
- "RULE-SET,direct,DIRECT",
235
- "RULE-SET,lancidr,DIRECT",
236
- "RULE-SET,cncidr,DIRECT",
237
- "RULE-SET,telegramcidr,全局选择",
238
- "GEOIP,LAN,DIRECT",
239
- "GEOIP,CN,DIRECT",
240
- "MATCH,全局选择",
241
- ]
242
- )
243
-
244
- return template, userinfo
245
-
246
- # 黑名单模式 Rules 配置方式
247
- def genPB(self, sub_list: list[dict]):
248
- proxies = None
249
- if self.request_proxy:
250
- proxies = {
251
- "http": self.request_proxy,
252
- "https": self.request_proxy,
253
- }
254
-
255
- with open(str(module_dir / "data/template.yaml"), "r", encoding="utf-8") as f:
256
- template = yaml.safe_load(f)
257
-
258
- template["proxy-groups"].extend(
259
- [
260
- {
261
- "name": "全局选择",
262
- "type": "select",
263
- "proxies": ["自动选择", "手动选择", "轮询"]
264
- + [item["name"] for item in sub_list],
265
- },
266
- {
267
- "name": "自动选择",
268
- "type": "url-test",
269
- "url": "https://www.gstatic.com/generate_204",
270
- "interval": 300,
271
- "tolerance": 11,
272
- "lazy": True,
273
- "use": [f"provider.{item['name']}" for item in sub_list],
274
- },
275
- {
276
- "name": "手动选择",
277
- "type": "select",
278
- "use": [f"provider.{item['name']}" for item in sub_list],
279
- },
280
- {
281
- "name": "轮询",
282
- "type": "load-balance",
283
- "url": "https://api.bitget.com/api/v2/public/time",
284
- "interval": 300,
285
- "lazy": True,
286
- "strategy": "round-robin",
287
- "use": [f"provider.{item['name']}" for item in sub_list],
288
- },
289
- ]
290
- )
291
-
292
- userinfo = ""
293
- for item in sub_list:
294
- headers = {"User-Agent": item["user_agent"]} if item["user_agent"] else {}
295
-
296
- if not item["url"]:
297
- raise ValueError("Invalid subscription URL.")
298
- response = requests.get(item["url"], headers=headers, proxies=proxies)
299
- response.raise_for_status()
300
- if not userinfo:
301
- userinfo = response.headers["Subscription-Userinfo"]
302
- remote_config = yaml.safe_load(response.text)
303
-
304
- # 检查 remote_config 是否为 None
305
- if remote_config is None:
306
- raise ValueError("Invalid subscription content: empty or invalid YAML.")
307
-
308
- ps = remote_config.get("proxies", [])
309
- if not ps:
310
- raise ValueError("No proxies found in subscription.")
311
-
312
- template["proxy-providers"][f"provider.{item['name']}"] = {
313
- "type": "inline",
314
- "payload": ps,
315
- }
316
-
317
- template["proxy-groups"].append(
318
- {
319
- "name": item["name"],
320
- "type": "url-test",
321
- "url": "https://www.gstatic.com/generate_204",
322
- "interval": 300,
323
- "tolerance": 11,
324
- "lazy": True,
325
- "use": [f"provider.{item['name']}"],
326
- },
327
- )
328
-
329
- template["rules"].append("IP-CIDR,192.168.0.0/16,DIRECT,no-resolve")
330
- template["rules"].append("IP-CIDR,10.0.0.0/8,DIRECT,no-resolve")
331
- template["rules"].append("IP-CIDR,172.16.0.0/12,DIRECT,no-resolve")
332
- template["rules"].append("IP-CIDR,127.0.0.0/8,DIRECT,no-resolve")
333
- # ⬇️饥荒服务器相关端口
334
- # template["rules"].append("DST-PORT,10999,DIRECT")
335
- # template["rules"].append("DST-PORT,10998,DIRECT")
336
- # template["rules"].append("DST-PORT,27016,DIRECT")
337
- # template["rules"].append("DST-PORT,27017,DIRECT")
338
- # template["rules"].append("DST-PORT,8766,DIRECT")
339
- # template["rules"].append("DST-PORT,8767,DIRECT")
340
-
341
- template["rules"].append("DOMAIN,clash.razord.top,DIRECT")
342
- template["rules"].append("DOMAIN,yacd.haishan.me,DIRECT")
343
- # template["rules"].append("DOMAIN,accounts.klei.com,全局选择")
344
-
345
- # 获取rules
346
- rule_list = [
347
- [f"{self.my_rule_base_url}/direct.yaml", "DIRECT"],
348
- [f"{self.my_rule_base_url}/proxy.yaml", "全局选择"],
349
- [
350
- f"{self.my_rule_base_url}/round.yaml",
351
- "轮询",
352
- ],
353
- [
354
- f"{self.my_rule_base_url}/reject.yaml",
355
- "REJECT",
356
- ],
357
- ]
358
-
359
- for item in rule_list:
360
- response = requests.get(item[0], proxies=proxies)
361
- response.raise_for_status()
362
- remote = yaml.safe_load(response.text)
363
- template["rule-providers"][os.path.basename(item[0])] = {
364
- "type": "inline",
365
- "behavior": "classical",
366
- "payload": remote["payload"],
367
- }
368
-
369
- template["rules"].append(f"RULE-SET,{os.path.basename(item[0])},{item[1]}")
370
-
371
- template["rule-providers"].update(
372
- {
373
- "applications": {
374
- "type": "http",
375
- "format": "yaml",
376
- "behavior": "classical",
377
- "url": f"{self.rule_base_url}/applications.txt",
378
- "path": "./ruleset/applications.yaml",
379
- "interval": 86400,
380
- },
381
- "private": {
382
- "type": "http",
383
- "format": "yaml",
384
- "behavior": "domain",
385
- "url": f"{self.rule_base_url}/private.txt",
386
- "path": "./ruleset/private.yaml",
387
- "interval": 86400,
388
- },
389
- "tld-not-cn": {
390
- "type": "http",
391
- "format": "yaml",
392
- "behavior": "domain",
393
- "url": f"{self.rule_base_url}/tld-not-cn.txt",
394
- "path": "./ruleset/tld-not-cn.yaml",
395
- "interval": 86400,
396
- },
397
- "telegramcidr": {
398
- "type": "http",
399
- "format": "yaml",
400
- "behavior": "ipcidr",
401
- "url": f"{self.rule_base_url}/telegramcidr.txt",
402
- "path": "./ruleset/telegramcidr.yaml",
403
- "interval": 86400,
404
- },
405
- "gfw": {
406
- "type": "http",
407
- "format": "yaml",
408
- "behavior": "domain",
409
- "url": f"{self.rule_base_url}/gfw.txt",
410
- "path": "./ruleset/gfw.yaml",
411
- "interval": 86400,
412
- },
413
- # "reject": {
414
- # "type": "http",
415
- # "format": "yaml",
416
- # "behavior": "domain",
417
- # "url": f"{self.rule_base_url}/reject.txt",
418
- # "path": "./ruleset/reject.yaml",
419
- # "interval": 86400,
420
- # },
421
- },
422
- )
423
-
424
- template["rules"].extend(
425
- [
426
- "RULE-SET,applications,DIRECT",
427
- "RULE-SET,private,DIRECT",
428
- # "RULE-SET,reject,REJECT",
429
- "RULE-SET,tld-not-cn,全局选择",
430
- "RULE-SET,gfw,全局选择",
431
- "RULE-SET,telegramcidr,全局选择",
432
- "MATCH,DIRECT",
433
- ]
434
- )
435
-
436
- return template, userinfo
437
-
438
- def genB(self, sub_list: list[dict]):
439
- proxies = None
440
- if self.request_proxy:
441
- proxies = {
442
- "http": self.request_proxy,
443
- "https": self.request_proxy,
444
- }
445
-
446
- with open(str(module_dir / "data/template.yaml"), "r", encoding="utf-8") as f:
447
- template = yaml.safe_load(f)
448
-
449
- template["proxy-groups"].extend(
450
- [
451
- {
452
- "name": "全局选择",
453
- "type": "select",
454
- "proxies": ["自动选择", "手动选择", "轮询"]
455
- + [item["name"] for item in sub_list],
456
- },
457
- {
458
- "name": "自动选择",
459
- "type": "url-test",
460
- "include-all": True,
461
- },
462
- {
463
- "name": "手动选择",
464
- "type": "select",
465
- "include-all": True,
466
- },
467
- {
468
- "name": "轮询",
469
- "type": "load-balance",
470
- "url": "https://api.bitget.com/api/v2/public/time",
471
- "strategy": "round-robin",
472
- "include-all": True,
473
- },
474
- ]
475
- )
476
-
477
- userinfo = ""
478
- template["proxies"] = []
479
- for item in sub_list:
480
- headers = {"User-Agent": item["user_agent"]} if item["user_agent"] else {}
481
-
482
- if not item["url"]:
483
- raise ValueError("Invalid subscription URL.")
484
- response = requests.get(item["url"], headers=headers, proxies=proxies)
485
- response.raise_for_status()
486
- if not userinfo:
487
- userinfo = response.headers["Subscription-Userinfo"]
488
- remote_config = yaml.safe_load(response.text)
489
-
490
- # 检查 remote_config 是否为 None
491
- if remote_config is None:
492
- raise ValueError("Invalid subscription content: empty or invalid YAML.")
493
-
494
- ps = remote_config.get("proxies", [])
495
- if not ps:
496
- raise ValueError("No proxies found in subscription.")
497
-
498
- template["proxies"].extend(ps)
499
-
500
- template["proxy-groups"].append(
501
- {
502
- "name": item["name"],
503
- "type": "url-test",
504
- "proxies": [item["name"] for item in ps],
505
- },
506
- )
507
-
508
- template["rules"].append("IP-CIDR,192.168.0.0/16,DIRECT,no-resolve")
509
- template["rules"].append("IP-CIDR,10.0.0.0/8,DIRECT,no-resolve")
510
- template["rules"].append("IP-CIDR,172.16.0.0/12,DIRECT,no-resolve")
511
- template["rules"].append("IP-CIDR,127.0.0.0/8,DIRECT,no-resolve")
512
- # ⬇️饥荒服务器相关端口
513
- template["rules"].append("DST-PORT,10999,DIRECT")
514
- template["rules"].append("DST-PORT,10998,DIRECT")
515
- template["rules"].append("DST-PORT,27016,DIRECT")
516
- template["rules"].append("DST-PORT,27017,DIRECT")
517
- template["rules"].append("DST-PORT,8766,DIRECT")
518
- template["rules"].append("DST-PORT,8767,DIRECT")
519
-
520
- template["rules"].append("DOMAIN,clash.razord.top,DIRECT")
521
- template["rules"].append("DOMAIN,yacd.haishan.me,DIRECT")
522
- # template["rules"].append("DOMAIN,accounts.klei.com,全局选择")
523
-
524
- # 获取rules
525
- rule_list = [
526
- [f"{self.my_rule_base_url}/direct.yaml", "DIRECT"],
527
- [f"{self.my_rule_base_url}/proxy.yaml", "全局选择"],
528
- [
529
- f"{self.my_rule_base_url}/round.yaml",
530
- "轮询",
531
- ],
532
- [
533
- f"{self.my_rule_base_url}/reject.yaml",
534
- "REJECT",
535
- ],
536
- ]
537
-
538
- for item in rule_list:
539
- response = requests.get(item[0], proxies=proxies)
540
- response.raise_for_status()
541
- remote = yaml.safe_load(response.text)
542
- template["rule-providers"][os.path.basename(item[0])] = {
543
- "type": "inline",
544
- "behavior": "classical",
545
- "payload": remote["payload"],
546
- }
547
-
548
- template["rules"].append(f"RULE-SET,{os.path.basename(item[0])},{item[1]}")
549
-
550
- template["rule-providers"].update(
551
- {
552
- "applications": {
553
- "type": "http",
554
- "format": "yaml",
555
- "behavior": "classical",
556
- "url": f"{self.rule_base_url}/applications.txt",
557
- "path": "./ruleset/applications.yaml",
558
- "interval": 86400,
559
- },
560
- "private": {
561
- "type": "http",
562
- "format": "yaml",
563
- "behavior": "domain",
564
- "url": f"{self.rule_base_url}/private.txt",
565
- "path": "./ruleset/private.yaml",
566
- "interval": 86400,
567
- },
568
- "tld-not-cn": {
569
- "type": "http",
570
- "format": "yaml",
571
- "behavior": "domain",
572
- "url": f"{self.rule_base_url}/tld-not-cn.txt",
573
- "path": "./ruleset/tld-not-cn.yaml",
574
- "interval": 86400,
575
- },
576
- "telegramcidr": {
577
- "type": "http",
578
- "format": "yaml",
579
- "behavior": "ipcidr",
580
- "url": f"{self.rule_base_url}/telegramcidr.txt",
581
- "path": "./ruleset/telegramcidr.yaml",
582
- "interval": 86400,
583
- },
584
- "gfw": {
585
- "type": "http",
586
- "format": "yaml",
587
- "behavior": "domain",
588
- "url": f"{self.rule_base_url}/gfw.txt",
589
- "path": "./ruleset/gfw.yaml",
590
- "interval": 86400,
591
- },
592
- # "reject": {
593
- # "type": "http",
594
- # "format": "yaml",
595
- # "behavior": "domain",
596
- # "url": f"{self.rule_base_url}/reject.txt",
597
- # "path": "./ruleset/reject.yaml",
598
- # "interval": 86400,
599
- # },
600
- },
601
- )
602
-
603
- template["rules"].extend(
604
- [
605
- "RULE-SET,applications,DIRECT",
606
- "RULE-SET,private,DIRECT",
607
- # "RULE-SET,reject,REJECT",
608
- "RULE-SET,tld-not-cn,全局选择",
609
- "RULE-SET,gfw,全局选择",
610
- "RULE-SET,telegramcidr,全局选择",
611
- "MATCH,DIRECT",
612
- ]
613
- )
614
-
615
- return template, userinfo
616
-
617
- def query2sub(self, urls: str, agents: str, names: str):
618
- url_list = urls.split(",") if urls and urls.strip() else []
619
- agents_list = agents.split(",") if agents else []
620
- name_list = names.split(",") if names else []
621
-
622
- max_length = max(len(url_list), len(agents_list), len(name_list))
623
-
624
- while len(url_list) < max_length:
625
- url_list.append("")
626
- while len(agents_list) < max_length:
627
- agents_list.append("")
628
- while len(name_list) < max_length:
629
- name_list.append("")
630
-
631
- sub_list = []
632
-
633
- for i in range(max_length):
634
- if not url_list[i]:
635
- raise ValueError(f"Invalid subscription URL. #{i + 1}")
636
-
637
- sub_list.append(
638
- {
639
- "url": url_list[i],
640
- "user_agent": agents_list[i] if agents_list[i] else "",
641
- "name": name_list[i] if name_list[i] else f"订阅{i}",
642
- }
643
- )
644
-
645
- return sub_list
646
-
647
-
648
- generator_instance = None
649
-
650
-
651
- def init_generator(config: ClashConfig):
652
- global generator_instance
653
- generator_instance = ClashYamlGenerator(config)
654
-
655
-
656
- def get_generator():
657
- global generator_instance
658
- if generator_instance is None:
659
- raise RuntimeError("ClashYamlGenerator not initialized")
660
- return generator_instance
661
-
662
-
663
- def get_generator_dependency():
664
- return Depends(get_generator)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes