11 Commits

Author SHA1 Message Date
欧阳淇淇
803f32b567 ci(build-release): 放宽触发条件以支持任意标签
将 GitHub Actions 工作流的触发条件从仅限 'v*' 标签更改为
任意标签都会触发构建,提高发布流程的灵活性。
2025-12-17 23:46:50 +08:00
欧阳淇淇
ca6ef6443b ci(build-release): 添加图标文件验证步骤并修复图标路径配置
在构建发布工作流中添加了图标文件存在性验证步骤,确保构建时图标文件存在。
同时修改了 build.spec 文件中的图标路径配置方式,使用相对路径替代原有的绝对路径拼接方式,
以提高配置的可靠性和可维护性。
2025-12-17 23:43:56 +08:00
欧阳淇淇
ef7763dea0 refactor(extraction_thread): 优化压缩包内容分析的日志输出格式
将文件类型判断逻辑提取为变量,提高代码可读性。
2025-12-17 23:37:34 +08:00
欧阳淇淇
4b170b14f4 build(spec): 更新构建配置以包含新的模块和PySide6依赖
新增对 core.managers、core.handlers 和 ui.components 模块的子模块收集,
并移除旧的 handlers 模块引用。同时添加 PySide6 相关的隐藏导入依赖,
确保打包时包含必要的 Qt 模块。
2025-12-17 23:29:49 +08:00
欧阳淇淇
7f8e94fc25 build(gitignore): 添加 source/build.spec 到版本控制
新增 PyInstaller 构建配置文件 build.spec,用于定义应用打包时的模块、资源及可执行文件属性。
同时更新 .gitignore 以确保该配置文件被纳入版本管理。
2025-12-17 23:23:32 +08:00
欧阳淇淇
ba653b3470 build(workflow): 简化PyInstaller构建命令
将GitHub工作流中的PyInstaller构建步骤从冗长的手动参数调用改为使用build.spec配置文件,
以提高构建脚本的可维护性和清晰度。
2025-12-17 23:18:55 +08:00
欧阳淇淇
b1807abff7 build(workflow): 更新构建工作流配置以包含新增模块和资源
在 PyInstaller 构建命令中添加了新的数据目录 (bin, ui) 和多个隐藏导入模块,
包括 workers、core、handlers、utils 和 ui 等子模块。同时使用 --collect-submodules
选项确保所有相关子模块被正确收集,以解决打包后可能缺失依赖的问题。
2025-12-17 23:13:23 +08:00
欧阳淇淇
75ac82cb41 feat(ui): 为多个按钮文本添加颜色样式
在Ui_install.py中为“开始安装”、“禁/启用补丁”、“卸载补丁”和“退出程序”按钮
文本添加了颜色样式(color: #3333),以提升界面视觉效果。
2025-12-17 23:04:36 +08:00
欧阳淇淇
e75f52ab9c feat(workflow): 添加构建和发布 GitHub Actions 工作流
新增一个 GitHub Actions 配置文件,用于在推送版本标签时自动构建并发布应用。
该工作流使用 PyInstaller 打包 Windows 可执行文件,并上传为 Release 资源。
2025-12-17 22:45:49 +08:00
hyb-oyqq
adcd6da5b4 feat(core): 增强工作模式菜单状态同步功能
- 在主窗口和离线模式管理器中添加工作模式菜单状态同步逻辑,确保UI状态与实际工作模式一致。
- 在UI管理器中实现同步方法,提升菜单状态更新的可靠性和兼容性。
- 优化代码结构,确保在菜单创建后立即同步状态,增强用户体验。
2025-08-15 14:06:22 +08:00
hyb-oyqq
d7ceb71607 feat(privacy): 更新隐私政策,增加离线模式说明
- 在隐私政策中新增离线模式的相关说明,明确应用在离线状态下的行为。
- 更新隐私政策版本号至2025年8月15日。
2025-08-15 11:05:15 +08:00
12 changed files with 211 additions and 12 deletions

65
.github/workflows/build-release.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: Build and Release
on:
push:
tags:
- '*' # 任意 tag 都会触发构建
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: 'source/requirements.txt'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r source/requirements.txt
- name: Get version from tag
id: get_version
run: |
$version = "${{ github.ref_name }}"
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
- name: Verify icon file
run: |
cd source
if (Test-Path "assets/images/ICO/icon.ico") {
Write-Host "Icon file found"
Get-Item "assets/images/ICO/icon.ico"
} else {
Write-Host "Icon file NOT found!"
exit 1
}
- name: Build with PyInstaller
run: |
cd source
pyinstaller --noconfirm build.spec
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: release-${{ steps.get_version.outputs.VERSION }}
path: source/dist/FRAISEMOE_Addons_Installer_NEXT.exe
- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: release-${{ steps.get_version.outputs.VERSION }}
files: source/dist/FRAISEMOE_Addons_Installer_NEXT.exe
draft: false
prerelease: false
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -31,6 +31,7 @@ MANIFEST
# before PyInstaller builds the exe, so as to inject date/other infos into it. # before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest *.manifest
*.spec *.spec
!source/build.spec
# Installer logs # Installer logs
pip-log.txt pip-log.txt

View File

@@ -21,6 +21,10 @@
- 游戏安装路径:用于识别已安装的游戏和安装补丁 - 游戏安装路径:用于识别已安装的游戏和安装补丁
- 文件哈希值:用于验证文件完整性 - 文件哈希值:用于验证文件完整性
### 2.4 离线模式和本地文件
- **离线模式**:本应用提供离线模式。在离线模式下,应用不会进行任何网络连接,包括检查更新、获取云端配置或进行任何网络相关的测试,安装过程将只使用本地文件。
- **本地文件使用**:为了支持离线安装,本应用会扫描其所在目录下的压缩包,以查找用于安装的补丁压缩包。此文件扫描操作仅限于应用所在的文件夹,不会访问或修改您系统中的其他文件。
## 3. 信息使用 ## 3. 信息使用
我们收集的信息仅用于以下目的: 我们收集的信息仅用于以下目的:
@@ -88,4 +92,4 @@
本隐私政策可能会根据应用功能的变化而更新。请定期查看最新版本。 本隐私政策可能会根据应用功能的变化而更新。请定期查看最新版本。
最后更新日期2025年8月4 最后更新日期2025年8月15

73
source/build.spec Normal file
View File

@@ -0,0 +1,73 @@
# -*- mode: python ; coding: utf-8 -*-
import os
import sys
from PyInstaller.utils.hooks import collect_submodules, collect_data_files
block_cipher = None
# 收集所有子模块
hiddenimports = []
hiddenimports += collect_submodules('workers')
hiddenimports += collect_submodules('core')
hiddenimports += collect_submodules('core.managers')
hiddenimports += collect_submodules('core.handlers')
hiddenimports += collect_submodules('ui')
hiddenimports += collect_submodules('ui.components')
hiddenimports += collect_submodules('utils')
hiddenimports += collect_submodules('config')
# PySide6 相关隐藏导入
hiddenimports += ['PySide6.QtCore', 'PySide6.QtGui', 'PySide6.QtWidgets']
a = Analysis(
['Main.py'],
pathex=['.'],
binaries=[],
datas=[
('assets', 'assets'),
('data', 'data'),
('config', 'config'),
('bin', 'bin'),
('ui', 'ui'),
('workers', 'workers'),
('core', 'core'),
('utils', 'utils'),
],
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='FRAISEMOE_Addons_Installer_NEXT',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=os.path.join('assets', 'images', 'ICO', 'icon.ico'),
)

View File

@@ -4,12 +4,12 @@ import datetime
# 配置信息 # 配置信息
app_data = { app_data = {
"APP_VERSION": "1.4.0", "APP_VERSION": "1.4.2",
"APP_NAME": "FRAISEMOE Addons Installer NEXT", "APP_NAME": "FRAISEMOE Addons Installer NEXT",
"TEMP": "TEMP", "TEMP": "TEMP",
"CACHE": "FRAISEMOE", "CACHE": "FRAISEMOE",
"PLUGIN": "PLUGIN", "PLUGIN": "PLUGIN",
"CONFIG_URL": "aHR0cHM6Ly9hcGkuMncyLnRvcC9hcGkvb3V5YW5ncWlxaS9uZWtvcGFyYS9kb3dubG9hZF91cmwuanNvbg==", "CONFIG_URL": "aHR0cHM6Ly9uZWtvcGFyYS1hcGkub3ZvZmlzaC5jb20vYXBpL291eWFuZ3FpcWkvbmVrb3BhcmEvZG93bmxvYWRfdXJsLmpzb24=",
"UA_TEMPLATE": "Mozilla/5.0 (Linux debian12 FraiseMoe2-Accept-Next) Gecko/20100101 Firefox/114.0 FraiseMoe2/{}", "UA_TEMPLATE": "Mozilla/5.0 (Linux debian12 FraiseMoe2-Accept-Next) Gecko/20100101 Firefox/114.0 FraiseMoe2/{}",
"game_info": { "game_info": {
"NEKOPARA Vol.1": { "NEKOPARA Vol.1": {

View File

@@ -20,6 +20,7 @@ PRIVACY_POLICY_BRIEF = """
- **系统信息**:程序版本号。 - **系统信息**:程序版本号。
- **网络信息**IP 地址、ISP、地理位置用于使用统计、下载统计、IPv6 连接测试(通过访问 testipv6.cn、IPv6 地址获取(通过 ipw.cn - **网络信息**IP 地址、ISP、地理位置用于使用统计、下载统计、IPv6 连接测试(通过访问 testipv6.cn、IPv6 地址获取(通过 ipw.cn
- **文件信息**:游戏安装路径、文件哈希值。 - **文件信息**:游戏安装路径、文件哈希值。
- **离线模式**:在离线模式下,本应用不会进行任何网络活动,仅使用本地文件进行安装。为实现此功能,应用会扫描其所在目录下的压缩包文件。
## 系统修改 ## 系统修改
- 使用 Cloudflare 加速时会临时修改系统 hosts 文件。 - 使用 Cloudflare 加速时会临时修改系统 hosts 文件。
@@ -43,6 +44,7 @@ This application collects and processes the following information:
- **System info**: Application version. - **System info**: Application version.
- **Network info**: IP address, ISP, geographic location (for usage statistics), download statistics, IPv6 connectivity test (via testipv6.cn), IPv6 address acquisition (via ipw.cn). - **Network info**: IP address, ISP, geographic location (for usage statistics), download statistics, IPv6 connectivity test (via testipv6.cn), IPv6 address acquisition (via ipw.cn).
- **File info**: Game installation paths, file hash values. - **File info**: Game installation paths, file hash values.
- **Offline Mode**: In offline mode, the application will not perform any network activities and will only use local files for installation. To achieve this, the application scans for compressed files in its directory.
## System Modifications ## System Modifications
- Temporarily modifies system hosts file when using Cloudflare acceleration. - Temporarily modifies system hosts file when using Cloudflare acceleration.
@@ -57,7 +59,7 @@ The complete privacy policy can be found in the program's GitHub repository.
""" """
# 默认隐私协议版本 - 本地版本的日期 # 默认隐私协议版本 - 本地版本的日期
PRIVACY_POLICY_VERSION = "2025.08.04" PRIVACY_POLICY_VERSION = "2025.08.15"
def get_local_privacy_policy(): def get_local_privacy_policy():
"""获取本地打包的隐私协议文件 """获取本地打包的隐私协议文件

View File

@@ -204,9 +204,14 @@ class OfflineModeManager:
# 同步更新UI菜单中的模式选择状态 # 同步更新UI菜单中的模式选择状态
if hasattr(self.main_window, 'ui_manager'): if hasattr(self.main_window, 'ui_manager'):
ui_manager = self.main_window.ui_manager ui_manager = self.main_window.ui_manager
if hasattr(ui_manager, 'online_mode_action') and hasattr(ui_manager, 'offline_mode_action'): # 使用专门的同步方法确保菜单状态正确更新
ui_manager.online_mode_action.setChecked(not enabled) if hasattr(ui_manager, 'sync_work_mode_menu_state'):
ui_manager.offline_mode_action.setChecked(enabled) ui_manager.sync_work_mode_menu_state()
elif hasattr(ui_manager, 'online_mode_action') and hasattr(ui_manager, 'offline_mode_action'):
# 兼容旧版本的直接设置方式
if ui_manager.online_mode_action and ui_manager.offline_mode_action:
ui_manager.online_mode_action.setChecked(not enabled)
ui_manager.offline_mode_action.setChecked(enabled)
# 记录离线模式状态变化 # 记录离线模式状态变化
logger.debug(f"离线模式已{'启用' if enabled else '禁用'}") logger.debug(f"离线模式已{'启用' if enabled else '禁用'}")

View File

@@ -63,6 +63,13 @@ class UIManager:
self.disable_auto_restore_action = self.menu_builder.disable_auto_restore_action self.disable_auto_restore_action = self.menu_builder.disable_auto_restore_action
self.disable_pre_hash_action = self.menu_builder.disable_pre_hash_action self.disable_pre_hash_action = self.menu_builder.disable_pre_hash_action
# 保存对工作模式菜单项的引用,确保能正确同步状态
self.online_mode_action = self.menu_builder.online_mode_action
self.offline_mode_action = self.menu_builder.offline_mode_action
# 在菜单创建完成后,强制同步一次工作模式状态
self.sync_work_mode_menu_state()
# 为了向后兼容性,添加委托方法 # 为了向后兼容性,添加委托方法
def create_progress_window(self, title, initial_text="准备中..."): def create_progress_window(self, title, initial_text="准备中..."):
"""创建进度窗口委托给dialog_factory""" """创建进度窗口委托给dialog_factory"""
@@ -84,6 +91,39 @@ class UIManager:
"""显示菜单委托给menu_builder""" """显示菜单委托给menu_builder"""
return self.menu_builder.show_menu(menu, button) return self.menu_builder.show_menu(menu, button)
def sync_work_mode_menu_state(self):
"""同步工作模式菜单状态,确保菜单选择状态与实际工作模式一致"""
try:
# 检查是否有离线模式管理器和菜单项
if not hasattr(self.main_window, 'offline_mode_manager') or not self.main_window.offline_mode_manager:
return
if not hasattr(self, 'online_mode_action') or not hasattr(self, 'offline_mode_action'):
return
if not self.online_mode_action or not self.offline_mode_action:
return
# 获取当前离线模式状态
is_offline_mode = self.main_window.offline_mode_manager.is_in_offline_mode()
# 同步菜单选择状态
self.online_mode_action.setChecked(not is_offline_mode)
self.offline_mode_action.setChecked(is_offline_mode)
# 记录同步操作(仅在调试模式下)
if hasattr(self.main_window, 'config') and self.main_window.config.get('debug_mode', False):
from utils.logger import setup_logger
logger = setup_logger("ui_manager")
logger.debug(f"已同步工作模式菜单状态: 离线模式={is_offline_mode}")
except Exception as e:
# 静默处理异常,避免影响程序正常运行
if hasattr(self.main_window, 'config') and self.main_window.config.get('debug_mode', False):
from utils.logger import setup_logger
logger = setup_logger("ui_manager")
logger.debug(f"同步工作模式菜单状态时出错: {e}")

View File

@@ -207,6 +207,10 @@ class MainWindow(QMainWindow):
else: else:
self.window_manager.change_window_state(self.window_manager.STATE_ERROR) self.window_manager.change_window_state(self.window_manager.STATE_ERROR)
# 确保工作模式菜单状态与实际状态同步
if hasattr(self, 'ui_manager') and hasattr(self.ui_manager, 'sync_work_mode_menu_state'):
self.ui_manager.sync_work_mode_menu_state()
def set_start_button_enabled(self, enabled, installing=False): def set_start_button_enabled(self, enabled, installing=False):
"""[过渡方法] 设置按钮状态将调用委托给WindowManager """[过渡方法] 设置按钮状态将调用委托给WindowManager

View File

@@ -410,7 +410,7 @@ class Ui_MainWindows(object):
self.start_install_text.setText("开始安装") self.start_install_text.setText("开始安装")
self.start_install_text.setFont(self.custom_font) self.start_install_text.setFont(self.custom_font)
self.start_install_text.setAlignment(Qt.AlignmentFlag.AlignCenter) self.start_install_text.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.start_install_text.setStyleSheet("letter-spacing: 1px;") self.start_install_text.setStyleSheet("color: #333333; letter-spacing: 1px;")
# 点击区域透明按钮 # 点击区域透明按钮
self.start_install_btn = QPushButton(self.button_container) self.start_install_btn = QPushButton(self.button_container)
@@ -444,7 +444,7 @@ class Ui_MainWindows(object):
self.toggle_patch_text.setText("禁/启用补丁") self.toggle_patch_text.setText("禁/启用补丁")
self.toggle_patch_text.setFont(self.custom_font) self.toggle_patch_text.setFont(self.custom_font)
self.toggle_patch_text.setAlignment(Qt.AlignmentFlag.AlignCenter) self.toggle_patch_text.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.toggle_patch_text.setStyleSheet("letter-spacing: 1px;") self.toggle_patch_text.setStyleSheet("color: #333333; letter-spacing: 1px;")
# 点击区域透明按钮 # 点击区域透明按钮
self.toggle_patch_btn = QPushButton(self.toggle_patch_container) self.toggle_patch_btn = QPushButton(self.toggle_patch_container)
@@ -478,7 +478,7 @@ class Ui_MainWindows(object):
self.uninstall_text.setText("卸载补丁") self.uninstall_text.setText("卸载补丁")
self.uninstall_text.setFont(self.custom_font) self.uninstall_text.setFont(self.custom_font)
self.uninstall_text.setAlignment(Qt.AlignmentFlag.AlignCenter) self.uninstall_text.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.uninstall_text.setStyleSheet("letter-spacing: 1px;") self.uninstall_text.setStyleSheet("color: #333333; letter-spacing: 1px;")
# 点击区域透明按钮 # 点击区域透明按钮
self.uninstall_btn = QPushButton(self.uninstall_container) self.uninstall_btn = QPushButton(self.uninstall_container)
@@ -513,7 +513,7 @@ class Ui_MainWindows(object):
self.exit_text.setText("退出程序") self.exit_text.setText("退出程序")
self.exit_text.setFont(self.custom_font) self.exit_text.setFont(self.custom_font)
self.exit_text.setAlignment(Qt.AlignmentFlag.AlignCenter) self.exit_text.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.exit_text.setStyleSheet("letter-spacing: 1px;") self.exit_text.setStyleSheet("color: #333333; letter-spacing: 1px;")
# 点击区域透明按钮 # 点击区域透明按钮
self.exit_btn = QPushButton(self.exit_container) self.exit_btn = QPushButton(self.exit_container)

View File

@@ -105,7 +105,9 @@ class ExtractionThread(QThread):
debug_logger.debug(f"压缩包内容分析:") debug_logger.debug(f"压缩包内容分析:")
debug_logger.debug(f"- 文件总数: {len(file_list)}") debug_logger.debug(f"- 文件总数: {len(file_list)}")
for i, f in enumerate(file_list): for i, f in enumerate(file_list):
debug_logger.debug(f" {i+1}. {f} - 类型: {'文件夹' if f.endswith('/') or f.endswith('\\') else '文件'}") is_folder = f.endswith('/') or f.endswith('\\')
file_type = '文件夹' if is_folder else '文件'
debug_logger.debug(f" {i+1}. {f} - 类型: {file_type}")
update_progress(20, f"正在分析 {self.game_version} 的补丁文件...") update_progress(20, f"正在分析 {self.game_version} 的补丁文件...")

View File

@@ -129,6 +129,9 @@ class HashThread(QThread):
logger.debug(f"DEBUG: 哈希后检查 - {game_version} 补丁文件不存在: {install_path}") logger.debug(f"DEBUG: 哈希后检查 - {game_version} 补丁文件不存在: {install_path}")
continue continue
# 设置当前处理的游戏版本
result["game"] = game_version
try: try:
expected_hash = self.plugin_hash.get(game_version, "") expected_hash = self.plugin_hash.get(game_version, "")
if not expected_hash: if not expected_hash: