├── README.md ├── node_generator.py └── node_generator.spec /README.md: -------------------------------------------------------------------------------- 1 | 使用方法: 2 | 运行脚本 3 | 输入基础VLESS节点URL 4 | 依次输入IP列表(每行一个,空行结束) 5 | 依次输入端口列表(每行一个,空行结束) 6 | 依次输入属地/名称列表(每行一个,空行结束) 7 | 程序将生成所有可能组合的新节点并显示 8 | 这个脚本会保持原有节点的所有参数(如encryption、security、sni等)不变,只替换需要批量修改的部分。生成的节点会自动对属地/名称进行URL编码,确保特殊字符不会导致问题。 9 | 更多免费VPN分享频道:https://t.me/qiankeji 10 | 所需优选IP频道:https://t.me/bestcfipas 11 | -------------------------------------------------------------------------------- /node_generator.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | from typing import List, Dict 4 | import os 5 | import tkinter as tk 6 | from tkinter import ttk, messagebox, scrolledtext 7 | import pyperclip # 用于复制到剪贴板 8 | 9 | class NodeGeneratorGUI: 10 | def __init__(self, root): 11 | self.root = root 12 | self.root.title("节点生成器 v1.0.2 - By YouTube科技共享") 13 | self.root.geometry("800x800") 14 | self.generator = NodeGenerator() 15 | 16 | # 创建版本信息标签 17 | version_frame = ttk.Frame(self.root, padding="5") 18 | version_frame.grid(row=0, column=0, sticky=(tk.W, tk.E)) 19 | ttk.Label(version_frame, 20 | text="节点生成器 v1.0.2\n作者:YouTube科技共享", 21 | justify=tk.CENTER).grid(row=0, column=0, pady=5) 22 | 23 | # 创建主框架 24 | self.main_frame = ttk.Frame(self.root, padding="10") 25 | self.main_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) 26 | 27 | # 创建所有输入区域 28 | self.create_input_areas() 29 | 30 | # 创建按钮区域 31 | self.create_buttons() 32 | 33 | # 创建结果显示区域 34 | self.create_result_area() 35 | 36 | # 加载现有配置 37 | self.load_existing_config() 38 | 39 | def create_input_areas(self): 40 | """创建所有输入区域""" 41 | # 模板节点输入 42 | ttk.Label(self.main_frame, text="请输入模板节点:").grid(row=0, column=0, sticky=tk.W, pady=5) 43 | self.template_input = scrolledtext.ScrolledText(self.main_frame, height=4, width=80, wrap=tk.WORD) 44 | self.template_input.grid(row=1, column=0, columnspan=2, pady=5) 45 | 46 | # IP输入 47 | ttk.Label(self.main_frame, text="IP地址(每行一个):").grid(row=2, column=0, sticky=tk.W, pady=5) 48 | self.ip_input = scrolledtext.ScrolledText(self.main_frame, height=3, width=80, wrap=tk.WORD) 49 | self.ip_input.grid(row=3, column=0, columnspan=2, pady=5) 50 | 51 | # 端口输入 52 | ttk.Label(self.main_frame, text="端口(每行一个):").grid(row=4, column=0, sticky=tk.W, pady=5) 53 | self.port_input = scrolledtext.ScrolledText(self.main_frame, height=3, width=80, wrap=tk.WORD) 54 | self.port_input.grid(row=5, column=0, columnspan=2, pady=5) 55 | 56 | # 地区输入 57 | ttk.Label(self.main_frame, text="地区(每行一个):").grid(row=6, column=0, sticky=tk.W, pady=5) 58 | self.region_input = scrolledtext.ScrolledText(self.main_frame, height=3, width=80, wrap=tk.WORD) 59 | self.region_input.grid(row=7, column=0, columnspan=2, pady=5) 60 | 61 | def create_buttons(self): 62 | """创建按钮区域""" 63 | button_frame = ttk.Frame(self.main_frame) 64 | button_frame.grid(row=8, column=0, columnspan=2, pady=10) 65 | 66 | # 生成节点按钮 67 | self.generate_button = ttk.Button(button_frame, text="生成节点", command=self.generate_nodes) 68 | self.generate_button.grid(row=0, column=0, padx=5) 69 | 70 | # 复制节点按钮 71 | self.copy_button = ttk.Button(button_frame, text="复制节点", command=self.copy_nodes) 72 | self.copy_button.grid(row=0, column=1, padx=5) 73 | 74 | # 下载文件按钮 75 | self.save_button = ttk.Button(button_frame, text="下载文件", command=self.open_file_location) 76 | self.save_button.grid(row=0, column=2, padx=5) 77 | 78 | def create_result_area(self): 79 | """创建结果显示区域""" 80 | ttk.Label(self.main_frame, text="生成结果:").grid(row=9, column=0, sticky=tk.W, pady=5) 81 | self.result_display = scrolledtext.ScrolledText(self.main_frame, height=10, width=80, wrap=tk.WORD) 82 | self.result_display.grid(row=10, column=0, columnspan=2, pady=5) 83 | 84 | def copy_nodes(self): 85 | """复制节点到剪贴板""" 86 | result_text = self.result_display.get(1.0, tk.END).strip() 87 | if result_text: 88 | pyperclip.copy(result_text) 89 | messagebox.showinfo("成功", "节点已复制到剪贴板!") 90 | else: 91 | messagebox.showwarning("警告", "没有可复制的节点!") 92 | 93 | def open_file_location(self): 94 | """打开文件所在位置""" 95 | if os.path.exists(self.generator.output_file): 96 | os.startfile(os.path.dirname(os.path.abspath(self.generator.output_file))) 97 | else: 98 | messagebox.showwarning("警告", "文件尚未生成!") 99 | 100 | def generate_nodes(self): 101 | """生成节点""" 102 | # 获取当前输入的配置 103 | config = { 104 | "ip_list": [ip.strip() for ip in self.ip_input.get(1.0, tk.END).strip().split("\n") if ip.strip()], 105 | "port_list": [port.strip() for port in self.port_input.get(1.0, tk.END).strip().split("\n") if port.strip()], 106 | "regions": [region.strip() for region in self.region_input.get(1.0, tk.END).strip().split("\n") if region.strip()] 107 | } 108 | 109 | # 检查是否有输入 110 | if not config["ip_list"] or not config["port_list"] or not config["regions"]: 111 | messagebox.showerror("错误", "请确保已输入IP、端口和地区!") 112 | return 113 | 114 | template = self.template_input.get(1.0, tk.END).strip() 115 | if not template: 116 | messagebox.showerror("错误", "请输入模板节点!") 117 | return 118 | 119 | self.generator.set_template(template) 120 | 121 | # 直接传递当前配置给 generate_nodes 122 | generated_nodes = self.generator.generate_nodes(config) # 修改这里,传入配置 123 | self.generator.save_nodes(generated_nodes) 124 | 125 | # 显示结果 126 | result_text = f"已成功生成 {len(generated_nodes)} 个节点!\n" 127 | result_text += f"节点已保存到 {self.generator.output_file}\n\n" 128 | result_text += "生成的节点:\n" + "\n".join(generated_nodes) 129 | 130 | self.result_display.delete(1.0, tk.END) 131 | self.result_display.insert(tk.END, result_text) 132 | 133 | messagebox.showinfo("成功", f"已生成 {len(generated_nodes)} 个节点!") 134 | 135 | def load_existing_config(self): 136 | """加载现有配置到输入框 - 现在保持为空""" 137 | pass # 不执行任何操作,保持输入框为空 138 | 139 | def save_config(self): 140 | """保存配置到文件""" 141 | config = { 142 | "ip_list": [ip.strip() for ip in self.ip_input.get(1.0, tk.END).strip().split("\n") if ip.strip()], 143 | "port_list": [port.strip() for port in self.port_input.get(1.0, tk.END).strip().split("\n") if port.strip()], 144 | "regions": [region.strip() for region in self.region_input.get(1.0, tk.END).strip().split("\n") if region.strip()] 145 | } 146 | 147 | with open(self.generator.config_file, 'w', encoding='utf-8') as f: 148 | json.dump(config, f, indent=4, ensure_ascii=False) 149 | 150 | messagebox.showinfo("成功", "配置已保存!") 151 | 152 | class NodeGenerator: 153 | def __init__(self): 154 | self.template_node = "" 155 | self.config_file = "node_config.json" 156 | self.output_file = "generated_nodes.txt" 157 | 158 | def load_config(self) -> Dict: 159 | """加载配置文件""" 160 | # 返回空配置 161 | return { 162 | "ip_list": [], 163 | "port_list": [], 164 | "regions": [] 165 | } 166 | 167 | def set_template(self, node_string: str): 168 | """设置模板节点""" 169 | self.template_node = node_string 170 | 171 | def generate_nodes(self, config: Dict) -> List[str]: 172 | """生成新的节点列表""" 173 | generated_nodes = [] 174 | 175 | # 获取最短长度,确保一一对应 176 | min_length = min(len(config["ip_list"]), 177 | len(config["port_list"]), 178 | len(config["regions"])) 179 | 180 | for i in range(min_length): 181 | ip = config["ip_list"][i] 182 | port = config["port_list"][i] 183 | region = config["regions"][i] 184 | 185 | # 分割节点字符串在 # 处 186 | base_part, name_part = self.template_node.split('#', 1) if '#' in self.template_node else (self.template_node, '') 187 | 188 | # 替换IP和端口 189 | new_node = re.sub(r'@.*?:', f'@{ip}:', base_part) 190 | new_node = re.sub(r':\d+\?', f':{port}?', new_node) 191 | 192 | # 添加新的节点名称(格式:地区 YouTube科技共享) 193 | new_node = f"{new_node}#{region} YouTube科技共享" 194 | 195 | generated_nodes.append(new_node) 196 | 197 | return generated_nodes 198 | 199 | def save_nodes(self, nodes: List[str]): 200 | """保存生成的节点到文件""" 201 | with open(self.output_file, 'w', encoding='utf-8') as f: 202 | for node in nodes: 203 | f.write(node + '\n') 204 | 205 | def main(): 206 | root = tk.Tk() 207 | app = NodeGeneratorGUI(root) 208 | root.mainloop() 209 | 210 | if __name__ == "__main__": 211 | main() -------------------------------------------------------------------------------- /node_generator.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | 4 | a = Analysis( 5 | ['node_generator.py'], 6 | pathex=[], 7 | binaries=[], 8 | datas=[], 9 | hiddenimports=[], 10 | hookspath=[], 11 | hooksconfig={}, 12 | runtime_hooks=[], 13 | excludes=[], 14 | noarchive=False, 15 | optimize=0, 16 | ) 17 | pyz = PYZ(a.pure) 18 | 19 | exe = EXE( 20 | pyz, 21 | a.scripts, 22 | a.binaries, 23 | a.datas, 24 | [], 25 | name='node_generator', 26 | debug=False, 27 | bootloader_ignore_signals=False, 28 | strip=False, 29 | upx=True, 30 | upx_exclude=[], 31 | runtime_tmpdir=None, 32 | console=False, 33 | disable_windowed_traceback=False, 34 | argv_emulation=False, 35 | target_arch=None, 36 | codesign_identity=None, 37 | entitlements_file=None, 38 | ) 39 | --------------------------------------------------------------------------------