sub-customizer 0.0.1__py3-none-any.whl
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.
- sub_customizer/__init__.py +8 -0
- sub_customizer/customizer.py +394 -0
- sub_customizer/datastructures.py +168 -0
- sub_customizer-0.0.1.dist-info/LICENSE +674 -0
- sub_customizer-0.0.1.dist-info/METADATA +22 -0
- sub_customizer-0.0.1.dist-info/RECORD +11 -0
- sub_customizer-0.0.1.dist-info/WHEEL +5 -0
- sub_customizer-0.0.1.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_datastructures.py +107 -0
- tests/test_true.py +2 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
import io
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
from collections import OrderedDict
|
|
6
|
+
from functools import cached_property, lru_cache
|
|
7
|
+
from typing import IO, TYPE_CHECKING, List, Literal, Optional, TypedDict
|
|
8
|
+
from urllib import parse
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
import yaml
|
|
12
|
+
from pydantic import ValidationError
|
|
13
|
+
|
|
14
|
+
from .datastructures import ClashConfig
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@lru_cache(None)
|
|
20
|
+
def is_url(url: str) -> bool:
|
|
21
|
+
try:
|
|
22
|
+
result = parse.urlparse(url)
|
|
23
|
+
return result.scheme in ["http", "https", "ftp"] and bool(result.netloc)
|
|
24
|
+
except ValueError:
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConfigParserMultiValues(OrderedDict):
|
|
29
|
+
def __setitem__(self, key, value):
|
|
30
|
+
if key in self and isinstance(value, list):
|
|
31
|
+
self[key].extend(value)
|
|
32
|
+
else:
|
|
33
|
+
super().__setitem__(key, value)
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def getlist(value):
|
|
37
|
+
return value.splitlines()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ConfigParser(configparser.RawConfigParser):
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
|
|
43
|
+
def getlist(self, section: str, option: str, **kwargs) -> list[str]: # type: ignore
|
|
44
|
+
...
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self, strict=False, dict_type=ConfigParserMultiValues, converters=None, **kwargs
|
|
48
|
+
):
|
|
49
|
+
if converters is None:
|
|
50
|
+
converters = {"list": ConfigParserMultiValues.getlist}
|
|
51
|
+
super().__init__(
|
|
52
|
+
strict=strict, dict_type=dict_type, converters=converters, **kwargs
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RulesetParseResultT(TypedDict, total=False):
|
|
57
|
+
group: str
|
|
58
|
+
type: Literal["surge", "quanx", "clash-domain", "clash-ipcidr", "clash-classic"]
|
|
59
|
+
rule: str
|
|
60
|
+
is_url: bool
|
|
61
|
+
interval: Optional[int]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class CustomProxyGroupParseResultT(TypedDict, total=False):
|
|
65
|
+
name: str
|
|
66
|
+
type: Literal["select", "url-test", "fallback", "load-balance"]
|
|
67
|
+
rules: list[str]
|
|
68
|
+
test_url: Optional[str]
|
|
69
|
+
interval: Optional[int]
|
|
70
|
+
timeout: Optional[int]
|
|
71
|
+
tolerance: Optional[int]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class RulesetParser:
|
|
75
|
+
# 定义支持的类型和前缀映射
|
|
76
|
+
VALID_TYPES = ["surge", "quanx", "clash-domain", "clash-ipcidr", "clash-classic"]
|
|
77
|
+
|
|
78
|
+
def __init__(self):
|
|
79
|
+
self.ruleset_pattern = re.compile(
|
|
80
|
+
r"^(?P<group>.+?),"
|
|
81
|
+
r"(?:\[(?P<type>[a-zA-Z0-9\-]+)])?" # 匹配类型(可选)
|
|
82
|
+
r"(?P<rule>.*?)" # 匹配规则部分
|
|
83
|
+
r"(?:,(\d+))?$" # 匹配可选的更新间隔(秒)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def parse(self, rulesets: List[str]) -> List[RulesetParseResultT]:
|
|
87
|
+
"""
|
|
88
|
+
解析规则集,返回处理后的字典列表。
|
|
89
|
+
"""
|
|
90
|
+
parsed_rules = []
|
|
91
|
+
for ruleset in rulesets:
|
|
92
|
+
ruleset = ruleset.strip()
|
|
93
|
+
match = self.ruleset_pattern.match(ruleset)
|
|
94
|
+
if match:
|
|
95
|
+
group = match.group("group").strip()
|
|
96
|
+
rule_content = match.group("rule").strip()
|
|
97
|
+
interval = int(i) if (i := match.group(4)) else None
|
|
98
|
+
|
|
99
|
+
# 获取类型和去除前缀后的规则内容
|
|
100
|
+
rule_type, cleaned_rule_content = self._get_type_and_rule(rule_content)
|
|
101
|
+
|
|
102
|
+
parsed_rules.append(
|
|
103
|
+
{
|
|
104
|
+
"group": group,
|
|
105
|
+
"type": rule_type,
|
|
106
|
+
"rule": cleaned_rule_content,
|
|
107
|
+
"interval": interval,
|
|
108
|
+
"is_url": is_url(cleaned_rule_content),
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
return parsed_rules
|
|
112
|
+
|
|
113
|
+
def _get_type_and_rule(self, rule_content: str) -> (str, str):
|
|
114
|
+
"""
|
|
115
|
+
根据规则内容获取对应的类型和去除前缀后的规则内容。
|
|
116
|
+
"""
|
|
117
|
+
for rule_type in self.VALID_TYPES:
|
|
118
|
+
prefix = f"{rule_type}:"
|
|
119
|
+
if rule_content.startswith(prefix):
|
|
120
|
+
return rule_type, rule_content[len(prefix) :]
|
|
121
|
+
return "surge", rule_content # 默认是 surge 类型
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class CustomProxyGroupParser:
|
|
125
|
+
support_types = {"select", "url-test", "fallback", "load-balance"}
|
|
126
|
+
|
|
127
|
+
def _parse_rest(self, rest):
|
|
128
|
+
rules = []
|
|
129
|
+
test_url = interval = timeout = tolerance = None
|
|
130
|
+
for i, item in enumerate(rest):
|
|
131
|
+
item = item.strip()
|
|
132
|
+
if not is_url(item):
|
|
133
|
+
rules.append(item)
|
|
134
|
+
else:
|
|
135
|
+
test_url = item
|
|
136
|
+
interval_params = rest[i + 1].split(",")
|
|
137
|
+
interval = interval_params[0]
|
|
138
|
+
timeout = interval_params[1] if len(interval_params) > 1 else None
|
|
139
|
+
tolerance = interval_params[2] if len(interval_params) > 2 else None
|
|
140
|
+
break
|
|
141
|
+
r = {"rules": rules}
|
|
142
|
+
if test_url:
|
|
143
|
+
r["test_url"] = test_url
|
|
144
|
+
r["interval"] = interval
|
|
145
|
+
if timeout:
|
|
146
|
+
r["timeout"] = timeout
|
|
147
|
+
if tolerance:
|
|
148
|
+
r["tolerance"] = tolerance
|
|
149
|
+
return r
|
|
150
|
+
|
|
151
|
+
def parse(self, groups: list[str]) -> list[CustomProxyGroupParseResultT]:
|
|
152
|
+
"""
|
|
153
|
+
用于自定义组的选项 会覆盖 主程序目录中的配置文件 里的内容
|
|
154
|
+
使用以下模式生成 Clash 代理组,带有 "[]" 前缀将直接添加
|
|
155
|
+
Format: Group_Name`select`Rule_1`Rule_2`...
|
|
156
|
+
Group_Name`url-test|fallback|load-balance`Rule_1`Rule_2`...`test_url`interval[,timeout][,tolerance]
|
|
157
|
+
Rule with "[]" prefix will be added directly.
|
|
158
|
+
"""
|
|
159
|
+
parsed_groups = []
|
|
160
|
+
for group_str in groups:
|
|
161
|
+
group_str = group_str.strip()
|
|
162
|
+
parts = group_str.split("`")
|
|
163
|
+
if len(parts) < 3:
|
|
164
|
+
continue
|
|
165
|
+
group_name, type_, *rest = parts
|
|
166
|
+
if type_ not in self.support_types:
|
|
167
|
+
continue
|
|
168
|
+
try:
|
|
169
|
+
r = self._parse_rest(rest)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.exception(e)
|
|
172
|
+
continue
|
|
173
|
+
group = {"name": group_name, "type": type_}
|
|
174
|
+
group.update(r)
|
|
175
|
+
parsed_groups.append(group)
|
|
176
|
+
return parsed_groups
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class RemoteConfigParser:
|
|
180
|
+
sections = ["custom"]
|
|
181
|
+
supported_options = [
|
|
182
|
+
"ruleset",
|
|
183
|
+
"custom_proxy_group",
|
|
184
|
+
"overwrite_original_rules",
|
|
185
|
+
"enable_rule_generator",
|
|
186
|
+
]
|
|
187
|
+
supported_override_options = [
|
|
188
|
+
"port",
|
|
189
|
+
"socks-port",
|
|
190
|
+
"redir-port",
|
|
191
|
+
"tproxy-port",
|
|
192
|
+
"mixed-port",
|
|
193
|
+
"allow-lan",
|
|
194
|
+
"bind-address",
|
|
195
|
+
"mode",
|
|
196
|
+
"log-level",
|
|
197
|
+
"ipv6",
|
|
198
|
+
"external-controller",
|
|
199
|
+
"external-ui",
|
|
200
|
+
"secret",
|
|
201
|
+
"interface-name",
|
|
202
|
+
"routing-mark",
|
|
203
|
+
"hosts",
|
|
204
|
+
"profile",
|
|
205
|
+
"dns",
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
def __init__(self, ini_str, clash_config: dict = None):
|
|
209
|
+
self.ini_str = ini_str
|
|
210
|
+
self.config = ConfigParser()
|
|
211
|
+
self.config.read_string(ini_str)
|
|
212
|
+
self.clash_config = clash_config or {}
|
|
213
|
+
|
|
214
|
+
@classmethod
|
|
215
|
+
def from_url(cls, url: str, **init_kws):
|
|
216
|
+
res = requests.get(url)
|
|
217
|
+
return cls(res.text, **init_kws)
|
|
218
|
+
|
|
219
|
+
@cached_property
|
|
220
|
+
def options(self):
|
|
221
|
+
rulesets = []
|
|
222
|
+
custom_proxy_groups = []
|
|
223
|
+
overwrite_original_rules = False
|
|
224
|
+
enable_rule_generator = True
|
|
225
|
+
override_options = {}
|
|
226
|
+
for section in self.sections:
|
|
227
|
+
for option in self.supported_override_options:
|
|
228
|
+
if (
|
|
229
|
+
opt_value := self.config.get(section, option, fallback=None)
|
|
230
|
+
) is not None:
|
|
231
|
+
override_options.setdefault(option, opt_value)
|
|
232
|
+
overwrite_original_rules = self.config.getboolean(
|
|
233
|
+
section, "overwrite_original_rules", fallback=overwrite_original_rules
|
|
234
|
+
)
|
|
235
|
+
enable_rule_generator = self.config.getboolean(
|
|
236
|
+
section, "enable_rule_generator", fallback=enable_rule_generator
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
rulesets.extend(self.config.getlist(section, "ruleset", fallback=[]))
|
|
240
|
+
custom_proxy_groups.extend(
|
|
241
|
+
self.config.getlist(section, "custom_proxy_group", fallback=[])
|
|
242
|
+
)
|
|
243
|
+
return {
|
|
244
|
+
"rulesets": rulesets,
|
|
245
|
+
"custom_proxy_groups": custom_proxy_groups,
|
|
246
|
+
"overwrite_original_rules": overwrite_original_rules,
|
|
247
|
+
"enable_rule_generator": enable_rule_generator,
|
|
248
|
+
"override_options": override_options,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@cached_property
|
|
252
|
+
def all_clash_proxies(self) -> dict[str, str]:
|
|
253
|
+
all_proxies = {p["name"]: p for p in self.clash_config.get("proxies") or []}
|
|
254
|
+
return all_proxies
|
|
255
|
+
|
|
256
|
+
def parse_rulesets(self):
|
|
257
|
+
rulesets = self.options["rulesets"]
|
|
258
|
+
parser = RulesetParser()
|
|
259
|
+
return parser.parse(rulesets)
|
|
260
|
+
|
|
261
|
+
def parse_custom_proxy_groups(self):
|
|
262
|
+
custom_proxy_groups = self.options["custom_proxy_groups"]
|
|
263
|
+
parser = CustomProxyGroupParser()
|
|
264
|
+
return parser.parse(custom_proxy_groups)
|
|
265
|
+
|
|
266
|
+
def _convert_rules_text(self, rules_text: str, group: str) -> list:
|
|
267
|
+
lines = rules_text.strip().splitlines()
|
|
268
|
+
results = []
|
|
269
|
+
for line in lines:
|
|
270
|
+
line = line.strip()
|
|
271
|
+
if not line or line.startswith("#"):
|
|
272
|
+
continue
|
|
273
|
+
parts = line.split(",")
|
|
274
|
+
if len(parts) < 2:
|
|
275
|
+
continue
|
|
276
|
+
parts.insert(2, group)
|
|
277
|
+
results.append(",".join(parts))
|
|
278
|
+
return results
|
|
279
|
+
|
|
280
|
+
def extract_rules(self, rulesets: list[RulesetParseResultT]):
|
|
281
|
+
session = requests.Session()
|
|
282
|
+
rules = []
|
|
283
|
+
for rule_set in rulesets:
|
|
284
|
+
if rule_set["is_url"]:
|
|
285
|
+
url = rule_set["rule"]
|
|
286
|
+
try:
|
|
287
|
+
resp = session.get(url)
|
|
288
|
+
resp.raise_for_status()
|
|
289
|
+
except requests.RequestException:
|
|
290
|
+
continue
|
|
291
|
+
rules.extend(self._convert_rules_text(resp.text, rule_set["group"]))
|
|
292
|
+
elif rule_set["rule"].startswith("[]"):
|
|
293
|
+
rule = rule_set["rule"][2:]
|
|
294
|
+
if rule.lower() == "final":
|
|
295
|
+
rule = "MATCH"
|
|
296
|
+
rules.append(f"{rule},{rule_set['group']}")
|
|
297
|
+
return rules
|
|
298
|
+
|
|
299
|
+
@lru_cache(None)
|
|
300
|
+
def _get_proxies_by_regex(self, regex: str):
|
|
301
|
+
proxies = []
|
|
302
|
+
for proxy in self.all_clash_proxies:
|
|
303
|
+
if re.search(regex, proxy):
|
|
304
|
+
proxies.append(proxy)
|
|
305
|
+
return proxies
|
|
306
|
+
|
|
307
|
+
def extract_proxy_groups(self, proxy_groups: list[CustomProxyGroupParseResultT]):
|
|
308
|
+
groups = []
|
|
309
|
+
for proxy_group in proxy_groups:
|
|
310
|
+
group = {"name": proxy_group["name"], "type": proxy_group["type"]}
|
|
311
|
+
rules = proxy_group["rules"]
|
|
312
|
+
proxies = []
|
|
313
|
+
for rule in rules:
|
|
314
|
+
if rule.startswith("[]"):
|
|
315
|
+
proxies.append(rule[2:])
|
|
316
|
+
else:
|
|
317
|
+
proxies.extend(self._get_proxies_by_regex(rule))
|
|
318
|
+
group["proxies"] = proxies
|
|
319
|
+
for k in {
|
|
320
|
+
"test_url": "url",
|
|
321
|
+
"interval": "interval",
|
|
322
|
+
"timeout": "timeout",
|
|
323
|
+
"tolerance": "tolerance",
|
|
324
|
+
}:
|
|
325
|
+
if k in proxy_group:
|
|
326
|
+
group[k] = proxy_group[k]
|
|
327
|
+
groups.append(group)
|
|
328
|
+
return groups
|
|
329
|
+
|
|
330
|
+
def get_rules(self):
|
|
331
|
+
rulesets = self.parse_rulesets()
|
|
332
|
+
return self.extract_rules(rulesets)
|
|
333
|
+
|
|
334
|
+
def get_proxy_groups(self):
|
|
335
|
+
groups = self.parse_custom_proxy_groups()
|
|
336
|
+
return self.extract_proxy_groups(groups)
|
|
337
|
+
|
|
338
|
+
def get_override_options(self):
|
|
339
|
+
override_options = self.options["override_options"]
|
|
340
|
+
try:
|
|
341
|
+
inst = ClashConfig.model_validate(override_options)
|
|
342
|
+
valid_options = inst.model_dump(
|
|
343
|
+
mode="json", by_alias=True, exclude_unset=True
|
|
344
|
+
)
|
|
345
|
+
return valid_options
|
|
346
|
+
except ValidationError as e:
|
|
347
|
+
logger.exception(e)
|
|
348
|
+
return {}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class ClashSubCustomizer:
|
|
352
|
+
headers = {"User-Agent": "Clash"}
|
|
353
|
+
|
|
354
|
+
def __init__(self, yaml_str):
|
|
355
|
+
self.yaml_str = yaml_str
|
|
356
|
+
self.config = yaml.safe_load(yaml_str)
|
|
357
|
+
|
|
358
|
+
@classmethod
|
|
359
|
+
def from_url(cls, url: str, no_proxy=False):
|
|
360
|
+
proxies = None
|
|
361
|
+
if no_proxy:
|
|
362
|
+
parsed = parse.urlparse(url)
|
|
363
|
+
proxies = {"no_proxy": parsed.hostname}
|
|
364
|
+
res = requests.get(url, headers=cls.headers, proxies=proxies)
|
|
365
|
+
return cls(res.text)
|
|
366
|
+
|
|
367
|
+
def write_remote_config(self, remote_url) -> IO[bytes]:
|
|
368
|
+
parser = RemoteConfigParser.from_url(remote_url, clash_config=self.config)
|
|
369
|
+
proxy_groups = parser.get_proxy_groups()
|
|
370
|
+
if proxy_groups:
|
|
371
|
+
self.config["proxy-groups"] = proxy_groups
|
|
372
|
+
if parser.options["enable_rule_generator"]:
|
|
373
|
+
rules = parser.get_rules()
|
|
374
|
+
if rules:
|
|
375
|
+
if parser.options["overwrite_original_rules"]:
|
|
376
|
+
self.config["rules"] = rules
|
|
377
|
+
else:
|
|
378
|
+
# 这里扩展rules而不是覆盖,远程配置中的rules优先级更高
|
|
379
|
+
self.config["rules"] = rules + self.config.get("rules", [])
|
|
380
|
+
else:
|
|
381
|
+
self.config["rules"] = []
|
|
382
|
+
override_options = parser.get_override_options()
|
|
383
|
+
self.config.update(override_options)
|
|
384
|
+
buffer = io.BytesIO()
|
|
385
|
+
yaml.dump(
|
|
386
|
+
self.config,
|
|
387
|
+
stream=buffer,
|
|
388
|
+
default_flow_style=False,
|
|
389
|
+
allow_unicode=True,
|
|
390
|
+
sort_keys=False,
|
|
391
|
+
encoding="utf-8",
|
|
392
|
+
)
|
|
393
|
+
buffer.seek(0)
|
|
394
|
+
return buffer
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import List, Optional, Union
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ModeEnum(str, Enum):
|
|
8
|
+
RULE = "rule"
|
|
9
|
+
GLOBAL = "global"
|
|
10
|
+
DIRECT = "direct"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LogLevelEnum(str, Enum):
|
|
14
|
+
INFO = "info"
|
|
15
|
+
WARNING = "warning"
|
|
16
|
+
ERROR = "error"
|
|
17
|
+
DEBUG = "debug"
|
|
18
|
+
SILENT = "silent"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EnhancedModeEnum(str, Enum):
|
|
22
|
+
FAKE_IP = "fake-ip"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ProxyTypeEnum(str, Enum):
|
|
26
|
+
SS = "ss"
|
|
27
|
+
VMESS = "vmess"
|
|
28
|
+
SOCKS5 = "socks5"
|
|
29
|
+
HTTP = "http"
|
|
30
|
+
SNELL = "snell"
|
|
31
|
+
TROJAN = "trojan"
|
|
32
|
+
SSR = "ssr"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class NetworkEnum(str, Enum):
|
|
36
|
+
TCP = "tcp"
|
|
37
|
+
UDP = "udp"
|
|
38
|
+
WS = "ws"
|
|
39
|
+
H2 = "h2"
|
|
40
|
+
GRPC = "grpc"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ProxyGroupTypeEnum(str, Enum):
|
|
44
|
+
RELAY = "relay"
|
|
45
|
+
URL_TEST = "url-test"
|
|
46
|
+
FALLBACK = "fallback"
|
|
47
|
+
LOAD_BALANCE = "load-balance"
|
|
48
|
+
SELECT = "select"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ProxyProviderTypeEnum(str, Enum):
|
|
52
|
+
HTTP = "http"
|
|
53
|
+
FILE = "file"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RuleTypeEnum(str, Enum):
|
|
57
|
+
DOMAIN_SUFFIX = "DOMAIN-SUFFIX"
|
|
58
|
+
DOMAIN_KEYWORD = "DOMAIN-KEYWORD"
|
|
59
|
+
DOMAIN = "DOMAIN"
|
|
60
|
+
SRC_IP_CIDR = "SRC-IP-CIDR"
|
|
61
|
+
IP_CIDR = "IP-CIDR"
|
|
62
|
+
GEOIP = "GEOIP"
|
|
63
|
+
DST_PORT = "DST-PORT"
|
|
64
|
+
SRC_PORT = "SRC-PORT"
|
|
65
|
+
RULE_SET = "RULE-SET"
|
|
66
|
+
MATCH = "MATCH"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Proxy(BaseModel):
|
|
70
|
+
name: str
|
|
71
|
+
type: ProxyTypeEnum
|
|
72
|
+
server: str
|
|
73
|
+
port: int
|
|
74
|
+
cipher: Optional[str] = None
|
|
75
|
+
password: Optional[str] = None
|
|
76
|
+
plugin: Optional[str] = None
|
|
77
|
+
plugin_opts: Optional[dict] = Field(None, alias="plugin-opts")
|
|
78
|
+
uuid: Optional[str] = None
|
|
79
|
+
alterId: Optional[int] = None
|
|
80
|
+
network: Optional[NetworkEnum] = None
|
|
81
|
+
tls: Optional[bool] = None
|
|
82
|
+
skip_cert_verify: Optional[bool] = Field(None, alias="skip-cert-verify")
|
|
83
|
+
servername: Optional[str] = None
|
|
84
|
+
ws_opts: Optional[dict] = Field(None, alias="ws-opts")
|
|
85
|
+
h2_opts: Optional[dict] = Field(None, alias="h2-opts")
|
|
86
|
+
grpc_opts: Optional[dict] = Field(None, alias="grpc-opts")
|
|
87
|
+
obfs: Optional[str] = None
|
|
88
|
+
protocol: Optional[str] = None
|
|
89
|
+
obfs_param: Optional[str] = Field(None, alias="obfs-param")
|
|
90
|
+
protocol_param: Optional[str] = Field(None, alias="protocol-param")
|
|
91
|
+
udp: Optional[bool] = None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ProxyGroup(BaseModel):
|
|
95
|
+
name: str
|
|
96
|
+
type: ProxyGroupTypeEnum
|
|
97
|
+
proxies: List[Union[str, Proxy]]
|
|
98
|
+
tolerance: Optional[int] = None
|
|
99
|
+
lazy: Optional[bool] = None
|
|
100
|
+
url: Optional[str] = None
|
|
101
|
+
interval: Optional[int] = None
|
|
102
|
+
strategy: Optional[str] = None
|
|
103
|
+
interface_name: Optional[str] = Field(None, alias="interface-name")
|
|
104
|
+
routing_mark: Optional[int] = Field(None, alias="routing-mark")
|
|
105
|
+
use: Optional[List[str]] = None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ProxyProvider(BaseModel):
|
|
109
|
+
type: ProxyProviderTypeEnum
|
|
110
|
+
url: Optional[str] = None
|
|
111
|
+
interval: Optional[int] = None
|
|
112
|
+
path: str
|
|
113
|
+
health_check: Optional[dict] = Field(None, alias="health-check")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Tunnel(BaseModel):
|
|
117
|
+
network: List[NetworkEnum]
|
|
118
|
+
address: str
|
|
119
|
+
target: str
|
|
120
|
+
proxy: str
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class Rule(BaseModel):
|
|
124
|
+
type: RuleTypeEnum
|
|
125
|
+
value: str
|
|
126
|
+
proxy: str
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class DNS(BaseModel):
|
|
130
|
+
enable: bool
|
|
131
|
+
listen: str = None
|
|
132
|
+
default_nameserver: List[str] = Field(None, alias="default-nameserver")
|
|
133
|
+
fake_ip_range: str = Field(None, alias="fake-ip-range")
|
|
134
|
+
nameserver: List[str] = None
|
|
135
|
+
fallback: Optional[List[str]] = None
|
|
136
|
+
fallback_filter: Optional[dict] = Field(None, alias="fallback-filter")
|
|
137
|
+
nameserver_policy: Optional[dict] = Field(None, alias="nameserver-policy")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class ClashConfig(BaseModel):
|
|
141
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
142
|
+
|
|
143
|
+
port: int = 7890
|
|
144
|
+
socks_port: int = Field(None, alias="socks-port")
|
|
145
|
+
redir_port: Optional[int] = Field(None, alias="redir-port")
|
|
146
|
+
tproxy_port: Optional[int] = Field(None, alias="tproxy-port")
|
|
147
|
+
mixed_port: Optional[int] = Field(None, alias="mixed-port")
|
|
148
|
+
authentication: Optional[List[str]] = None
|
|
149
|
+
allow_lan: Optional[bool] = Field(None, alias="allow-lan")
|
|
150
|
+
bind_address: Optional[str] = Field(None, alias="bind-address")
|
|
151
|
+
mode: ModeEnum = ModeEnum.RULE
|
|
152
|
+
log_level: Optional[LogLevelEnum] = Field(None, alias="log-level")
|
|
153
|
+
ipv6: Optional[bool] = None
|
|
154
|
+
external_controller: str = Field(None, alias="external-controller")
|
|
155
|
+
external_ui: Optional[str] = Field(None, alias="external-ui")
|
|
156
|
+
secret: Optional[str] = None
|
|
157
|
+
interface_name: Optional[str] = Field(None, alias="interface-name")
|
|
158
|
+
routing_mark: Optional[int] = Field(None, alias="routing-mark")
|
|
159
|
+
hosts: Optional[dict] = None
|
|
160
|
+
profile: Optional[dict] = None
|
|
161
|
+
dns: Optional[DNS] = None
|
|
162
|
+
proxies: List[Proxy] = None
|
|
163
|
+
proxy_groups: List[ProxyGroup] = Field(None, alias="proxy-groups")
|
|
164
|
+
proxy_providers: Optional[List[ProxyProvider]] = Field(
|
|
165
|
+
None, alias="proxy-providers"
|
|
166
|
+
)
|
|
167
|
+
tunnels: Optional[List[Tunnel]] = None
|
|
168
|
+
rules: List[Rule] = None
|