pymecli 0.6.3__tar.gz → 0.6.4__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 (52) hide show
  1. {pymecli-0.6.3 → pymecli-0.6.4}/PKG-INFO +1 -1
  2. {pymecli-0.6.3 → pymecli-0.6.4}/api/v1/clash.py +39 -3
  3. {pymecli-0.6.3 → pymecli-0.6.4}/core/clash.py +85 -10
  4. {pymecli-0.6.3 → pymecli-0.6.4}/pyproject.toml +1 -1
  5. {pymecli-0.6.3 → pymecli-0.6.4}/.gitignore +0 -0
  6. {pymecli-0.6.3 → pymecli-0.6.4}/README.md +0 -0
  7. {pymecli-0.6.3 → pymecli-0.6.4}/api/__init__.py +0 -0
  8. {pymecli-0.6.3 → pymecli-0.6.4}/api/v1/__init__.py +0 -0
  9. {pymecli-0.6.3 → pymecli-0.6.4}/api/v1/redis.py +0 -0
  10. {pymecli-0.6.3 → pymecli-0.6.4}/cli/__init__.py +0 -0
  11. {pymecli-0.6.3 → pymecli-0.6.4}/cli/bitget.py +0 -0
  12. {pymecli-0.6.3 → pymecli-0.6.4}/cli/dnspod.py +0 -0
  13. {pymecli-0.6.3 → pymecli-0.6.4}/cli/dst.py +0 -0
  14. {pymecli-0.6.3 → pymecli-0.6.4}/cli/etcd_sync.py +0 -0
  15. {pymecli-0.6.3 → pymecli-0.6.4}/cli/example.py +0 -0
  16. {pymecli-0.6.3 → pymecli-0.6.4}/cli/fast.py +0 -0
  17. {pymecli-0.6.3 → pymecli-0.6.4}/cli/gate.py +0 -0
  18. {pymecli-0.6.3 → pymecli-0.6.4}/cli/okx.py +0 -0
  19. {pymecli-0.6.3 → pymecli-0.6.4}/cli/quark.py +0 -0
  20. {pymecli-0.6.3 → pymecli-0.6.4}/cli/redis_csv.py +0 -0
  21. {pymecli-0.6.3 → pymecli-0.6.4}/cli/tele.py +0 -0
  22. {pymecli-0.6.3 → pymecli-0.6.4}/cli/util.py +0 -0
  23. {pymecli-0.6.3 → pymecli-0.6.4}/core/__init__.py +0 -0
  24. {pymecli-0.6.3 → pymecli-0.6.4}/core/config.py +0 -0
  25. {pymecli-0.6.3 → pymecli-0.6.4}/core/redis_client.py +0 -0
  26. {pymecli-0.6.3 → pymecli-0.6.4}/crypto/__init__.py +0 -0
  27. {pymecli-0.6.3 → pymecli-0.6.4}/crypto/bitget.py +0 -0
  28. {pymecli-0.6.3 → pymecli-0.6.4}/crypto/gate.py +0 -0
  29. {pymecli-0.6.3 → pymecli-0.6.4}/crypto/okx.py +0 -0
  30. {pymecli-0.6.3 → pymecli-0.6.4}/data/__init__.py +0 -0
  31. {pymecli-0.6.3 → pymecli-0.6.4}/data/dou_dict.py +0 -0
  32. {pymecli-0.6.3 → pymecli-0.6.4}/data/dou_list.py +0 -0
  33. {pymecli-0.6.3 → pymecli-0.6.4}/data/main.py +0 -0
  34. {pymecli-0.6.3 → pymecli-0.6.4}/data/template.yaml +0 -0
  35. {pymecli-0.6.3 → pymecli-0.6.4}/models/__init__.py +0 -0
  36. {pymecli-0.6.3 → pymecli-0.6.4}/models/douzero_model.py +0 -0
  37. {pymecli-0.6.3 → pymecli-0.6.4}/models/ocr_model.py +0 -0
  38. {pymecli-0.6.3 → pymecli-0.6.4}/models/response.py +0 -0
  39. {pymecli-0.6.3 → pymecli-0.6.4}/utils/__init__.py +0 -0
  40. {pymecli-0.6.3 → pymecli-0.6.4}/utils/elapsed.py +0 -0
  41. {pymecli-0.6.3 → pymecli-0.6.4}/utils/etcd.py +0 -0
  42. {pymecli-0.6.3 → pymecli-0.6.4}/utils/helper.py +0 -0
  43. {pymecli-0.6.3 → pymecli-0.6.4}/utils/logger.py +0 -0
  44. {pymecli-0.6.3 → pymecli-0.6.4}/utils/mysql.py +0 -0
  45. {pymecli-0.6.3 → pymecli-0.6.4}/utils/notify.py +0 -0
  46. {pymecli-0.6.3 → pymecli-0.6.4}/utils/path.py +0 -0
  47. {pymecli-0.6.3 → pymecli-0.6.4}/utils/pd.py +0 -0
  48. {pymecli-0.6.3 → pymecli-0.6.4}/utils/pyredis.py +0 -0
  49. {pymecli-0.6.3 → pymecli-0.6.4}/utils/sendNotify.py +0 -0
  50. {pymecli-0.6.3 → pymecli-0.6.4}/utils/sleep.py +0 -0
  51. {pymecli-0.6.3 → pymecli-0.6.4}/utils/text.py +0 -0
  52. {pymecli-0.6.3 → pymecli-0.6.4}/utils/toml.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pymecli
3
- Version: 0.6.3
3
+ Version: 0.6.4
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
@@ -1,3 +1,5 @@
1
+ import base64
2
+
1
3
  import yaml
2
4
  from fastapi import APIRouter, Query, Response
3
5
 
@@ -5,6 +7,25 @@ from core.clash import ClashYamlGenerator, get_generator_dependency
5
7
 
6
8
  router = APIRouter()
7
9
 
10
+ # base64 解码后 YAML 最大 64KB,防止恶意超大请求
11
+ _MAX_CUSTOM_YAML_BYTES = 64 * 1024
12
+
13
+
14
+ def _parse_custom_proxies(custom_b64: str | None) -> list:
15
+ """把 base64 编码的 custom.yaml 解码,只提取 proxies 列表"""
16
+ if not custom_b64:
17
+ return []
18
+ try:
19
+ raw = base64.b64decode(custom_b64)
20
+ if len(raw) > _MAX_CUSTOM_YAML_BYTES:
21
+ return []
22
+ cfg = yaml.safe_load(raw.decode("utf-8"))
23
+ if not isinstance(cfg, dict):
24
+ return []
25
+ return cfg.get("proxies", []) or []
26
+ except Exception:
27
+ return []
28
+
8
29
 
9
30
  class NoAnchorDumper(yaml.Dumper):
10
31
  def ignore_aliases(self, data):
@@ -26,11 +47,16 @@ async def subProviderWhite(
26
47
  names: str = Query(
27
48
  "订阅1", description="订阅名称,逗号分隔(可选,在客户端中的显示名)"
28
49
  ),
50
+ custom: str | None = Query(
51
+ None,
52
+ description="自定义clash配置(base64编码,可选,只提取proxies节点合并)",
53
+ ),
29
54
  generator: ClashYamlGenerator = get_generator_dependency(),
30
55
  ):
31
56
  # 将JSON字符串解析为Python对象
32
57
  sub_list = generator.query2sub(urls, agents, names)
33
- yaml_content, userinfo = generator.genPW(sub_list)
58
+ custom_proxies = _parse_custom_proxies(custom)
59
+ yaml_content, userinfo = generator.genPW(sub_list, custom_proxies)
34
60
  yaml_string = yaml.dump(
35
61
  yaml_content,
36
62
  allow_unicode=True,
@@ -60,11 +86,16 @@ async def subProviderBlack(
60
86
  names: str = Query(
61
87
  "订阅1", description="订阅名称,逗号分隔(可选,在客户端中的显示名)"
62
88
  ),
89
+ custom: str | None = Query(
90
+ None,
91
+ description="自定义clash配置(base64编码,可选,只提取proxies节点合并)",
92
+ ),
63
93
  generator: ClashYamlGenerator = get_generator_dependency(),
64
94
  ):
65
95
  # 将JSON字符串解析为Python对象
66
96
  sub_list = generator.query2sub(urls, agents, names)
67
- yaml_content, userinfo = generator.genPB(sub_list)
97
+ custom_proxies = _parse_custom_proxies(custom)
98
+ yaml_content, userinfo = generator.genPB(sub_list, custom_proxies)
68
99
  yaml_string = yaml.dump(
69
100
  yaml_content,
70
101
  allow_unicode=True,
@@ -94,11 +125,16 @@ async def subBlack(
94
125
  names: str = Query(
95
126
  "订阅1", description="订阅名称,逗号分隔(可选,在客户端中的显示名)"
96
127
  ),
128
+ custom: str | None = Query(
129
+ None,
130
+ description="自定义clash配置(base64编码,可选,只提取proxies节点合并)",
131
+ ),
97
132
  generator: ClashYamlGenerator = get_generator_dependency(),
98
133
  ):
99
134
  # 将JSON字符串解析为Python对象
100
135
  sub_list = generator.query2sub(urls, agents, names)
101
- yaml_content, userinfo = generator.genB(sub_list)
136
+ custom_proxies = _parse_custom_proxies(custom)
137
+ yaml_content, userinfo = generator.genB(sub_list, custom_proxies)
102
138
  yaml_string = yaml.dump(
103
139
  yaml_content,
104
140
  allow_unicode=True,
@@ -52,6 +52,30 @@ class ClashYamlGenerator:
52
52
 
53
53
  # ---------- 通用 helpers ----------
54
54
 
55
+ def _dedupe_proxy_names(self, existing_proxies: list, new_proxies: list, suffix: str = "_custom") -> list:
56
+ """对 new_proxies 里与 existing_proxies 重名的节点改名加后缀。
57
+
58
+ 例:已有 "lighthouse",custom 也有 "lighthouse" → 改名为 "lighthouse_custom"
59
+ 再加一个 → "lighthouse_custom_2"
60
+ """
61
+ used = {p["name"] for p in existing_proxies}
62
+ result = []
63
+ for p in new_proxies:
64
+ name = p.get("name", "")
65
+ if name not in used:
66
+ used.add(name)
67
+ result.append(p)
68
+ else:
69
+ i = 1
70
+ new_name = f"{name}{suffix}"
71
+ while new_name in used:
72
+ i += 1
73
+ new_name = f"{name}{suffix}_{i}"
74
+ used.add(new_name)
75
+ renamed = {**p, "name": new_name}
76
+ result.append(renamed)
77
+ return result
78
+
55
79
  def _get_proxies(self):
56
80
  if not self.request_proxy:
57
81
  return None
@@ -150,15 +174,18 @@ class ClashYamlGenerator:
150
174
  "gfw": self._http_rule_provider("gfw", "domain"),
151
175
  }
152
176
 
153
- def _provider_proxy_groups(self, sub_list):
177
+ def _provider_proxy_groups(self, sub_list, with_custom=False):
154
178
  """provider 风格的 proxy-groups(genPW / genPB)"""
155
179
  use_list = [f"provider.{item['name']}" for item in sub_list]
180
+ if with_custom:
181
+ use_list.append("provider.custom")
156
182
  return [
157
183
  {
158
184
  "name": "全局选择",
159
185
  "type": "select",
160
186
  "proxies": ["自动选择", "手动选择", "轮询"]
161
- + [item["name"] for item in sub_list],
187
+ + [item["name"] for item in sub_list]
188
+ + (["自定义节点"] if with_custom else []),
162
189
  },
163
190
  {
164
191
  "name": "自动选择",
@@ -224,13 +251,18 @@ class ClashYamlGenerator:
224
251
  )
225
252
 
226
253
  def _add_inline_sub(self, template, item, ps):
227
- """单个订阅直接写入 proxies + url-test group(genB)"""
228
- template["proxies"].extend(ps)
254
+ """单个订阅直接写入 proxies + url-test group(genB)
255
+
256
+ 重名节点自动加后缀(订阅名),group 里的 proxies 列表同步用重命名后的名字。
257
+ """
258
+ suffix = f"_{item['name']}" if item.get("name") else ""
259
+ deduped = self._dedupe_proxy_names(template["proxies"], ps, suffix)
260
+ template["proxies"].extend(deduped)
229
261
  template["proxy-groups"].append(
230
262
  {
231
263
  "name": item["name"],
232
264
  "type": "url-test",
233
- "proxies": [p["name"] for p in ps],
265
+ "proxies": [p["name"] for p in deduped],
234
266
  }
235
267
  )
236
268
 
@@ -266,16 +298,35 @@ class ClashYamlGenerator:
266
298
  # ---------- 三种生成模式 ----------
267
299
 
268
300
  # 白名单模式 Rules 配置方式
269
- def genPW(self, sub_list: list[dict]):
301
+ def genPW(self, sub_list: list[dict], custom_proxies: list | None = None):
270
302
  proxies = self._get_proxies()
271
303
  template = self._load_template(proxies)
272
304
 
273
- template["proxy-groups"].extend(self._provider_proxy_groups(sub_list))
305
+ has_custom = bool(custom_proxies)
306
+ template["proxy-groups"].extend(self._provider_proxy_groups(sub_list, has_custom))
274
307
 
275
308
  subs, userinfo = self._fetch_subscriptions(sub_list, proxies)
276
309
  for item, ps in subs:
277
310
  self._add_provider_sub(template, item, ps)
278
311
 
312
+ # custom proxies → provider + group
313
+ if custom_proxies:
314
+ template["proxy-providers"]["provider.custom"] = {
315
+ "type": "inline",
316
+ "payload": custom_proxies,
317
+ }
318
+ template["proxy-groups"].append(
319
+ {
320
+ "name": "自定义节点",
321
+ "type": "url-test",
322
+ "url": "https://www.gstatic.com/generate_204",
323
+ "interval": 300,
324
+ "tolerance": 11,
325
+ "lazy": True,
326
+ "use": ["provider.custom"],
327
+ }
328
+ )
329
+
279
330
  self._add_inline_rule_providers(template, proxies)
280
331
  template["rule-providers"].update(self._whitelist_http_rule_providers())
281
332
  template["rules"].extend(WHITELIST_RULES)
@@ -283,16 +334,35 @@ class ClashYamlGenerator:
283
334
  return template, userinfo
284
335
 
285
336
  # 黑名单模式 Rules 配置方式
286
- def genPB(self, sub_list: list[dict]):
337
+ def genPB(self, sub_list: list[dict], custom_proxies: list | None = None):
287
338
  proxies = self._get_proxies()
288
339
  template = self._load_template(proxies)
289
340
 
290
- template["proxy-groups"].extend(self._provider_proxy_groups(sub_list))
341
+ has_custom = bool(custom_proxies)
342
+ template["proxy-groups"].extend(self._provider_proxy_groups(sub_list, has_custom))
291
343
 
292
344
  subs, userinfo = self._fetch_subscriptions(sub_list, proxies)
293
345
  for item, ps in subs:
294
346
  self._add_provider_sub(template, item, ps)
295
347
 
348
+ # custom proxies → provider + group
349
+ if custom_proxies:
350
+ template["proxy-providers"]["provider.custom"] = {
351
+ "type": "inline",
352
+ "payload": custom_proxies,
353
+ }
354
+ template["proxy-groups"].append(
355
+ {
356
+ "name": "自定义节点",
357
+ "type": "url-test",
358
+ "url": "https://www.gstatic.com/generate_204",
359
+ "interval": 300,
360
+ "tolerance": 11,
361
+ "lazy": True,
362
+ "use": ["provider.custom"],
363
+ }
364
+ )
365
+
296
366
  self._add_base_rules(template, with_dst_port=False)
297
367
  self._add_inline_rule_providers(template, proxies)
298
368
  template["rule-providers"].update(self._blacklist_http_rule_providers())
@@ -300,7 +370,7 @@ class ClashYamlGenerator:
300
370
 
301
371
  return template, userinfo
302
372
 
303
- def genB(self, sub_list: list[dict]):
373
+ def genB(self, sub_list: list[dict], custom_proxies: list | None = None):
304
374
  proxies = self._get_proxies()
305
375
  template = self._load_template(proxies)
306
376
 
@@ -311,6 +381,11 @@ class ClashYamlGenerator:
311
381
  for item, ps in subs:
312
382
  self._add_inline_sub(template, item, ps)
313
383
 
384
+ # custom proxies 去重后追加(proxy-groups 用 include-all 自动包含)
385
+ if custom_proxies:
386
+ deduped = self._dedupe_proxy_names(template["proxies"], custom_proxies)
387
+ template["proxies"].extend(deduped)
388
+
314
389
  self._add_base_rules(template, with_dst_port=True)
315
390
  self._add_inline_rule_providers(template, proxies)
316
391
  template["rule-providers"].update(self._blacklist_http_rule_providers())
@@ -38,7 +38,7 @@
38
38
  name = "pymecli"
39
39
  readme = "README.md"
40
40
  requires-python = ">=3.10"
41
- version = "0.6.3"
41
+ version = "0.6.4"
42
42
 
43
43
  [project.urls]
44
44
  Homepage = "https://pypi.org/project/pymecli"
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
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