Spaces:
Running
Running
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
from typing import Dict, Iterator, List, Union | |
from .resource import Resource | |
from dataclasses import dataclass | |
from urllib import parse | |
import json,re | |
from base64 import b64decode,b64encode | |
from .utils import decode_base64_with_filter,truncate_encoded_string | |
class BaseDecoder: | |
def __init__(self, encode_str: str, nameinfo: str = ""): | |
self.encode_str = encode_str | |
self.decode_str: str | |
self.nameinfo = nameinfo | |
def _bytesto_str(iobytes: bytes) -> str: | |
return iobytes.decode('utf-8') | |
def decode(self) -> None: | |
self.decode_str = self._bytesto_str(b64decode(self.encode_str)) | |
class EncodedCfg: | |
encoded_config_str: str | |
nameinfo: str = "" | |
class ListDecoder(BaseDecoder): | |
def iter_encode_config(self) -> Iterator[EncodedCfg]: | |
for config_str in self.decode_str.splitlines(): | |
_config_str = re.sub(r"(vmess|ss)://", "", config_str) | |
nameinfo = "" | |
if "#" in _config_str: | |
_config_str, nameinfo = _config_str.split("#", 1) | |
nameinfo = parse.unquote(nameinfo) | |
_encoded_config_str = _config_str + ( | |
(4 - len(_config_str) % 4) * "=" | |
) | |
if "trojan" in config_str and "0.0.0.0" not in config_str and '专线' not in nameinfo and '443' not in config_str: | |
# 使用正则表达式去除 type=... 参数,包括#前的部分 | |
# 这里我们匹配 &type= 后面跟着任意字符直到下一个 & 或 # 或字符串结束 ,去除type=tcp== 或者type=tcp= 这类影响链接的多余部分 | |
_encoded_config_str_without_type = re.sub(r'([&?]type=[^&#]*)', '',_encoded_config_str) | |
if "allowInsecure" in config_str: | |
yield _encoded_config_str_without_type.replace('allowInsecure=0', 'allowInsecure=1')+"#"+truncate_encoded_string(nameinfo) # 适当缩短name | |
else: | |
yield _encoded_config_str_without_type+"&allowInsecure=1"+"#"+truncate_encoded_string(nameinfo) # 适当缩短name | |
# vmess无法获取到nameinfo 因为在里面的ps这个key对于的value作为remark | |
if ("vmess" in config_str): | |
vmess_decoded_data = decode_base64_with_filter(_config_str) | |
vmess_info_json = json.loads(vmess_decoded_data) | |
vmess_info_str = json.dumps(vmess_info_json,ensure_ascii=False)#不转译中文 | |
if ('倍率提示'not in vmess_info_str)and('导航' not in vmess_info_str) and('443' not in vmess_info_str): | |
# 获取 ps 对应的remark值 | |
ps_value = vmess_info_json.get('ps', '') # 如果 'ps' 键不存在,默认值为空字符串 | |
# 如果 ps_value 不为空,则调用 truncate_encoded_string 函数并更新值。如果 ps_value 为空,则保持原值(空字符串)。 | |
vmess_info_json['ps'] = truncate_encoded_string(ps_value) if ps_value else ps_value | |
vmess_node_return = 'vmess://'+b64encode(json.dumps(vmess_info_json,ensure_ascii=False).encode("utf-8")).decode("utf-8") | |
yield vmess_node_return | |
# 不能简单判断ss:// 因为vmess://也包含ss:// | |
if (config_str.startswith("ss://")) and ("套餐" not in nameinfo) and('到期' not in nameinfo) and('流量' not in nameinfo) and('剩余' not in nameinfo) and ('专线' not in nameinfo): | |
#shadowsocks节点保留 | |
yield 'ss://'+_config_str+'#'+truncate_encoded_string(nameinfo) | |
class ConfigDecoder(BaseDecoder): | |
def get_json(self) -> Dict: | |
if re.match(r"^{.*}$", self.decode_str): | |
return json.loads(self.decode_str) | |
return self._parse_ss() | |
def _parse_ss(self): | |
crypt, pw_at_addr, port = self.decode_str.split(":") | |
pw, addr = pw_at_addr.split("@") | |
return { | |
"ps": self.nameinfo, | |
"add": addr, | |
"port": int(port), | |
"method": crypt, | |
"password": pw, | |
"ss": True, | |
"level": 0, | |
} | |
def _get_resource_from_url(url: str) -> str: | |
resource = Resource(url) | |
return resource.get_encoded_data() | |
def _get_listencoded_cfg_from_encoded_str(encoded_str: str) -> List[EncodedCfg]: | |
decoder = ListDecoder(encoded_str) | |
decoder.decode() | |
return [item for item in decoder.iter_encode_config()] | |
def decode_url_to_configs(url: str)->list: | |
encoded_str = _get_resource_from_url(url) | |
lst_encoded_cfg = _get_listencoded_cfg_from_encoded_str(encoded_str)#lst_encoded_cfg解析为节点后的列表,包含所有节点链接 | |
return lst_encoded_cfg |