feat(core): 优化隐私协议管理并添加日志功能

- 重构 PrivacyManager 类,增加隐私协议版本检查和用户同意状态管理
- 在主窗口初始化时获取云端配置,提高效率
- 添加日志功能,记录应用启动、隐私协议加载等关键事件
- 优化错误处理和用户提示信息
This commit is contained in:
hyb-oyqq
2025-07-31 14:38:12 +08:00
parent 5ad4062346
commit c941c03446
8 changed files with 367 additions and 65 deletions

View File

@@ -9,13 +9,11 @@
本应用在运行过程中可能会收集或处理以下信息: 本应用在运行过程中可能会收集或处理以下信息:
### 2.1 系统信息 ### 2.1 系统信息
- 操作系统版本信息:作为 User-Agent 的一部分,用于与服务器通信
- 程序版本号:用于检查更新和兼容性 - 程序版本号:用于检查更新和兼容性
### 2.2 网络相关信息 ### 2.2 网络相关信息
- IP 地址:在使用 Cloudflare 加速功能时,会进行 IP 优选 - **IP 地址、ISP 及地理位置**: 应用启动时,为获取云端配置,您的 IP 地址会被服务器记录。服务器可能会根据您的 IP 地址推断您的互联网服务提供商ISP和地理位置这些信息仅用于用户数量、区域分布的统计和软件使用情况分析。当您使用 Cloudflare 加速功能时,您的 IP 地址也会被用于节点优选
- 域名解析信息:用于优化下载速度 - **下载统计信息**:用于监控下载进度和速度
- 下载统计信息:用于监控下载进度和速度
### 2.3 文件信息 ### 2.3 文件信息
- 游戏安装路径:用于识别已安装的游戏和安装补丁 - 游戏安装路径:用于识别已安装的游戏和安装补丁
@@ -31,8 +29,9 @@
- 下载加速:通过 Cloudflare 优化下载速度 - 下载加速:通过 Cloudflare 优化下载速度
### 3.2 服务改进 ### 3.2 服务改进
- 提供更新:检查应用版本并推送更新 - **应用更新**:检查应用版本并推送更新
- 错误报告:收集错误信息以改进应用体验 - **使用情况分析**通过统计IP地址、ISP和地理位置等信息分析用户下载次数与软件使用情况以帮助我们改进服务。您的所有信息都仅用于软件使用统计不会用于其他特殊目的。
- **错误报告**:收集错误信息以改进应用体验。
## 4. 数据存储 ## 4. 数据存储
@@ -56,12 +55,12 @@
本应用使用以下第三方服务: 本应用使用以下第三方服务:
### 6.1 Cloudflare ### 6.1 Cloudflare
- 当您选择使用 Cloudflare 加速功能时,本应用会使用 CloudflareSpeedTest 工具测试并选择最优 IP 地址 - 本应用使用第三方开源项目 [CloudflareSpeedTest (CFST)](https://github.com/XIU2/CloudflareSpeedTest/) 为您提供 Cloudflare 加速功能。该优选服务由 CFST 项目提供,本项目及作者不负责其功能的实际维护。
- 此过程会向 Cloudflare 服务器发送请求以测试连接速度 - 启用此功能时CFST 将向 Cloudflare 的所有节点发送请求以测试延迟,此过程不可避免地会将您的 IP 地址提交至 Cloudflare。我们建议您遵循并查阅 Cloudflare 的相关用户协议和隐私政策。
### 6.2 云端配置服务 ### 6.2 云端配置服务
- 本应用从云端服务器获取配置信息(如下载链接等) - 本应用启动时会从云端服务器获取配置信息(如下载链接等)。在此过程中服务器会获取并统计您的IP地址、地理位置及ISP等信息以用于软件使用情况分析。
- 在此过程中,您的 IP 地址和 User-Agent 信息会被传输到云端服务 - 为确保通信安全和服务的稳定性,云端服务器设置了严格的 User-Agent 校验,仅允许本应用内置的特定 User-Agent 发出请求。非本应用指定的 User-Agent 将无法访问服务
## 7. 用户控制 ## 7. 用户控制

View File

@@ -1,21 +1,35 @@
import sys import sys
from PySide6.QtWidgets import QApplication from PySide6.QtWidgets import QApplication, QMessageBox
from main_window import MainWindow from main_window import MainWindow
from core.privacy_manager import PrivacyManager from core.privacy_manager import PrivacyManager
from utils.logger import setup_logger
if __name__ == "__main__": if __name__ == "__main__":
# 初始化日志
logger = setup_logger("main")
logger.info("应用启动")
app = QApplication(sys.argv) app = QApplication(sys.argv)
# 初始化隐私协议管理器 # 初始化隐私协议管理器
privacy_manager = PrivacyManager() try:
privacy_manager = PrivacyManager()
except Exception as e:
logger.error(f"初始化隐私协议管理器失败: {e}")
QMessageBox.critical(
None,
"隐私协议加载错误",
f"无法加载隐私协议管理器,程序将退出。\n\n错误信息:{e}"
)
sys.exit(1)
# 显示隐私协议对话框(仅在首次运行或用户未同意时显示) # 显示隐私协议对话框
if not privacy_manager.show_privacy_dialog(): if not privacy_manager.show_privacy_dialog():
print("用户未同意隐私协议,程序退出") logger.info("用户未同意隐私协议,程序退出")
sys.exit(0) # 如果用户不同意隐私协议,退出程序 sys.exit(0) # 如果用户不同意隐私协议,退出程序
# 用户已同意隐私协议,继续启动程序 # 用户已同意隐私协议,继续启动程序
print("隐私协议已同意,启动主程序") logger.info("隐私协议已同意,启动主程序")
window = MainWindow() window = MainWindow()
window.show() window.show()
sys.exit(app.exec()) sys.exit(app.exec())

View File

@@ -41,8 +41,14 @@ class DebugManager:
Args: Args:
checked: 是否启用调试模式 checked: 是否启用调试模式
""" """
print(f"Toggle debug mode: {checked}")
self.main_window.config["debug_mode"] = checked self.main_window.config["debug_mode"] = checked
self.main_window.save_config(self.main_window.config) self.main_window.save_config(self.main_window.config)
# 更新打开log文件按钮状态
if hasattr(self, 'ui_manager') and hasattr(self.ui_manager, 'open_log_action'):
self.ui_manager.open_log_action.setEnabled(checked)
if checked: if checked:
self.start_logging() self.start_logging()
else: else:

View File

@@ -6,36 +6,77 @@ import json
from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QCheckBox, QLabel, QMessageBox from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QCheckBox, QLabel, QMessageBox
from PySide6.QtCore import Qt from PySide6.QtCore import Qt
from data.privacy_policy import PRIVACY_POLICY_BRIEF from data.privacy_policy import PRIVACY_POLICY_BRIEF, get_local_privacy_policy, PRIVACY_POLICY_VERSION
from data.config import CACHE, APP_NAME from data.config import CACHE, APP_NAME, APP_VERSION
from utils import msgbox_frame from utils import msgbox_frame
from utils.logger import setup_logger
class PrivacyManager: class PrivacyManager:
"""隐私协议管理器,负责显示隐私协议对话框并处理用户选择""" """隐私协议管理器,负责显示隐私协议对话框并处理用户选择"""
def __init__(self): def __init__(self):
"""初始化隐私协议管理器""" """初始化隐私协议管理器"""
# 初始化日志
self.logger = setup_logger("privacy_manager")
self.logger.info("正在初始化隐私协议管理器")
# 确保缓存目录存在 # 确保缓存目录存在
os.makedirs(CACHE, exist_ok=True) os.makedirs(CACHE, exist_ok=True)
self.config_file = os.path.join(CACHE, "privacy_config.json") self.config_file = os.path.join(CACHE, "privacy_config.json")
self.privacy_accepted = self._load_privacy_config() self.privacy_config = self._load_privacy_config()
# 获取隐私协议内容和版本
self.logger.info("读取本地隐私协议文件")
self.privacy_content, self.current_privacy_version, error = get_local_privacy_policy()
if error:
self.logger.warning(f"读取本地隐私协议文件警告: {error}")
# 使用默认版本作为备用
self.current_privacy_version = PRIVACY_POLICY_VERSION
self.logger.info(f"隐私协议版本: {self.current_privacy_version}")
# 检查隐私协议版本和用户同意状态
self.privacy_accepted = self._check_privacy_acceptance()
def _load_privacy_config(self): def _load_privacy_config(self):
"""加载隐私协议配置 """加载隐私协议配置
Returns: Returns:
bool: 用户是否已同意隐私协议 dict: 隐私协议配置信息
""" """
if os.path.exists(self.config_file): if os.path.exists(self.config_file):
try: try:
with open(self.config_file, "r", encoding="utf-8") as f: with open(self.config_file, "r", encoding="utf-8") as f:
config = json.load(f) config = json.load(f)
return config.get("privacy_accepted", False) return config
except (json.JSONDecodeError, IOError) as e: except (json.JSONDecodeError, IOError) as e:
print(f"读取隐私配置失败: {e}") self.logger.error(f"读取隐私配置失败: {e}")
# 如果读取失败,返回False,强制显示隐私协议 # 如果读取失败,返回空配置,强制显示隐私协议
return False return {"privacy_accepted": False}
return False return {"privacy_accepted": False}
def _check_privacy_acceptance(self):
"""检查隐私协议是否需要重新同意
如果隐私协议版本变更,则需要重新同意
Returns:
bool: 是否已有有效的隐私协议同意
"""
# 获取存储的版本信息
stored_privacy_version = self.privacy_config.get("privacy_version", "0.0.0")
stored_app_version = self.privacy_config.get("app_version", "0.0.0")
privacy_accepted = self.privacy_config.get("privacy_accepted", False)
self.logger.info(f"存储的隐私协议版本: {stored_privacy_version}, 当前版本: {self.current_privacy_version}")
self.logger.info(f"存储的应用版本: {stored_app_version}, 当前版本: {APP_VERSION}")
self.logger.info(f"隐私协议接受状态: {privacy_accepted}")
# 如果隐私协议版本变更,需要重新同意
if stored_privacy_version != self.current_privacy_version:
self.logger.info("隐私协议版本已变更,需要重新同意")
return False
# 返回当前的同意状态
return privacy_accepted
def _save_privacy_config(self, accepted): def _save_privacy_config(self, accepted):
"""保存隐私协议配置 """保存隐私协议配置
@@ -50,18 +91,24 @@ class PrivacyManager:
# 确保目录存在 # 确保目录存在
os.makedirs(os.path.dirname(self.config_file), exist_ok=True) os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
# 写入配置文件 # 写入配置文件,包含应用版本和隐私协议版本
with open(self.config_file, "w", encoding="utf-8") as f: with open(self.config_file, "w", encoding="utf-8") as f:
json.dump({ json.dump({
"privacy_accepted": accepted, "privacy_accepted": accepted,
"version": "1.0" # 添加版本号,便于将来升级隐私协议时使用 "privacy_version": self.current_privacy_version, # 保存当前隐私协议版本
"app_version": APP_VERSION # 保存当前应用版本
}, f, indent=2) }, f, indent=2)
# 更新实例变量 # 更新实例变量
self.privacy_accepted = accepted self.privacy_accepted = accepted
self.privacy_config = {
"privacy_accepted": accepted,
"privacy_version": self.current_privacy_version,
"app_version": APP_VERSION
}
return True return True
except IOError as e: except IOError as e:
print(f"保存隐私协议配置失败: {e}") self.logger.error(f"保存隐私协议配置失败: {e}")
# 显示保存失败的提示 # 显示保存失败的提示
QMessageBox.warning( QMessageBox.warning(
None, None,
@@ -78,10 +125,10 @@ class PrivacyManager:
""" """
# 如果用户已经同意了隐私协议直接返回True不显示对话框 # 如果用户已经同意了隐私协议直接返回True不显示对话框
if self.privacy_accepted: if self.privacy_accepted:
print("用户已同意隐私协议,无需再次显示") self.logger.info("用户已同意当前版本的隐私协议,无需再次显示")
return True return True
print("首次运行或用户未同意隐私协议,显示隐私对话框") self.logger.info("首次运行或隐私协议版本变更,显示隐私对话框")
# 创建隐私协议对话框 # 创建隐私协议对话框
dialog = QDialog() dialog = QDialog()
@@ -92,13 +139,14 @@ class PrivacyManager:
# 创建布局 # 创建布局
layout = QVBoxLayout(dialog) layout = QVBoxLayout(dialog)
# 添加标题 # 添加标题和版本信息
title_label = QLabel("请阅读并同意以下隐私政策") title_label = QLabel(f"请阅读并同意以下隐私政策 (更新日期: {self.current_privacy_version})")
title_label.setStyleSheet("font-size: 14px; font-weight: bold;") title_label.setStyleSheet("font-size: 14px; font-weight: bold;")
layout.addWidget(title_label) layout.addWidget(title_label)
# 添加隐私协议文本框 # 添加隐私协议文本框
text_browser = QTextBrowser() text_browser = QTextBrowser()
# 这里使用PRIVACY_POLICY_BRIEF而不是self.privacy_content保持UI简洁
text_browser.setMarkdown(PRIVACY_POLICY_BRIEF) text_browser.setMarkdown(PRIVACY_POLICY_BRIEF)
text_browser.setOpenExternalLinks(True) text_browser.setOpenExternalLinks(True)
layout.addWidget(text_browser) layout.addWidget(text_browser)
@@ -118,7 +166,7 @@ class PrivacyManager:
# 连接选择框状态变化 - 修复勾选后按钮不亮起的问题 # 连接选择框状态变化 - 修复勾选后按钮不亮起的问题
def on_checkbox_state_changed(state): def on_checkbox_state_changed(state):
print(f"复选框状态变更为: {state}") self.logger.debug(f"复选框状态变更为: {state}")
agree_button.setEnabled(state == 2) # Qt.Checked 在 PySide6 中值为 2 agree_button.setEnabled(state == 2) # Qt.Checked 在 PySide6 中值为 2
checkbox.stateChanged.connect(on_checkbox_state_changed) checkbox.stateChanged.connect(on_checkbox_state_changed)

View File

@@ -5,7 +5,7 @@ import webbrowser
import os import os
from utils import load_base64_image, msgbox_frame, resource_path from utils import load_base64_image, msgbox_frame, resource_path
from data.config import APP_NAME, APP_VERSION from data.config import APP_NAME, APP_VERSION, LOG_FILE
class UIManager: class UIManager:
def __init__(self, main_window): def __init__(self, main_window):
@@ -269,9 +269,32 @@ class UIManager:
menu_style = self._get_menu_style(font_family) menu_style = self._get_menu_style(font_family)
self.dev_menu.setStyleSheet(menu_style) self.dev_menu.setStyleSheet(menu_style)
# 创建Debug模式选项并添加到开发者选项子菜单 # 创建Debug子菜单
self.debug_action = QAction("Debug模式", self.main_window, checkable=True) self.debug_submenu = QMenu("Debug模式", self.main_window)
self.debug_action.setFont(menu_font) # 设置相同的字体 self.debug_submenu.setFont(menu_font)
self.debug_submenu.setStyleSheet(menu_style)
# 创建hosts文件选项子菜单
self.hosts_submenu = QMenu("hosts文件选项", self.main_window)
self.hosts_submenu.setFont(menu_font)
self.hosts_submenu.setStyleSheet(menu_style)
# 添加hosts子选项
self.restore_hosts_action = QAction("还原软件备份的hosts文件", self.main_window)
self.restore_hosts_action.setFont(menu_font)
self.restore_hosts_action.triggered.connect(self.restore_hosts_backup)
self.clean_hosts_action = QAction("手动删除软件添加的hosts条目", self.main_window)
self.clean_hosts_action.setFont(menu_font)
self.clean_hosts_action.triggered.connect(self.clean_hosts_entries)
# 添加到hosts子菜单
self.hosts_submenu.addAction(self.restore_hosts_action)
self.hosts_submenu.addAction(self.clean_hosts_action)
# 创建Debug开关选项
self.debug_action = QAction("Debug开关", self.main_window, checkable=True)
self.debug_action.setFont(menu_font)
# 安全地获取config属性 # 安全地获取config属性
config = getattr(self.main_window, 'config', {}) config = getattr(self.main_window, 'config', {})
@@ -285,24 +308,33 @@ class UIManager:
if hasattr(self.main_window, 'toggle_debug_mode'): if hasattr(self.main_window, 'toggle_debug_mode'):
self.debug_action.triggered.connect(self.main_window.toggle_debug_mode) self.debug_action.triggered.connect(self.main_window.toggle_debug_mode)
# 创建狂暴下载选项(无功能) # 创建打开log文件选项
self.turbo_download_action = QAction("狂暴下载", self.main_window) self.open_log_action = QAction("打开log.txt", self.main_window)
self.turbo_download_action.setFont(menu_font) # 设置自定义字体 self.open_log_action.setFont(menu_font)
self.turbo_download_action.setEnabled(False) # 禁用按钮 # 初始状态根据debug模式设置启用状态
self.open_log_action.setEnabled(debug_mode)
# 添加到开发者选项子菜单 # 连接打开log文件的事件
self.dev_menu.addAction(self.debug_action) self.open_log_action.triggered.connect(self.open_log_file)
self.dev_menu.addAction(self.turbo_download_action)
# 为未来功能预留的"切换下载源"按钮 # 添加到Debug子菜单
self.switch_source_action = QAction("切换下载源", self.main_window) self.debug_submenu.addAction(self.debug_action)
self.debug_submenu.addAction(self.open_log_action)
# 为未来功能预留的"修改下载源"按钮 - 现在点击时显示"正在开发中"
self.switch_source_action = QAction("修改下载源", self.main_window)
self.switch_source_action.setFont(menu_font) # 设置自定义字体 self.switch_source_action.setFont(menu_font) # 设置自定义字体
self.switch_source_action.setEnabled(False) # 暂时禁用 self.switch_source_action.setEnabled(True) # 启用但显示"正在开发中"
self.switch_source_action.triggered.connect(self.show_under_development)
# 添加到主菜单 # 添加到主菜单
self.ui.menu.addAction(self.switch_source_action) self.ui.menu.addAction(self.switch_source_action)
self.ui.menu.addSeparator() self.ui.menu.addSeparator()
self.ui.menu.addMenu(self.dev_menu) # 添加开发者选项子菜单 self.ui.menu.addMenu(self.dev_menu) # 添加开发者选项子菜单
# 添加Debug子菜单到开发者选项菜单
self.dev_menu.addMenu(self.debug_submenu)
self.dev_menu.addMenu(self.hosts_submenu) # 添加hosts文件选项子菜单
def show_menu(self, menu, button): def show_menu(self, menu, button):
"""显示菜单 """显示菜单
@@ -409,6 +441,112 @@ class UIManager:
) )
error_msg.exec() error_msg.exec()
def show_under_development(self):
"""显示功能正在开发中的提示"""
msg_box = msgbox_frame(
f"提示 - {APP_NAME}",
"\n该功能正在开发中,敬请期待!\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
def open_log_file(self):
"""打开log.txt文件"""
if os.path.exists(LOG_FILE):
try:
# 使用操作系统默认程序打开日志文件
if os.name == 'nt': # Windows
os.startfile(LOG_FILE)
else: # macOS 和 Linux
import subprocess
subprocess.call(['xdg-open', LOG_FILE])
except Exception as e:
msg_box = msgbox_frame(
f"错误 - {APP_NAME}",
f"\n打开log.txt文件失败\n\n{str(e)}\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
else:
msg_box = msgbox_frame(
f"提示 - {APP_NAME}",
"\nlog.txt文件不存在请确保Debug模式已开启并生成日志。\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
def restore_hosts_backup(self):
"""还原软件备份的hosts文件"""
if hasattr(self.main_window, 'download_manager') and hasattr(self.main_window.download_manager, 'hosts_manager'):
try:
# 调用恢复hosts文件的方法
result = self.main_window.download_manager.hosts_manager.restore()
if result:
msg_box = msgbox_frame(
f"成功 - {APP_NAME}",
"\nhosts文件已成功还原为备份版本。\n",
QMessageBox.StandardButton.Ok,
)
else:
msg_box = msgbox_frame(
f"警告 - {APP_NAME}",
"\n还原hosts文件失败或没有找到备份文件。\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
except Exception as e:
msg_box = msgbox_frame(
f"错误 - {APP_NAME}",
f"\n还原hosts文件时发生错误\n\n{str(e)}\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
else:
msg_box = msgbox_frame(
f"错误 - {APP_NAME}",
"\n无法访问hosts管理器。\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
def clean_hosts_entries(self):
"""手动删除软件添加的hosts条目"""
if hasattr(self.main_window, 'download_manager') and hasattr(self.main_window.download_manager, 'hosts_manager'):
try:
# 调用清理hosts条目的方法
result = self.main_window.download_manager.hosts_manager.check_and_clean_all_entries()
if result:
msg_box = msgbox_frame(
f"成功 - {APP_NAME}",
"\n已成功清理软件添加的hosts条目。\n",
QMessageBox.StandardButton.Ok,
)
else:
msg_box = msgbox_frame(
f"提示 - {APP_NAME}",
"\n未发现软件添加的hosts条目或清理操作失败。\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
except Exception as e:
msg_box = msgbox_frame(
f"错误 - {APP_NAME}",
f"\n清理hosts条目时发生错误\n\n{str(e)}\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
else:
msg_box = msgbox_frame(
f"错误 - {APP_NAME}",
"\n无法访问hosts管理器。\n",
QMessageBox.StandardButton.Ok,
)
msg_box.exec()
def show_about_dialog(self): def show_about_dialog(self):
"""显示关于对话框""" """显示关于对话框"""
about_text = f""" about_text = f"""

View File

@@ -1,6 +1,11 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os
import re
import sys
from datetime import datetime
# 隐私协议的缩略版内容 # 隐私协议的缩略版内容
PRIVACY_POLICY_BRIEF = """ PRIVACY_POLICY_BRIEF = """
# FRAISEMOE Addons Installer NEXT 隐私政策摘要 # FRAISEMOE Addons Installer NEXT 隐私政策摘要
@@ -8,19 +13,19 @@ PRIVACY_POLICY_BRIEF = """
本应用在运行过程中会收集和处理以下信息: 本应用在运行过程中会收集和处理以下信息:
## 收集的信息 ## 收集的信息
- 系统信息:操作系统版本、程序版本号 - **系统信息**:程序版本号
- 网络信息IP 地址(Cloudflare 加速时)、域名解析、下载统计 - **网络信息**IP 地址、ISP、地理位置用于使用统计、下载统计
- 文件信息:游戏安装路径、文件哈希值 - **文件信息**:游戏安装路径、文件哈希值
## 系统修改 ## 系统修改
- 使用 Cloudflare 加速时会临时修改系统 hosts 文件 - 使用 Cloudflare 加速时会临时修改系统 hosts 文件
- 修改前会自动备份,程序退出时自动恢复 - 修改前会自动备份,程序退出时自动恢复
## 第三方服务 ## 第三方服务
- Cloudflare 服务:用于测试和优化下载速度 - **Cloudflare 服务**:通过开源项目 CloudflareSpeedTest (CFST) 提供,用于优化下载速度。此过程会将您的 IP 提交至 Cloudflare 节点。
- 云端配置服务:获取配置信息和下载链接 - **云端配置服务**:获取配置信息。服务器会记录您的 IP、ISP 及地理位置用于统计。
完整的隐私政策可在程序中访问github仓库查看。 完整的隐私政策可在程序的 GitHub 仓库查看。
""" """
# 隐私协议的英文版缩略版内容 # 隐私协议的英文版缩略版内容
@@ -30,17 +35,60 @@ PRIVACY_POLICY_BRIEF_EN = """
This application collects and processes the following information: This application collects and processes the following information:
## Information Collected ## Information Collected
- System info: OS version, application version - **System info**: Application version.
- Network info: IP address (for Cloudflare acceleration), DNS resolution, download statistics - **Network info**: IP address, ISP, geographic location (for usage statistics), download statistics.
- File info: Game installation paths, file hash values - **File info**: Game installation paths, file hash values.
## System Modifications ## System Modifications
- Temporarily modifies system hosts file when using Cloudflare acceleration - Temporarily modifies system hosts file when using Cloudflare acceleration.
- Automatically backs up before modification and restores upon exit - Automatically backs up before modification and restores upon exit.
## Third-party Services ## Third-party Services
- Cloudflare services: Used for testing and optimizing download speeds - **Cloudflare services**: Provided via the open-source project CloudflareSpeedTest (CFST) to optimize download speeds. This process submits your IP to Cloudflare nodes.
- Cloud configuration services: For obtaining configuration information and download links - **Cloud configuration services**: For obtaining configuration information. The server logs your IP, ISP, and location for statistical purposes.
The complete privacy policy can be found in the github repository of the program. The complete privacy policy can be found in the program's GitHub repository.
""" """
# 默认隐私协议版本 - 本地版本的日期
PRIVACY_POLICY_VERSION = "2025.07.31"
def get_local_privacy_policy():
"""获取本地打包的隐私协议文件
Returns:
tuple: (隐私协议内容, 版本号, 错误信息)
"""
# 尝试不同的可能路径
possible_paths = [
"PRIVACY.md", # 相对于可执行文件
os.path.join(os.path.dirname(sys.executable), "PRIVACY.md"), # 可执行文件目录
os.path.join(os.path.dirname(__file__), "PRIVACY.md"), # 当前模块目录
]
for path in possible_paths:
try:
if os.path.exists(path):
with open(path, "r", encoding="utf-8") as f:
content = f.read()
# 提取更新日期
date_pattern = r'最后更新日期:(\d{4}\d{1,2}月\d{1,2}日)'
match = re.search(date_pattern, content)
if match:
date_str = match.group(1)
try:
date_obj = datetime.strptime(date_str, '%Y年%m月%d')
date_version = date_obj.strftime('%Y.%m.%d')
print(f"成功读取本地隐私协议文件: {path}, 版本: {date_version}")
return content, date_version, ""
except ValueError:
print(f"本地隐私协议日期格式解析错误: {path}")
else:
print(f"本地隐私协议未找到更新日期: {path}")
except Exception as e:
print(f"读取本地隐私协议失败 {path}: {str(e)}")
# 所有路径都尝试失败,使用默认版本
return PRIVACY_POLICY_BRIEF, PRIVACY_POLICY_VERSION, "无法读取本地隐私协议文件"

View File

@@ -152,6 +152,7 @@ class MainWindow(QMainWindow):
self.animator.animation_finished.connect(self.on_animations_finished) self.animator.animation_finished.connect(self.on_animations_finished)
self.animator.start_animations() self.animator.start_animations()
# 在动画开始时获取云端配置
self.fetch_cloud_config() self.fetch_cloud_config()
def on_animations_finished(self): def on_animations_finished(self):
@@ -186,7 +187,7 @@ class MainWindow(QMainWindow):
self.install_button_enabled = enabled self.install_button_enabled = enabled
def fetch_cloud_config(self): def fetch_cloud_config(self):
"""获取云端配置""" """获取云端配置(异步方式)"""
self.config_manager.fetch_cloud_config( self.config_manager.fetch_cloud_config(
lambda url, headers, debug_mode, parent=None: ConfigFetchThread(url, headers, debug_mode, self), lambda url, headers, debug_mode, parent=None: ConfigFetchThread(url, headers, debug_mode, self),
self.on_config_fetched self.on_config_fetched
@@ -223,6 +224,9 @@ class MainWindow(QMainWindow):
self.cloud_config = self.config_manager.get_cloud_config() self.cloud_config = self.config_manager.get_cloud_config()
self.config_valid = self.config_manager.is_config_valid() self.config_valid = self.config_manager.is_config_valid()
self.last_error_message = self.config_manager.get_last_error() self.last_error_message = self.config_manager.get_last_error()
# 重新启用窗口,恢复用户交互
self.setEnabled(True)
def toggle_debug_mode(self, checked): def toggle_debug_mode(self, checked):
"""切换调试模式 """切换调试模式

View File

@@ -1,4 +1,7 @@
from .helpers import censor_url from .helpers import censor_url
import logging
import os
from data.config import CACHE
class Logger: class Logger:
def __init__(self, filename, stream): def __init__(self, filename, stream):
@@ -16,4 +19,46 @@ class Logger:
self.log.flush() self.log.flush()
def close(self): def close(self):
self.log.close() self.log.close()
def setup_logger(name):
"""设置并返回一个命名的logger
Args:
name: logger的名称
Returns:
logging.Logger: 配置好的logger对象
"""
# 创建logger
logger = logging.getLogger(name)
# 避免重复添加处理器
if logger.hasHandlers():
return logger
logger.setLevel(logging.DEBUG)
# 确保日志目录存在
log_dir = os.path.join(CACHE, "logs")
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, f"{name}.log")
# 创建文件处理器
file_handler = logging.FileHandler(log_file, encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 创建格式器并添加到处理器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# 添加处理器到logger
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger