feat(download): 初步实现ip优选

This commit is contained in:
hyb-oyqq
2025-07-17 11:49:29 +08:00
parent fa63b35ea5
commit a31b9a87ea
6 changed files with 279 additions and 3 deletions

BIN
source/cfst.exe Normal file

Binary file not shown.

View File

@@ -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)

25
source/ip.txt Normal file
View File

@@ -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

130
source/ip_optimizer.py Normal file
View File

@@ -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。")

97
source/ipv6.txt Normal file
View File

@@ -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

View File

@@ -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(