diff --git a/source/cfst.exe b/source/cfst.exe new file mode 100644 index 0000000..ed6f13c Binary files /dev/null and b/source/cfst.exe differ diff --git a/source/download.py b/source/download.py index e6038b4..79f184f 100644 --- a/source/download.py +++ b/source/download.py @@ -14,11 +14,12 @@ class DownloadThread(QThread): progress = Signal(dict) finished = Signal(bool, str) - def __init__(self, url, _7z_path, game_version, parent=None): + def __init__(self, url, _7z_path, game_version, preferred_ip=None, parent=None): super().__init__(parent) self.url = url self._7z_path = _7z_path self.game_version = game_version + self.preferred_ip = preferred_ip self.process = None self.is_running = True @@ -40,6 +41,16 @@ class DownloadThread(QThread): command = [ aria2c_path, + ] + + # 如果有优选IP,则添加到 aaric2 命令中 + if self.preferred_ip: + hostname = parsed_url.hostname + port = parsed_url.port or (443 if parsed_url.scheme == 'https' else 80) + command.extend(['--resolve', f'{hostname}:{port}:{self.preferred_ip}']) + print(f"已应用优选IP: {hostname} -> {self.preferred_ip}") + + command.extend([ '--dir', download_dir, '--out', file_name, '--user-agent', UA, @@ -66,7 +77,7 @@ class DownloadThread(QThread): '--split=16', '--max-connection-per-server=16', self.url - ] + ]) creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 self.process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, encoding='utf-8', errors='replace', creationflags=creation_flags) diff --git a/source/ip.txt b/source/ip.txt new file mode 100644 index 0000000..9980eca --- /dev/null +++ b/source/ip.txt @@ -0,0 +1,25 @@ +173.245.48.0/20 +103.21.244.0/22 +103.22.200.0/22 +103.31.4.0/22 +141.101.64.0/18 +108.162.192.0/18 +190.93.240.0/20 +188.114.96.0/20 +197.234.240.0/22 +198.41.128.0/17 +162.158.0.0/15 +104.16.0.0/12 +172.64.0.0/17 +172.64.128.0/18 +172.64.192.0/19 +172.64.224.0/22 +172.64.229.0/24 +172.64.230.0/23 +172.64.232.0/21 +172.64.240.0/21 +172.64.248.0/21 +172.65.0.0/16 +172.66.0.0/16 +172.67.0.0/16 +131.0.72.0/22 \ No newline at end of file diff --git a/source/ip_optimizer.py b/source/ip_optimizer.py new file mode 100644 index 0000000..47cd2dc --- /dev/null +++ b/source/ip_optimizer.py @@ -0,0 +1,130 @@ +import os +import re +import subprocess +import sys +from urllib.parse import urlparse + +from utils import resource_path + +def get_optimal_ip(url: str) -> str | None: + """ + 使用 CloudflareSpeedTest 工具获取给定 URL 的最优 Cloudflare IP。 + + Args: + url: 需要进行优选的下载链接。 + + Returns: + 最优的 IP 地址字符串,如果找不到则返回 None。 + """ + try: + # 1. 定位 CloudflareSpeedTest 工具路径,使用新的文件名 cfst.exe + cst_path = resource_path("cfst.exe") + if not os.path.exists(cst_path): + print(f"错误: cfst.exe 未在资源路径中找到。") + return None + + # 2. 构建命令行参数 + # -p 1: 只输出最快的一个 IP + # -o "": 不生成 result.csv 文件 + # -url: 指定我们自己的测速链接 + # -f: 指定 ip.txt 的路径 + ip_txt_path = resource_path("ip.txt") + command = [ + cst_path, + "-p", "1", + "-o", "", + "-url", url, + "-f", ip_txt_path, + "-dd", + ] + + # 3. 执行命令并捕获输出 + # 使用 CREATE_NO_WINDOW 标志来隐藏控制台窗口 + creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + process = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + encoding='utf-8', + errors='replace', + creationflags=creation_flags, + bufsize=1, # 使用行缓冲 + ) + + # 4. 实时读取、打印并解析输出 + print("--- CloudflareSpeedTest 实时输出 ---") + + if not process.stdout: + print("错误: 无法获取子进程的输出流。") + return None + + # 根据用户提供的最新格式更新正则表达式 + # 格式: IP Sent Recv Loss Avg-Latency DL-Speed Region + ip_pattern = re.compile(r'^\s*([\d\.]+)\s+\d+\s+\d+\s+[\d\.]+%?\s+[\d\.]+\s+[\d\.]+\s+.*$') + fd = process.stdout.fileno() + buffer = b'' + + while process.poll() is None: + try: + chunk = os.read(fd, 1024) + if not chunk: + break + buffer += chunk + + while b'\n' in buffer or b'\r' in buffer: + end_index_n = buffer.find(b'\n') + end_index_r = buffer.find(b'\r') + end_index = min(end_index_n, end_index_r) if end_index_n != -1 and end_index_r != -1 else max(end_index_n, end_index_r) + + line_bytes = buffer[:end_index] + line = line_bytes.decode('utf-8', errors='replace').strip() + + if line: + print(line) + match = ip_pattern.match(line) + if match: + optimal_ip = match.group(1) + print(f"找到最优 IP: {optimal_ip}, 正在终止测速进程...") + print("------------------------------------") + process.terminate() # 终止进程 + return optimal_ip + + buffer = buffer[end_index+1:] + + except (IOError, OSError): + break + + # 处理可能残留在缓冲区的数据 + if buffer: + line = buffer.decode('utf-8', errors='replace').strip() + if line: + print(line) + match = ip_pattern.match(line) + if match: + optimal_ip = match.group(1) + print(f"找到最优 IP: {optimal_ip}") + print("------------------------------------") + process.terminate() # 确保在返回前终止进程 + return optimal_ip + + print("------------------------------------") + + # 5. 在循环结束后,检查是否找到了 IP + # (IP 在循环内部找到并返回) + process.wait() # 等待进程完全终止 + print("警告: 未能在 CloudflareSpeedTest 输出中找到最优 IP。") + return None + + except Exception as e: + print(f"执行 CloudflareSpeedTest 时发生错误: {e}") + return None + +if __name__ == '__main__': + # 用于直接测试此模块 + test_url = "https://speed.cloudflare.com/__down?during=download&bytes=104857600" + ip = get_optimal_ip(test_url) + if ip: + print(f"为 {test_url} 找到的最优 IP 是: {ip}") + else: + print(f"未能为 {test_url} 找到最优 IP。") \ No newline at end of file diff --git a/source/ipv6.txt b/source/ipv6.txt new file mode 100644 index 0000000..978fba9 --- /dev/null +++ b/source/ipv6.txt @@ -0,0 +1,97 @@ +2400:cb00:2049::/48 +2400:cb00:f00e::/48 +2606:4700::/32 +2606:4700:10::/48 +2606:4700:130::/48 +2606:4700:3000::/48 +2606:4700:3001::/48 +2606:4700:3002::/48 +2606:4700:3003::/48 +2606:4700:3004::/48 +2606:4700:3005::/48 +2606:4700:3006::/48 +2606:4700:3007::/48 +2606:4700:3008::/48 +2606:4700:3009::/48 +2606:4700:3010::/48 +2606:4700:3011::/48 +2606:4700:3012::/48 +2606:4700:3013::/48 +2606:4700:3014::/48 +2606:4700:3015::/48 +2606:4700:3016::/48 +2606:4700:3017::/48 +2606:4700:3018::/48 +2606:4700:3019::/48 +2606:4700:3020::/48 +2606:4700:3021::/48 +2606:4700:3022::/48 +2606:4700:3023::/48 +2606:4700:3024::/48 +2606:4700:3025::/48 +2606:4700:3026::/48 +2606:4700:3027::/48 +2606:4700:3028::/48 +2606:4700:3029::/48 +2606:4700:3030::/48 +2606:4700:3031::/48 +2606:4700:3032::/48 +2606:4700:3033::/48 +2606:4700:3034::/48 +2606:4700:3035::/48 +2606:4700:3036::/48 +2606:4700:3037::/48 +2606:4700:3038::/48 +2606:4700:3039::/48 +2606:4700:a0::/48 +2606:4700:a1::/48 +2606:4700:a8::/48 +2606:4700:a9::/48 +2606:4700:a::/48 +2606:4700:b::/48 +2606:4700:c::/48 +2606:4700:d0::/48 +2606:4700:d1::/48 +2606:4700:d::/48 +2606:4700:e0::/48 +2606:4700:e1::/48 +2606:4700:e2::/48 +2606:4700:e3::/48 +2606:4700:e4::/48 +2606:4700:e5::/48 +2606:4700:e6::/48 +2606:4700:e7::/48 +2606:4700:e::/48 +2606:4700:f1::/48 +2606:4700:f2::/48 +2606:4700:f3::/48 +2606:4700:f4::/48 +2606:4700:f5::/48 +2606:4700:f::/48 +2803:f800:50::/48 +2803:f800:51::/48 +2a06:98c1:3100::/48 +2a06:98c1:3101::/48 +2a06:98c1:3102::/48 +2a06:98c1:3103::/48 +2a06:98c1:3104::/48 +2a06:98c1:3105::/48 +2a06:98c1:3106::/48 +2a06:98c1:3107::/48 +2a06:98c1:3108::/48 +2a06:98c1:3109::/48 +2a06:98c1:310a::/48 +2a06:98c1:310b::/48 +2a06:98c1:310c::/48 +2a06:98c1:310d::/48 +2a06:98c1:310e::/48 +2a06:98c1:310f::/48 +2a06:98c1:3120::/48 +2a06:98c1:3121::/48 +2a06:98c1:3122::/48 +2a06:98c1:3123::/48 +2a06:98c1:3200::/48 +2a06:98c1:50::/48 +2a06:98c1:51::/48 +2a06:98c1:54::/48 +2a06:98c1:58::/48 \ No newline at end of file diff --git a/source/main_window.py b/source/main_window.py index 4b39971..16fd8aa 100644 --- a/source/main_window.py +++ b/source/main_window.py @@ -20,6 +20,7 @@ from utils import ( load_base64_image, HashManager, AdminPrivileges, msgbox_frame ) from download import DownloadThread, ProgressWindow +from ip_optimizer import get_optimal_ip from pic_data import img_data class MainWindow(QMainWindow): @@ -165,8 +166,20 @@ class MainWindow(QMainWindow): return self.progress_window = ProgressWindow(self) + + # --- IP 优选逻辑 --- + self.progress_window.game_label.setText("正在优化下载线路,请稍候...") + QApplication.processEvents() # 刷新UI以显示上述消息 - self.current_download_thread = DownloadThread(url, _7z_path, game_version, self) + preferred_ip = get_optimal_ip(url) + + if preferred_ip: + print(f"已为 {game_version} 获取到优选IP: {preferred_ip}") + else: + print(f"未能为 {game_version} 获取优选IP,将使用默认线路。") + # --- IP 优选逻辑结束 --- + + self.current_download_thread = DownloadThread(url, _7z_path, game_version, preferred_ip, self) self.current_download_thread.progress.connect(self.progress_window.update_progress) self.current_download_thread.finished.connect( lambda success, error: self.install_setting(