脚本自动部署v2ray订阅链接

1. 任务介绍

最近修整了一下自己的科学上网服务器,虽然已经部署了自己的订阅地址,但是每次都需要手动复制分享 URL 并访问 Base64 转换网站,转换后手动修改文件内容实现订阅内容的更替,十分的不方便。于是决定用 python 脚本来完成上述步骤。
关于配置自己的科学上网服务器和部署自己的订阅服务可以参考以前的笔记:
快速搭建v2ray服务器并实现流量伪装_2024
v2ray客户端配置·设置自己的订阅链接

1.1 任务分解

1.1.1 获取分享 URL

订阅 URL 一般都是由 v2ray 的服务器端生成的,这个要看具体使用的哪个服务器方案才好决定获取方式。如果采用的和前面笔记里一样的服务器脚本,那么通过命令:
v2ray info
就能获取形如下面这样的返回内容(彩色,数据为胡编的):

1.
Group: A
IP: xxx.xxx.xxx.xxx
Port: ?????
TLS: 关闭
UUID: dwuihefpwfalhdfialewfawef
Alter ID: 0
Network: kcp wechat-video

vmess://feshfalsdnlakehjflakjhdsjlakjbejfhaljhndsslkaejbfrlaebfetngslalwehndfvdjslgfnaelwkrhugwleikjdflwa

2.
Group: B
IP: www.youdomain.com
Port: ?????
TLS: 开启 flow:
ID: deijafje;sefjs;porfksaepfa
Encryption: none
Network: kcp wechat-video seed: hfueasiofhaf

vless://doeaihfdoafhashdlkaushdlaeifhasluhflakjsldkakefhgjps;lfhjlakhjflasuhdflaueh

然后以链接开头vmess:// vless:// 为正则表达式的筛选条件获取分享 URL 即可。

1.1.2 去除 bash 颜色显示

注意通过管道或者result = subprocess.run(['v2ray', 'info'], capture_output=True, text=True, check=True)获得的返回字符串是包含 bash 环境下表示显示颜色的字符的,会导致筛选失败。所以需要先把返回字符串消除颜色代码才可继续运行。

1.1.3 组合 URL 并编码为 Base64

订阅服务文件的编写要求是:先把 URL 一个一行的组合在一起,然后一次性的进行 Base64 编码。而不是分别进行编码后再一行一个的排列。

1.1.4 写入订阅文件并备份

注意备份原订阅服务文件,在进行文件覆写。

2. 代码记录

#!/usr/bin/env python3
import subprocess
import base64
import re
import os
import shutil

def remove_ansi_codes(text):
    """
    移除ANSI颜色代码和转义序列
    例如: \x1b[32mText\x1b[0m -> Text
    """
    ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
    return ansi_escape.sub('', text)

def extract_and_encode_urls():
    try:
        # 执行命令
        result = subprocess.run(['v2ray', 'info'], 
                              capture_output=True, 
                              text=True, 
                              check=True)

        # 移除颜色代码
        clean_output = remove_ansi_codes(result.stdout)

        '''
        print("=== 清理后的输出(前500字符)===")
        print(clean_output[:500])
        print("=== 结束 ===")
        '''

        urls = []
        current_url = []
        in_url_block = False

        # 逐行处理清理后的输出
        for line in clean_output.split('\n'):
            clean_line = line.strip()

            # 检测URL开始
            if clean_line.startswith(('vless://', 'vmess://')):
                in_url_block = True
                # 如果是新的URL开始,保存上一个URL
                if current_url:
                    full_url = ''.join(current_url)
                    urls.append(full_url)
                    print(f"\033[92m发现URL: {full_url[:80]}...\033[0m\n\n")
                    current_url = []
                current_url.append(clean_line)
            elif in_url_block and clean_line:
                # 如果当前在URL块中且该行不为空,可能是URL的继续
                current_url.append(clean_line)
            elif in_url_block and not clean_line:
                # 遇到空行,结束当前URL块
                in_url_block = False
                if current_url:
                    full_url = ''.join(current_url)
                    urls.append(full_url)
                    print(f"\033[92m发现URL: {full_url[:80]}...\033[0m\n\n")
                    current_url = []

        # 添加最后一个URL(如果存在)
        if current_url:
            full_url = ''.join(current_url)
            urls.append(full_url)
            print(f"\033[92m发现URL: {full_url[:80]}...\033[0m\n\n")

        if not urls:
            print("未找到任何 vless:// 或 vmess:// 链接")
            print("原始输出(带颜色):")
            print(result.stdout[:1000] +  "\n\n")
            return

        # 写入文件
        output_file = 'v2raysubscribe.txt'
        try:
            # 备份原文件(如果存在)
            if os.path.exists(output_file):
                backup_file = output_file + '.bak'
                shutil.copy2(output_file, backup_file)
                print(f"已备份原文件至: {backup_file}")

            # 将所有URL组合成多行字符串(每行一个URL)
            all_urls_text = '\n'.join(urls) + '\n'

            # 对整个多行字符串进行一次Base64编码
            encoded_content = base64.b64encode(all_urls_text.encode()).decode('utf-8')

            # 一次性写入文件
            with open(output_file, 'w') as f:
                f.write(encoded_content)

            print(f"成功写入 {len(urls)} 个URL到 {output_file}\n\n")

            # 显示编码后的内容
            print(f"{encoded_content}")

            # 解码验证
            try:
                decoded = base64.b64decode(encoded_content).decode()
                print(f"解码验证:\n \033[92m{decoded}\033[0m")
            except:
                print(f"解码验证: 解码失败")

        except PermissionError:
            print(f"权限不足,请使用sudo运行脚本")
        except Exception as e:
            print(f"文件操作失败: {e}")


    except subprocess.CalledProcessError as e:
        print(f"命令执行失败: {e}")
        print(f"错误输出: {e.stderr}")
    except FileNotFoundError:
        print("错误: 未找到 v2ray 命令,请确保 v2ray 已安装并在PATH中")
    except Exception as e:
        print(f"发生错误: {e}")

if __name__ == "__main__":
    extract_and_encode_urls()