pymecli 0.4.1__tar.gz → 0.4.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.4.1 → pymecli-0.4.3}/PKG-INFO +3 -1
  2. {pymecli-0.4.1 → pymecli-0.4.3}/README.md +2 -0
  3. pymecli-0.4.3/api/v1/clash.py +62 -0
  4. {pymecli-0.4.1 → pymecli-0.4.3}/core/clash.py +217 -10
  5. {pymecli-0.4.1 → pymecli-0.4.3}/data/template.yaml +16 -21
  6. {pymecli-0.4.1 → pymecli-0.4.3}/pyproject.toml +1 -1
  7. pymecli-0.4.1/api/v1/clash.py +0 -34
  8. {pymecli-0.4.1 → pymecli-0.4.3}/.gitignore +0 -0
  9. {pymecli-0.4.1 → pymecli-0.4.3}/api/__init__.py +0 -0
  10. {pymecli-0.4.1 → pymecli-0.4.3}/api/v1/__init__.py +0 -0
  11. {pymecli-0.4.1 → pymecli-0.4.3}/api/v1/redis.py +0 -0
  12. {pymecli-0.4.1 → pymecli-0.4.3}/cli/__init__.py +0 -0
  13. {pymecli-0.4.1 → pymecli-0.4.3}/cli/baidu.py +0 -0
  14. {pymecli-0.4.1 → pymecli-0.4.3}/cli/bitget.py +0 -0
  15. {pymecli-0.4.1 → pymecli-0.4.3}/cli/example.py +0 -0
  16. {pymecli-0.4.1 → pymecli-0.4.3}/cli/fast.py +0 -0
  17. {pymecli-0.4.1 → pymecli-0.4.3}/cli/gate.py +0 -0
  18. {pymecli-0.4.1 → pymecli-0.4.3}/cli/redis_csv.py +0 -0
  19. {pymecli-0.4.1 → pymecli-0.4.3}/cli/util.py +0 -0
  20. {pymecli-0.4.1 → pymecli-0.4.3}/core/__init__.py +0 -0
  21. {pymecli-0.4.1 → pymecli-0.4.3}/core/config.py +0 -0
  22. {pymecli-0.4.1 → pymecli-0.4.3}/core/redis_client.py +0 -0
  23. {pymecli-0.4.1 → pymecli-0.4.3}/crypto/__init__.py +0 -0
  24. {pymecli-0.4.1 → pymecli-0.4.3}/crypto/bitget.py +0 -0
  25. {pymecli-0.4.1 → pymecli-0.4.3}/crypto/gate.py +0 -0
  26. {pymecli-0.4.1 → pymecli-0.4.3}/data/__init__.py +0 -0
  27. {pymecli-0.4.1 → pymecli-0.4.3}/data/dou_dict.py +0 -0
  28. {pymecli-0.4.1 → pymecli-0.4.3}/data/dou_list.py +0 -0
  29. {pymecli-0.4.1 → pymecli-0.4.3}/data/main.py +0 -0
  30. {pymecli-0.4.1 → pymecli-0.4.3}/models/__init__.py +0 -0
  31. {pymecli-0.4.1 → pymecli-0.4.3}/models/douzero_model.py +0 -0
  32. {pymecli-0.4.1 → pymecli-0.4.3}/models/ocr_model.py +0 -0
  33. {pymecli-0.4.1 → pymecli-0.4.3}/models/response.py +0 -0
  34. {pymecli-0.4.1 → pymecli-0.4.3}/utils/__init__.py +0 -0
  35. {pymecli-0.4.1 → pymecli-0.4.3}/utils/elapsed.py +0 -0
  36. {pymecli-0.4.1 → pymecli-0.4.3}/utils/helper.py +0 -0
  37. {pymecli-0.4.1 → pymecli-0.4.3}/utils/logger.py +0 -0
  38. {pymecli-0.4.1 → pymecli-0.4.3}/utils/mysql.py +0 -0
  39. {pymecli-0.4.1 → pymecli-0.4.3}/utils/path.py +0 -0
  40. {pymecli-0.4.1 → pymecli-0.4.3}/utils/pd.py +0 -0
  41. {pymecli-0.4.1 → pymecli-0.4.3}/utils/pyredis.py +0 -0
  42. {pymecli-0.4.1 → pymecli-0.4.3}/utils/sleep.py +0 -0
  43. {pymecli-0.4.1 → pymecli-0.4.3}/utils/text.py +0 -0
  44. {pymecli-0.4.1 → pymecli-0.4.3}/utils/toml.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymecli
3
- Version: 0.4.1
3
+ Version: 0.4.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
@@ -43,4 +43,6 @@ uv run example goodbye Xiaoming -f
43
43
 
44
44
  ```shell
45
45
  uv run fast --port 8877
46
+ uv build
47
+ uv publish --token (TOKEN)
46
48
  ```
@@ -11,4 +11,6 @@ uv run example goodbye Xiaoming -f
11
11
 
12
12
  ```shell
13
13
  uv run fast --port 8877
14
+ uv build
15
+ uv publish --token (TOKEN)
14
16
  ```
@@ -0,0 +1,62 @@
1
+ import yaml
2
+ from fastapi import APIRouter, Query, Response
3
+
4
+ from core.clash import ClashYamlGenerator, get_generator_dependency
5
+
6
+ router = APIRouter()
7
+
8
+
9
+ @router.get(
10
+ "/subwhite",
11
+ summary="转换订阅(可以转换多个订阅)",
12
+ description="根据提供的URL、User-Agent和名称获取并处理订阅信息,返回YAML格式的Clash配置",
13
+ response_description="返回YAML格式的Clash配置文件",
14
+ )
15
+ async def subWhite(
16
+ urls: str = Query(..., description="订阅URL,逗号分隔"),
17
+ agents: str = Query(
18
+ "clash-verge/v2.4.3",
19
+ description="User-Agent,逗号分隔(根据客户段选择)",
20
+ ),
21
+ names: str = Query(
22
+ "订阅1", description="订阅名称,逗号分隔(可选,在客户端中的显示名)"
23
+ ),
24
+ generator: ClashYamlGenerator = get_generator_dependency(),
25
+ ):
26
+ # 将JSON字符串解析为Python对象
27
+ sub_list = generator.query2sub(urls, agents, names)
28
+ yaml_content, userinfo = generator.genW(sub_list)
29
+ yaml_string = yaml.dump(yaml_content, allow_unicode=True, default_flow_style=False)
30
+ return Response(
31
+ headers={"Subscription-Userinfo": userinfo},
32
+ content=yaml_string,
33
+ media_type="text/yaml",
34
+ )
35
+
36
+
37
+ @router.get(
38
+ "/subblack",
39
+ summary="转换订阅(可以转换多个订阅)",
40
+ description="根据提供的URL、User-Agent和名称获取并处理订阅信息,返回YAML格式的Clash配置",
41
+ response_description="返回YAML格式的Clash配置文件",
42
+ )
43
+ async def subBlack(
44
+ urls: str = Query(..., description="订阅URL,逗号分隔"),
45
+ agents: str = Query(
46
+ "clash-verge/v2.4.3",
47
+ description="User-Agent,逗号分隔(根据客户段选择)",
48
+ ),
49
+ names: str = Query(
50
+ "订阅1", description="订阅名称,逗号分隔(可选,在客户端中的显示名)"
51
+ ),
52
+ generator: ClashYamlGenerator = get_generator_dependency(),
53
+ ):
54
+ # 将JSON字符串解析为Python对象
55
+ sub_list = generator.query2sub(urls, agents, names)
56
+ yaml_content, userinfo = generator.genB(sub_list)
57
+ yaml_string = yaml.dump(yaml_content, allow_unicode=True, default_flow_style=False)
58
+ return Response(
59
+ headers={"Subscription-Userinfo": userinfo},
60
+ content=yaml_string,
61
+ media_type="text/yaml",
62
+ )
@@ -21,7 +21,8 @@ class ClashYamlGenerator:
21
21
  self.my_rule_base_url = config.my_rule_base_url
22
22
  self.request_proxy = config.request_proxy
23
23
 
24
- def gen(self, sub_list: list[dict]):
24
+ # 白名单模式 Rules 配置方式
25
+ def genW(self, sub_list: list[dict]):
25
26
  proxies = None
26
27
  if self.request_proxy:
27
28
  proxies = {
@@ -57,7 +58,7 @@ class ClashYamlGenerator:
57
58
  {
58
59
  "name": "轮询",
59
60
  "type": "load-balance",
60
- "url": "https://www.gstatic.com/generate_204",
61
+ "url": "https://api.bitget.com/api/v2/public/time",
61
62
  "interval": 300,
62
63
  "lazy": True,
63
64
  "strategy": "round-robin",
@@ -102,6 +103,13 @@ class ClashYamlGenerator:
102
103
  },
103
104
  )
104
105
 
106
+ template["rules"].append("IP-CIDR,192.168.0.0/16,DIRECT,no-resolve")
107
+ template["rules"].append("IP-CIDR,10.0.0.0/8,DIRECT,no-resolve")
108
+ template["rules"].append("IP-CIDR,172.16.0.0/12,DIRECT,no-resolve")
109
+ template["rules"].append("IP-CIDR,127.0.0.0/8,DIRECT,no-resolve")
110
+
111
+ template["rules"].append("DOMAIN,clash.razord.top,DIRECT")
112
+ template["rules"].append("DOMAIN,yacd.haishan.me,DIRECT")
105
113
  # 获取rules
106
114
  rule_list = [
107
115
  [f"{self.my_rule_base_url}/direct.yaml", "DIRECT"],
@@ -111,8 +119,8 @@ class ClashYamlGenerator:
111
119
  "轮询",
112
120
  ],
113
121
  [
114
- f"{self.my_rule_base_url}/xbox.yaml",
115
- "全局选择",
122
+ f"{self.my_rule_base_url}/reject.yaml",
123
+ "REJECT",
116
124
  ],
117
125
  ]
118
126
 
@@ -210,31 +218,230 @@ class ClashYamlGenerator:
210
218
  "path": "./ruleset/telegramcidr.yaml",
211
219
  "interval": 86400,
212
220
  },
221
+ # "reject": {
222
+ # "type": "http",
223
+ # "format": "yaml",
224
+ # "behavior": "domain",
225
+ # "url": f"{self.rule_base_url}/reject.txt",
226
+ # "path": "./ruleset/reject.yaml",
227
+ # "interval": 86400,
228
+ # },
213
229
  },
214
230
  )
215
231
 
216
232
  template["rules"].extend(
217
233
  [
218
234
  "RULE-SET,applications,DIRECT",
219
- "DOMAIN,clash.razord.top,DIRECT",
220
- "DOMAIN,yacd.haishan.me,DIRECT",
221
235
  "RULE-SET,private,DIRECT",
236
+ # "RULE-SET,reject,REJECT",
222
237
  "RULE-SET,icloud,DIRECT",
223
238
  "RULE-SET,apple,DIRECT",
224
239
  "RULE-SET,google,全局选择",
225
240
  "RULE-SET,proxy,全局选择",
226
241
  "RULE-SET,direct,DIRECT",
227
- "RULE-SET,lancidr,DIRECT,no-resolve",
228
- "RULE-SET,cncidr,DIRECT,no-resolve",
242
+ "RULE-SET,lancidr,DIRECT",
243
+ "RULE-SET,cncidr,DIRECT",
229
244
  "RULE-SET,telegramcidr,全局选择",
230
- "GEOIP,LAN,DIRECT,no-resolve",
231
- "GEOIP,CN,DIRECT,no-resolve",
245
+ "GEOIP,LAN,DIRECT",
246
+ "GEOIP,CN,DIRECT",
232
247
  "MATCH,全局选择",
233
248
  ]
234
249
  )
235
250
 
236
251
  return template, userinfo
237
252
 
253
+ # 黑名单模式 Rules 配置方式
254
+ def genB(self, sub_list: list[dict]):
255
+ proxies = None
256
+ if self.request_proxy:
257
+ proxies = {
258
+ "http": self.request_proxy,
259
+ "https": self.request_proxy,
260
+ }
261
+
262
+ with open(str(module_dir / "data/template.yaml"), "r", encoding="utf-8") as f:
263
+ template = yaml.safe_load(f)
264
+
265
+ template["proxy-groups"].extend(
266
+ [
267
+ {
268
+ "name": "全局选择",
269
+ "type": "select",
270
+ "proxies": ["自动选择", "手动选择", "轮询"]
271
+ + [item["name"] for item in sub_list],
272
+ },
273
+ {
274
+ "name": "自动选择",
275
+ "type": "url-test",
276
+ "url": "https://www.gstatic.com/generate_204",
277
+ "interval": 300,
278
+ "tolerance": 11,
279
+ "lazy": True,
280
+ "use": [f"provider.{item['name']}" for item in sub_list],
281
+ },
282
+ {
283
+ "name": "手动选择",
284
+ "type": "select",
285
+ "use": [f"provider.{item['name']}" for item in sub_list],
286
+ },
287
+ {
288
+ "name": "轮询",
289
+ "type": "load-balance",
290
+ "url": "https://api.bitget.com/api/v2/public/time",
291
+ "interval": 300,
292
+ "lazy": True,
293
+ "strategy": "round-robin",
294
+ "use": [f"provider.{item['name']}" for item in sub_list],
295
+ },
296
+ ]
297
+ )
298
+
299
+ userinfo = ""
300
+ for item in sub_list:
301
+ headers = {"User-Agent": item["user_agent"]} if item["user_agent"] else {}
302
+
303
+ if not item["url"]:
304
+ raise ValueError("Invalid subscription URL.")
305
+ response = requests.get(item["url"], headers=headers, proxies=proxies)
306
+ response.raise_for_status()
307
+ if not userinfo:
308
+ userinfo = response.headers["Subscription-Userinfo"]
309
+ remote_config = yaml.safe_load(response.text)
310
+
311
+ # 检查 remote_config 是否为 None
312
+ if remote_config is None:
313
+ raise ValueError("Invalid subscription content: empty or invalid YAML.")
314
+
315
+ ps = remote_config.get("proxies", [])
316
+ if not ps:
317
+ raise ValueError("No proxies found in subscription.")
318
+
319
+ template["proxy-providers"][f"provider.{item['name']}"] = {
320
+ "type": "inline",
321
+ "payload": ps,
322
+ }
323
+
324
+ template["proxy-groups"].append(
325
+ {
326
+ "name": item["name"],
327
+ "type": "url-test",
328
+ "url": "https://www.gstatic.com/generate_204",
329
+ "interval": 300,
330
+ "tolerance": 11,
331
+ "lazy": True,
332
+ "use": [f"provider.{item['name']}"],
333
+ },
334
+ )
335
+
336
+ template["rules"].append("IP-CIDR,192.168.0.0/16,DIRECT,no-resolve")
337
+ template["rules"].append("IP-CIDR,10.0.0.0/8,DIRECT,no-resolve")
338
+ template["rules"].append("IP-CIDR,172.16.0.0/12,DIRECT,no-resolve")
339
+ template["rules"].append("IP-CIDR,127.0.0.0/8,DIRECT,no-resolve")
340
+ # ⬇️饥荒服务器相关端口
341
+ template["rules"].append("DST-PORT,10999,DIRECT")
342
+ template["rules"].append("DST-PORT,10998,DIRECT")
343
+ template["rules"].append("DST-PORT,27016,DIRECT")
344
+ template["rules"].append("DST-PORT,27017,DIRECT")
345
+ template["rules"].append("DST-PORT,8766,DIRECT")
346
+ template["rules"].append("DST-PORT,8767,DIRECT")
347
+
348
+ template["rules"].append("DOMAIN,clash.razord.top,DIRECT")
349
+ template["rules"].append("DOMAIN,yacd.haishan.me,DIRECT")
350
+ # template["rules"].append("DOMAIN,accounts.klei.com,全局选择")
351
+
352
+ # 获取rules
353
+ rule_list = [
354
+ [f"{self.my_rule_base_url}/direct.yaml", "DIRECT"],
355
+ [f"{self.my_rule_base_url}/proxy.yaml", "全局选择"],
356
+ [
357
+ f"{self.my_rule_base_url}/round.yaml",
358
+ "轮询",
359
+ ],
360
+ [
361
+ f"{self.my_rule_base_url}/reject.yaml",
362
+ "REJECT",
363
+ ],
364
+ ]
365
+
366
+ for item in rule_list:
367
+ response = requests.get(item[0], proxies=proxies)
368
+ response.raise_for_status()
369
+ remote = yaml.safe_load(response.text)
370
+ template["rule-providers"][os.path.basename(item[0])] = {
371
+ "type": "inline",
372
+ "behavior": "classical",
373
+ "payload": remote["payload"],
374
+ }
375
+
376
+ template["rules"].append(f"RULE-SET,{os.path.basename(item[0])},{item[1]}")
377
+
378
+ template["rule-providers"].update(
379
+ {
380
+ "applications": {
381
+ "type": "http",
382
+ "format": "yaml",
383
+ "behavior": "classical",
384
+ "url": f"{self.rule_base_url}/applications.txt",
385
+ "path": "./ruleset/applications.yaml",
386
+ "interval": 86400,
387
+ },
388
+ "private": {
389
+ "type": "http",
390
+ "format": "yaml",
391
+ "behavior": "domain",
392
+ "url": f"{self.rule_base_url}/private.txt",
393
+ "path": "./ruleset/private.yaml",
394
+ "interval": 86400,
395
+ },
396
+ "tld-not-cn": {
397
+ "type": "http",
398
+ "format": "yaml",
399
+ "behavior": "domain",
400
+ "url": f"{self.rule_base_url}/tld-not-cn.txt",
401
+ "path": "./ruleset/tld-not-cn.yaml",
402
+ "interval": 86400,
403
+ },
404
+ "telegramcidr": {
405
+ "type": "http",
406
+ "format": "yaml",
407
+ "behavior": "ipcidr",
408
+ "url": f"{self.rule_base_url}/telegramcidr.txt",
409
+ "path": "./ruleset/telegramcidr.yaml",
410
+ "interval": 86400,
411
+ },
412
+ "gfw": {
413
+ "type": "http",
414
+ "format": "yaml",
415
+ "behavior": "domain",
416
+ "url": f"{self.rule_base_url}/gfw.txt",
417
+ "path": "./ruleset/gfw.yaml",
418
+ "interval": 86400,
419
+ },
420
+ # "reject": {
421
+ # "type": "http",
422
+ # "format": "yaml",
423
+ # "behavior": "domain",
424
+ # "url": f"{self.rule_base_url}/reject.txt",
425
+ # "path": "./ruleset/reject.yaml",
426
+ # "interval": 86400,
427
+ # },
428
+ },
429
+ )
430
+
431
+ template["rules"].extend(
432
+ [
433
+ "RULE-SET,applications,DIRECT",
434
+ "RULE-SET,private,DIRECT",
435
+ # "RULE-SET,reject,REJECT",
436
+ "RULE-SET,tld-not-cn,全局选择",
437
+ "RULE-SET,gfw,全局选择",
438
+ "RULE-SET,telegramcidr,全局选择",
439
+ "MATCH,DIRECT",
440
+ ]
441
+ )
442
+
443
+ return template, userinfo
444
+
238
445
  def query2sub(self, urls: str, agents: str, names: str):
239
446
  url_list = urls.split(",") if urls and urls.strip() else []
240
447
  agents_list = agents.split(",") if agents else []
@@ -12,15 +12,15 @@ tun:
12
12
  mtu: 1500
13
13
  dns:
14
14
  enable: true
15
- listen: ":5353"
15
+ listen: ":53"
16
16
  enhanced-mode: "fake-ip"
17
17
  fake-ip-range: "198.18.0.1/16"
18
18
  fake-ip-filter-mode: "blacklist"
19
19
  prefer-h3: false
20
- respect-rules: false # 是否让 DNS 解析遵循路由规则
21
- use-hosts: true # 启用本地 hosts
22
- use-system-hosts: true # 启用系统 hosts
23
- ipv6: false
20
+ respect-rules: false
21
+ use-hosts: false
22
+ use-system-hosts: false
23
+ ipv6: true
24
24
  fake-ip-filter:
25
25
  - "*.lan"
26
26
  - "*.local"
@@ -32,22 +32,17 @@ dns:
32
32
  - "localhost.ptlogin2.qq.com"
33
33
  - "*.msftncsi.com"
34
34
  - "www.msftconnecttest.com"
35
- - "ipv6.shuzilm.cn"
36
- - "+.qq.com"
37
- - "+.tgp.qq.com"
38
- - "+.klei.com"
39
- - "+.steamserver.net"
40
- - "dontstarve_rail_x64.exe"
41
- default-nameserver: # 默认 DNS 服务器
42
- - "223.5.5.5" # 阿里云 DNS(国内)
43
- - "8.8.8.8" # Google DNS(国外)
44
- - "2400:3200::1" # 阿里云 IPv6 DNS
45
- - "2001:4860:4860::8888" # Google IPv6 DNS
35
+ default-nameserver:
36
+ - "system"
37
+ - "223.6.6.6"
38
+ - "8.8.8.8"
39
+ - "2400:3200::1"
40
+ - "2001:4860:4860::8888"
46
41
  nameserver:
47
- - "8.8.8.8" # Google DNS(UDP 协议)
48
- - "https://doh.pub/dns-query" # 公共 DoH DNS(加密解析,防污染)
49
- - "https://dns.alidns.com/dns-query" # 阿里云 DoH DNS(国内稳定)
50
- fallback: []
42
+ - "8.8.8.8"
43
+ - "https://doh.pub/dns-query"
44
+ - "https://dns.alidns.com/dns-query"
45
+ direct-nameserver-follow-policy: false
51
46
  fallback-filter:
52
47
  geoip: true
53
48
  geoip-code: "CN"
@@ -58,11 +53,11 @@ dns:
58
53
  - "+.google.com"
59
54
  - "+.facebook.com"
60
55
  - "+.youtube.com"
56
+ fallback: []
61
57
  proxy-server-nameserver:
62
58
  - "https://doh.pub/dns-query"
63
59
  - "https://dns.alidns.com/dns-query"
64
60
  - "tls://223.5.5.5"
65
- direct-nameserver-follow-policy: false # 直连域名的 DNS 不遵循代理规则
66
61
  direct-nameserver: []
67
62
  external-controller-cors:
68
63
  allow-private-network: true
@@ -30,7 +30,7 @@
30
30
  name = "pymecli"
31
31
  readme = "README.md"
32
32
  requires-python = ">=3.10"
33
- version = "0.4.1"
33
+ version = "0.4.3"
34
34
 
35
35
  [project.urls]
36
36
  Homepage = "https://pypi.org/project/pymecli"
@@ -1,34 +0,0 @@
1
- import yaml
2
- from fastapi import APIRouter, Query, Response
3
-
4
- from core.clash import ClashYamlGenerator, get_generator_dependency
5
-
6
- router = APIRouter()
7
-
8
-
9
- @router.get(
10
- "/sub",
11
- summary="转换订阅(可以转换多个订阅)",
12
- description="根据提供的URL、User-Agent和名称获取并处理订阅信息,返回YAML格式的Clash配置",
13
- response_description="返回YAML格式的Clash配置文件",
14
- )
15
- async def sub(
16
- urls: str = Query(..., description="订阅URL,逗号分隔"),
17
- agents: str = Query(
18
- "clash-verge/v2.4.3",
19
- description="User-Agent,逗号分隔(根据客户段选择)",
20
- ),
21
- names: str = Query(
22
- "订阅1", description="订阅名称,逗号分隔(可选,在客户端中的显示名)"
23
- ),
24
- generator: ClashYamlGenerator = get_generator_dependency(),
25
- ):
26
- # 将JSON字符串解析为Python对象
27
- sub_list = generator.query2sub(urls, agents, names)
28
- yaml_content, userinfo = generator.gen(sub_list)
29
- yaml_string = yaml.dump(yaml_content, allow_unicode=True, default_flow_style=False)
30
- return Response(
31
- headers={"Subscription-Userinfo": userinfo},
32
- content=yaml_string,
33
- media_type="text/yaml",
34
- )
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