pymecli 0.6.2__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.
- {pymecli-0.6.2 → pymecli-0.6.4}/PKG-INFO +10 -2
- {pymecli-0.6.2 → pymecli-0.6.4}/README.md +9 -1
- {pymecli-0.6.2 → pymecli-0.6.4}/api/v1/clash.py +39 -3
- {pymecli-0.6.2 → pymecli-0.6.4}/cli/etcd_sync.py +35 -6
- {pymecli-0.6.2 → pymecli-0.6.4}/core/clash.py +85 -10
- {pymecli-0.6.2 → pymecli-0.6.4}/pyproject.toml +1 -1
- {pymecli-0.6.2 → pymecli-0.6.4}/.gitignore +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/api/__init__.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/api/v1/__init__.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/api/v1/redis.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/cli/__init__.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/cli/bitget.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/cli/dnspod.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/cli/dst.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/cli/example.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/cli/fast.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/cli/gate.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/cli/okx.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/cli/quark.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/cli/redis_csv.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/cli/tele.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/cli/util.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/core/__init__.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/core/config.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/core/redis_client.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/crypto/__init__.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/crypto/bitget.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/crypto/gate.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/crypto/okx.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/data/__init__.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/data/dou_dict.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/data/dou_list.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/data/main.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/data/template.yaml +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/models/__init__.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/models/douzero_model.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/models/ocr_model.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/models/response.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/utils/__init__.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/utils/elapsed.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/utils/etcd.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/utils/helper.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/utils/logger.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/utils/mysql.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/utils/notify.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/utils/path.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/utils/pd.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/utils/pyredis.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/utils/sendNotify.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/utils/sleep.py +0 -0
- {pymecli-0.6.2 → pymecli-0.6.4}/utils/text.py +0 -0
- {pymecli-0.6.2 → 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
|
+
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
|
|
@@ -88,6 +88,15 @@ uv run quark check
|
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
# dnspod
|
|
91
|
+
## 环境变量说明
|
|
92
|
+
- `DP_ID`: DNSPod 登录 ID
|
|
93
|
+
- `DP_TOKEN`: DNSPod 登录 Token
|
|
94
|
+
- `DP_DOMAIN`: 域名 (如 cursor.email)
|
|
95
|
+
- `DP_RECORD_ID`: IPv4 A 记录 ID
|
|
96
|
+
- `DP_RECORD_ID_IPV6`: IPv6 AAAA 记录 ID
|
|
97
|
+
- `DP_SUB_DOMAIN`: 子域名前缀 (默认 "api")
|
|
98
|
+
- `DP_RECORD_LINE`: 记录线路 (默认 "默认")
|
|
99
|
+
## 示例
|
|
91
100
|
```shell
|
|
92
101
|
# 列出域名下所有记录(先跑这个查 record_id)
|
|
93
102
|
uv run dnspod list
|
|
@@ -97,6 +106,5 @@ uv run dnspod ddns
|
|
|
97
106
|
uv run dnspod ddns --force
|
|
98
107
|
# 更新 IPv6 AAAA 记录(从 Redis 读 local.IPv6)
|
|
99
108
|
uv run dnspod ddns6
|
|
100
|
-
|
|
101
109
|
uv run dnspod ddns6 --force
|
|
102
110
|
```
|
|
@@ -49,6 +49,15 @@ uv run quark check
|
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
# dnspod
|
|
52
|
+
## 环境变量说明
|
|
53
|
+
- `DP_ID`: DNSPod 登录 ID
|
|
54
|
+
- `DP_TOKEN`: DNSPod 登录 Token
|
|
55
|
+
- `DP_DOMAIN`: 域名 (如 cursor.email)
|
|
56
|
+
- `DP_RECORD_ID`: IPv4 A 记录 ID
|
|
57
|
+
- `DP_RECORD_ID_IPV6`: IPv6 AAAA 记录 ID
|
|
58
|
+
- `DP_SUB_DOMAIN`: 子域名前缀 (默认 "api")
|
|
59
|
+
- `DP_RECORD_LINE`: 记录线路 (默认 "默认")
|
|
60
|
+
## 示例
|
|
52
61
|
```shell
|
|
53
62
|
# 列出域名下所有记录(先跑这个查 record_id)
|
|
54
63
|
uv run dnspod list
|
|
@@ -58,6 +67,5 @@ uv run dnspod ddns
|
|
|
58
67
|
uv run dnspod ddns --force
|
|
59
68
|
# 更新 IPv6 AAAA 记录(从 Redis 读 local.IPv6)
|
|
60
69
|
uv run dnspod ddns6
|
|
61
|
-
|
|
62
70
|
uv run dnspod ddns6 --force
|
|
63
71
|
```
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -25,6 +25,32 @@ logger = get_logger(__name__, level=logging.INFO)
|
|
|
25
25
|
DEFAULT_TABLE = "kvs"
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# 智能 JSON 转换(etcd ↔ 文件)
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
def _try_json_loads(s: str):
|
|
33
|
+
"""尝试把字符串当 JSON 解析,成功返回对象,失败返回原字符串。
|
|
34
|
+
|
|
35
|
+
只接受以 { / [ 开头的内容,避免把 "123" "true" 这类普通值也误解析。
|
|
36
|
+
"""
|
|
37
|
+
s = s.strip()
|
|
38
|
+
if not s or s[0] not in "{[":
|
|
39
|
+
return s
|
|
40
|
+
try:
|
|
41
|
+
return json.loads(s)
|
|
42
|
+
except (json.JSONDecodeError, TypeError):
|
|
43
|
+
return s
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _json_or_str(v) -> str:
|
|
47
|
+
"""dict/list → 紧凑 JSON 字符串;其他 → str(v)。"""
|
|
48
|
+
if isinstance(v, (dict, list)):
|
|
49
|
+
# separators=(",", ":") 去掉空格,保持 etcd 里原样
|
|
50
|
+
return json.dumps(v, ensure_ascii=False, separators=(",", ":"))
|
|
51
|
+
return str(v)
|
|
52
|
+
|
|
53
|
+
|
|
28
54
|
@app.command("mysql2etcd")
|
|
29
55
|
def mysql_to_etcd(
|
|
30
56
|
prefix: str = typer.Argument(
|
|
@@ -182,17 +208,19 @@ def etcd_to_json(
|
|
|
182
208
|
):
|
|
183
209
|
"""将 etcd 中指定前缀的 key/value 导出为 JSON 文件。
|
|
184
210
|
|
|
185
|
-
JSON
|
|
211
|
+
智能转换:如果 etcd value 是 JSON 字符串(以 { 或 [ 开头),
|
|
212
|
+
会自动解析为真正的 JSON 对象写入文件,编辑更方便;
|
|
213
|
+
普通字符串保持原样。
|
|
186
214
|
"""
|
|
187
215
|
etcd = get_etcd_client(env_path)
|
|
188
216
|
|
|
189
217
|
try:
|
|
190
|
-
data: dict
|
|
218
|
+
data: dict = {}
|
|
191
219
|
iterator = etcd.get_all() if not prefix else etcd.get_prefix(prefix)
|
|
192
220
|
for value, meta in iterator:
|
|
193
221
|
k = meta.key.decode("utf-8")
|
|
194
|
-
|
|
195
|
-
data[k] =
|
|
222
|
+
raw = value.decode("utf-8") if isinstance(value, bytes) else value
|
|
223
|
+
data[k] = _try_json_loads(raw)
|
|
196
224
|
|
|
197
225
|
with open(file_path, "w", encoding="utf-8") as f:
|
|
198
226
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
@@ -226,7 +254,8 @@ def json_to_etcd(
|
|
|
226
254
|
):
|
|
227
255
|
"""将 JSON 文件中的 key/value 写入 etcd。
|
|
228
256
|
|
|
229
|
-
|
|
257
|
+
智能转换:dict/list 会自动序列化为紧凑 JSON 字符串写入 etcd;
|
|
258
|
+
字符串/数字/布尔保持 str() 转换。
|
|
230
259
|
--prefix 会加到每个 key 前面(如果 key 本身已带该前缀则不重复加)。
|
|
231
260
|
"""
|
|
232
261
|
etcd = get_etcd_client(env_path)
|
|
@@ -253,7 +282,7 @@ def json_to_etcd(
|
|
|
253
282
|
if prefix and not k.startswith(prefix):
|
|
254
283
|
full_key = prefix.rstrip("/") + "/" + k.lstrip("/")
|
|
255
284
|
|
|
256
|
-
etcd.put(full_key,
|
|
285
|
+
etcd.put(full_key, _json_or_str(v))
|
|
257
286
|
logger.debug("put: %s -> %s", full_key, v)
|
|
258
287
|
count += 1
|
|
259
288
|
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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())
|
|
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
|