mirror of
https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT.git
synced 2025-12-22 06:48:35 +00:00
feat(core): 优化隐私协议管理并添加日志功能
- 重构 PrivacyManager 类,增加隐私协议版本检查和用户同意状态管理 - 在主窗口初始化时获取云端配置,提高效率 - 添加日志功能,记录应用启动、隐私协议加载等关键事件 - 优化错误处理和用户提示信息
This commit is contained in:
19
PRIVACY.md
19
PRIVACY.md
@@ -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. 用户控制
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"""
|
||||||
|
|||||||
@@ -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, "无法读取本地隐私协议文件"
|
||||||
@@ -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):
|
||||||
"""切换调试模式
|
"""切换调试模式
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user