个人订阅转换

个人订阅转换

由于主力机是linux, 没有用windows, 所以没法使用 v-2-r-a-y-N 转换订阅,

所以写了个脚本, 获取对应的服务器相关参数, 方便本地脚本一键配置

这个脚本主要做了3件事

1. 从订阅网站 请求数据 (返回内容是 base64 格式)

   fetch_subscribe_content(url)

2. 使用 base64 解码 订阅内容, 拆分成多条服务器链接
   decode_subscribe_content(content)

3. 根据协议规则,解析链接中的字段信息
   decode_protocol_links(links)

   这里要注意,要根据字符串开头匹配不同协议, 因为他们的解析方式不同

   protocolA 的 body 是 base64编码,直接解码即可,解码后的内容是 key=value 类型的数据

  protocolB 的 body 是 base64 + 明文 + URL 编码,需要先拆分,再分别解码

使用方法:

# 安装一下需要的库 (防止运行出错)
$ pip install requests

# 转换订阅信息为 多条 ~/.cache/serverxx.conf 信息
$ python3 ./subconvert.py -s https://sub.xxxxxx/api/v1/client/subscribe?token=xxxxxxxxxxxxxxx -o ~/.cache

# 生成的配置举例
$ cat ~/.cache/server01.conf
{
    "uuid": "xxx-xxx-xxx-xxx....",
    "port": "xxx",
    "addr": "xxx.com",
    "protocol": "xxxx",
    "note": "香港G1"
}

附上 python 代码实现 (部分字符要用16进制表示,嘿嘿🙍,懂得都懂,如果看不懂,可以复制到chatgpt 翻译一下)

#!/usr/bin/env python3
# Author: Zack4396(zokkkki)
# Date: 2024-04-22
# Description: This is a Python script for subscribe convert


def base64_decode(data):
    import base64

    return base64.urlsafe_b64decode(data + "=" * (4 - len(data) % 4)).decode("utf-8")


def is_base64(s):
    try:
        base64_decode(s)
        return True
    except:
        return False


class protocol(object):
    def __init__(self, name, link) -> None:
        self._name = name
        self._link = link
        pass

    def decode(self):
        raise NotImplementedError("decode() method must be implemented")


class protocolA:
    # S-S-:-/-/
    name = b"\x73\x73\x3a\x2f\x2f".decode()
    # S-H-A-D-O-W-S-O-C-K-S
    protocol = b"\x73\x68\x61\x64\x6f\x77\x73\x6f\x63\x6b\x73".decode()

    def decode(self, link):
        import urllib.parse

        body = link.replace(self.name, "").replace("\r", "")
        header, footer = body.split("#")

        if is_base64(header):
            import re

            """
                 [base64]
                    |
                  (dec)
                    |
                    v
                 [method]:[uuid]@[addr]:[port]
            """
            method, uuid, addr, port = re.split(r"[:@]", base64_decode(header))
            note = footer
        else:
            import re

            """
                 [base64]@[addr]:[port]
                    |
                  (dec)
                    |
                    v
                 [method]:[uuid]
            """
            method_uuid, addr, port = re.split(r"[:@]", header)
            method, uuid = base64_decode(method_uuid).split(":")
            note = urllib.parse.unquote(footer.split("%20")[1])
        return {
            "uuid": uuid,
            "port": port,
            "addr": addr,
            "protocol": self.protocol,
            "method": method,
            "note": note,
        }


class protocolB:
    # V-M-E-S-S-:-/-/
    name = b"\x76\x6d\x65\x73\x73\x3a\x2f\x2f".decode()
    # V-M-E-S-S
    protocol = b"\x76\x6d\x65\x73\x73".decode()

    def decode(self, link):
        body = link.replace(self.name, "")
        decoded_body = base64_decode(body)
        decoded_body = eval(decoded_body)

        return {
            "uuid": decoded_body["id"],
            "port": decoded_body["port"],
            "addr": decoded_body["add"],
            "protocol": self.protocol,
            "note": decoded_body["ps"].split(" ")[1],
        }


def fetch_subscribe_content(url):
    try:
        import requests
    except ImportError:
        print(f"requests is not install, please run")
        print(f"$ pip install requests")
        exit(1)

    try:
        req = requests.get(url, timeout=(10, 10), verify=False)
    except Exception:
        print(
            "\nFailed to fetch subscribe content: \033[1;31m(please check subscribe URL or network)\033[0m"
        )
        exit(1)

    return req.content


def decode_subscribe_content(content):
    import base64

    try:
        decoded_content = base64.b64decode(content).decode("utf-8") if content else None
    except Exception:
        print(
            "\nFailed to decode subscribe content: \033[1;31m(the subscription token is wrong, please check it again)\033[0m"
        )
        exit(1)

    if content == b"":
        print(
            "\nFailed to decode subscribe content: \033[1;31m(the subscription is expired, please renewal it)\033[0m"
        )
        exit(1)

    return decoded_content.strip().split("\n") if decoded_content else []


def decode_protocol_links(links):
    data = []

    protocols = {
        protocolA.name: protocolA(),
        protocolB.name: protocolB(),
    }

    for link in links:
        for protocol_name, protocol_obj in protocols.items():
            if link.startswith(protocol_name):
                data.append(protocol_obj.decode(link))

    return data


def args_parse():
    from argparse import ArgumentParser

    parser = ArgumentParser(description="Subscribe Convert Tool")

    parser.add_argument(
        "-s", "--subscribe", metavar="URL", required=True, help="URL to subscribe"
    )
    parser.add_argument(
        "-o", "--outdir", metavar="DIR", required=True, help="Path to outdir"
    )

    debug = parser.add_argument_group("test options")
    debug.add_argument("--fakecontent", help="fake content")

    return parser.parse_args()


def main():
    import os
    import json

    args = args_parse()

    if args.fakecontent:
        content = open(args.fakecontent, "rb").read()
    else:
        content = fetch_subscribe_content(args.subscribe)

    links = decode_subscribe_content(content)

    data = decode_protocol_links(links)

    count = 0
    for item in data:
        count += 1
        filename = os.path.join(args.outdir, f"server{count:02d}.conf")
        with open(filename, "w", encoding="utf-8") as f:
            json.dump(item, f, indent=4, ensure_ascii=False)
            print(
                f"Output file saved at \033[1;32m{os.path.realpath(f.name)}\033[0m ({count:02d}: {item['note']})"
            )
    print("Subscribe conversion: \033[1;32mCompleted\033[0m")


if __name__ == "__main__":
    main()

  • 12
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值