parser.py
@@ -0,0 +1,163 @@
+import base64
+import re
+
+import yaml
+from http.server import BaseHTTPRequestHandler, HTTPServer
+import requests
+
+
+class YAMLModifierHTTPRequestHandler(BaseHTTPRequestHandler):
+ def do_GET(self):
+ # 解析查询参数
+ query_components = self.parse_query_parameters(self.path)
+
+ # 提取并解码 URL
+ source_url = base64.b64decode(query_components.get('source', [''])[0]).decode()
+ parser_url = base64.b64decode(query_components.get('parser', [''])[0]).decode()
+
+ # 获取并解析 YAML 文件
+ source_yaml = self.fetch_yaml(source_url)
+ parser_yaml = self.fetch_yaml(parser_url)
+
+ if source_yaml and parser_yaml:
+ # 使用 parser_yaml 中的规则修改 source_yaml
+ modified_yaml = self.modify_yaml(source_yaml, parser_yaml)
+
+ # 发送响应
+ self.send_response(200)
+ self.send_header('Content-type', 'text/yaml')
+ self.end_headers()
+ self.wfile.write(
+ yaml.dump(modified_yaml, allow_unicode=True, default_flow_style=False, sort_keys=False).encode())
+ else:
+ # 发送错误响应
+ self.send_error(404, "Unable to fetch or parse YAML files")
+
+ @staticmethod
+ def parse_query_parameters(path):
+ # 解析 URL 中的查询参数
+ from urllib.parse import urlparse, parse_qs
+ query = urlparse(path).query
+ return parse_qs(query)
+
+ @staticmethod
+ def fetch_yaml(url):
+ # 从给定的 URL 获取并解析 YAML
+ try:
+ response = requests.get(url)
+ response.raise_for_status()
+ return yaml.safe_load(response.content)
+ except Exception as e:
+ print(f"Error fetching YAML from {url}: {e}")
+ return None
+
+ def modify_yaml(self, source_yaml, parser_yaml):
+ # 根据 parser_yaml 中的规则修改 source_yaml
+ for key, operations in parser_yaml.get('yaml', {}).items():
+ if key.startswith('append-') or key.startswith('prepend-'):
+ self.modify_list(source_yaml, key, operations)
+ elif key.startswith('mix-'):
+ self.mix_object(source_yaml, key, operations)
+
+ # 执行 commands
+ for command in parser_yaml.get('yaml', {}).get('commands', []):
+ self.apply_command(source_yaml, command)
+
+ return source_yaml
+
+ def modify_list(self, source_yaml, key, operations):
+ # 修改 source_yaml 中的列表
+ list_name = key.split('-', 1)[1]
+ source_list = source_yaml.get(list_name, [])
+
+ # Prepend or append operations
+ # 根据操作类型(附加 Prepend 或前置 append)进行修改
+ if key.startswith('append-'):
+ source_list.extend(operations)
+ elif key.startswith('prepend-'):
+ source_list = operations + source_list
+
+ source_yaml[list_name] = source_list
+
+ def mix_object(self, source_yaml, key, operations):
+ # 确定 source_yaml 中的目标对象
+ object_name = key.split('-', 1)[1]
+ source_object = source_yaml.get(object_name, {})
+
+ # 合并对象
+ source_object.update(operations)
+ source_yaml[object_name] = source_object
+
+ def apply_command(self, source_yaml, command):
+ path, operation, value = self.parse_command(command)
+
+ # 查找并处理目标策略组
+ target_group = self.find_proxy_group(source_yaml, path)
+ if target_group is None:
+ return # 如果没有找到目标策略组,则跳过此命令
+
+ if value.startswith('[]'):
+ regex_pattern = value[2:]
+ self.apply_regex_to_proxies(source_yaml, target_group, regex_pattern)
+
+ if operation == '+':
+ # 插入代理到特定位置
+ index = int(path[2]) if path[2].isdigit() else None
+ if index is not None and index < len(target_group['proxies']):
+ target_group['proxies'].insert(index, value)
+ else:
+ target_group['proxies'].append(value) # 如果索引无效,则默认添加到末尾
+
+ def find_proxy_group(self, source_yaml, path):
+ # 检查路径长度是否足够
+ if len(path) < 2:
+ return None
+ # 查找指定的策略组
+ group_name = path[1]
+ for group in source_yaml.get('proxy-groups', []):
+ if group['name'] == group_name:
+ return group
+ return None
+
+ def apply_regex_to_proxies(self, source_yaml, target_group, regex_pattern):
+ # 使用正则表达式筛选代理
+ regex = re.compile(regex_pattern)
+ matched_proxies = [proxy['name'] for proxy in source_yaml.get('proxies', []) if regex.search(proxy['name'])]
+
+ # 将匹配的代理添加到目标策略组
+ target_group['proxies'].extend(matched_proxies)
+
+ def parse_command(self, command):
+ # 首先尝试以 '+' 为分隔符分割命令
+ if '+' in command:
+ parts = command.split('+', 1)
+ operation = '+'
+ # 如果没有 '+', 则尝试以 '=' 为分隔符
+ elif '=' in command:
+ parts = command.split('=', 1)
+ operation = '='
+ else:
+ raise ValueError("Invalid command format")
+
+ if len(parts) != 2:
+ raise ValueError("Invalid command format")
+
+ path = parts[0].split('.')
+ value = parts[1]
+
+ return path, operation, value
+
+ def log_message(self, format, *args):
+ # 覆盖以避免记录每个请求
+ pass
+
+
+def run_server(server_class=HTTPServer, handler_class=YAMLModifierHTTPRequestHandler, port=8000):
+ server_address = ('', port)
+ httpd = server_class(server_address, handler_class)
+ print(f"Starting HTTP server on port {port}")
+ httpd.serve_forever()
+
+
+if __name__ == '__main__':
+ run_server()
\ No newline at end of file
readme.txt
@@ -0,0 +1,49 @@
+请求:GET http://localhost:8000/?source={base64过的URL}&parser={base64过的URL}
+例如:GET http://localhost:8000/?source=aHR0cHM6Ly9zdWIuaWQ5LmNjL3N1Yj90YXJnZXQ9Y2xhc2gmbmV3X25hbWU9dHJ1ZSZ1cmw9dGVzdCZpbnNlcnQ9ZmFsc2UmY29uZmlnPWh0dHBzJTNBJTJGJTJGcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSUyRkFDTDRTU1IlMkZBQ0w0U1NSJTJGbWFzdGVyJTJGQ2xhc2glMkZjb25maWclMkZBQ0w0U1NSX09ubGluZS5pbmk=&parser={base64过的URL}
+
+source 传入需要修改的 Clash yaml 如订阅转换后的地址,例如:
+aHR0cHM6Ly9zdWIuaWQ5LmNjL3N1Yj90YXJnZXQ9Y2xhc2gmbmV3X25hbWU9dHJ1ZSZ1cmw9dGVzdCZpbnNlcnQ9ZmFsc2UmY29uZmlnPWh0dHBzJTNBJTJGJTJGcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSUyRkFDTDRTU1IlMkZBQ0w0U1NSJTJGbWFzdGVyJTJGQ2xhc2glMkZjb25maWclMkZBQ0w0U1NSX09ubGluZS5pbmk=
+
+
+parser 传入 CFW 的 yaml 版规则预处理 yaml
+说明:https://docs.gtk.pw/contents/parser.html#%E7%89%88%E6%9C%AC%E8%A6%81%E6%B1%82
+例如:aHR0cHM6Ly9leGFtcGxlLmNvbS9jZndfcGFyc2VyLnlhbWw=
+
+Command 功能支持不完全,大概只支持下面例子里的用法。
+
+
+parser 需要以 yaml 开头,例如:
+```
+yaml:
+ commands:
+ # 向 策略组 内添加节点
+ - proxy-groups.🇪🇺 欧盟节点.proxies=[]proxyNames|^(?=.*(?:奥地利|Austria|🇦🇹|比利时|Belgium|🇧🇪|保加利亚|Bulgaria|🇧🇬|克罗地亚|Croatia|🇭🇷|塞浦路斯|Cyprus|🇨🇾|捷克共和国|Czech Republic|🇨🇿|丹麦|Denmark|🇩🇰|爱沙尼亚|Estonia|🇪🇪|芬兰|Finland|🇫🇮|法国|France|🇫🇷|德国|Germany|🇩🇪|希腊|Greece|🇬🇷|匈牙利|Hungary|🇭🇺|爱尔兰|Ireland|🇮🇪|意大利|Italy|🇮🇹|拉脱维亚|Latvia|🇱🇻|立陶宛|Lithuania|🇱🇹|卢森堡|Luxembourg|🇱🇺|马耳他|Malta|🇲🇹|荷兰|Netherlands|🇳🇱|波兰|Poland|🇵🇱|葡萄牙|Portugal|🇵🇹|罗马尼亚|Romania|🇷🇴|斯洛伐克|Slovakia|🇸🇰|斯洛文尼亚|Slovenia|🇸🇮|西班牙|Spain|🇪🇸|瑞典|Sweden|🇸🇪)).*$
+ - proxy-groups.🚀 节点选择.proxies.0+🚰 大流量
+ - proxy-groups.🚀 节点选择.proxies.0+🚰 大流量负载均衡
+ prepend-rules:
+ - DOMAIN-SUFFIX,s.team,🚀 节点选择
+ - DOMAIN,fanatical.com,🚀 节点选择
+ - DOMAIN,humblebundle.com,🚀 节点选择
+ - DOMAIN,playartifact.com,🚀 节点选择
+ - DOMAIN-SUFFIX,steam-chat.com,🚀 节点选择
+ - DOMAIN-SUFFIX,steamcommunity.com,🚀 节点选择
+ - DOMAIN-SUFFIX,steamgames.com,🚀 节点选择
+ - DOMAIN-SUFFIX,steamstatic.com,🚀 节点选择
+ - DOMAIN-SUFFIX,steamusercontent.com,🚀 节点选择
+ - DOMAIN-SUFFIX,underlords.com,🚀 节点选择
+ - DOMAIN-SUFFIX,valvesoftware.com,🚀 节点选择
+ - DOMAIN,api.steampowered.com,🚀 节点选择
+ - DOMAIN,store.steampowered.com,🚀 节点选择
+ - DOMAIN-SUFFIX,steamserver.net,DIRECT
+ - DOMAIN-SUFFIX,test.com,🚰 大流量
+ mix-proxy-providers: #添加自定义规则组
+ hightraffic:
+ type: http
+ path: ./Proxy/hightraffic.yaml
+ url: https://example.com/clash.yaml
+ interval: 21600
+ health-check:
+ enable: true
+ url: http://www.gstatic.com/generate_204
+ interval: 300
+```
\ No newline at end of file