diff --git a/.gitignore b/.gitignore index a9ae564..e525598 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,9 @@ __pycache__/ *.pyo *.egg-info/ dist/ -build/ +# PyInstaller output (在build目录内),但保留构建脚本 +build/build/ +build/*.log # Virtual environment venv/ diff --git a/build/build.bat b/build/build.bat new file mode 100644 index 0000000..fcf3cc4 --- /dev/null +++ b/build/build.bat @@ -0,0 +1,117 @@ +@echo off +setlocal enabledelayedexpansion + +REM === WindowMsgBot 构建脚本 === +REM 自动化打包和签名流程 +REM 用法: build.bat /skip-cert /skip-sign +REM build.bat --skip-cert --skip-sign (PowerShell兼容) +REM build.bat skip-cert skip-sign (无前缀) + +set "SCRIPT_DIR=%~dp0" +set "PROJECT_DIR=%SCRIPT_DIR%.." +set "PYTHON=%PROJECT_DIR%\.venv311\Scripts\python.exe" +set "PYINSTALLER=%PYTHON% -m PyInstaller" + +set "SKIP_CERT=0" +set "SKIP_SIGN=0" + +REM 解析参数(兼容 / -- 无前缀 三种写法) +:parse_args +if "%~1"=="" goto :start_build +set "_arg=%~1" +set "_arg=!_arg:/=!" +set "_arg=!_arg:--=!" +if "!_arg!"=="skip-cert" set "SKIP_CERT=1" +if "!_arg!"=="skip-sign" set "SKIP_SIGN=1" +shift +goto :parse_args + +:start_build +echo === WindowMsgBot 构建脚本 === +echo 项目目录: %PROJECT_DIR% +echo Python: %PYTHON% +echo. + +REM 检查Python +if not exist "%PYTHON%" ( + echo 错误: Python不存在: %PYTHON% + echo 请先创建虚拟环境 + pause + exit /b 1 +) + +REM 检查PyInstaller +"%PYTHON%" -c "import PyInstaller" >nul 2>&1 +if %errorlevel% neq 0 ( + echo 安装PyInstaller... + "%PYTHON%" -m pip install pyinstaller +) + +REM 1. 生成证书(可选) +if "%SKIP_CERT%"=="0" ( + echo 1. 生成自签名证书... + if exist "%SCRIPT_DIR%code_signing_cert.pfx" ( + echo 证书已存在,跳过生成 + ) else ( + powershell -ExecutionPolicy Bypass -File "%SCRIPT_DIR%generate_cert.ps1" + if %errorlevel% neq 0 ( + echo ⚠ 证书生成失败,继续构建... + ) + ) +) else ( + echo 1. 跳过证书生成 +) + +REM 2. 清理旧构建(只清理PyInstaller输出,保留build目录里的spec和hooks) +echo. +echo 2. 清理旧构建文件... +if exist "%PROJECT_DIR%\build\build" rmdir /s /q "%PROJECT_DIR%\build\build" +if exist "%PROJECT_DIR%\dist" rmdir /s /q "%PROJECT_DIR%\dist" + +REM 3. PyInstaller打包 +echo. +echo 3. PyInstaller打包... + +REM 禁用 CUDA 防止 PyInstaller isolated 子进程 access violation 崩溃 +set "PYTORCH_NO_CUDA_DISPATCH=1" +set "CUDA_VISIBLE_DEVICES=" + +cd /d "%PROJECT_DIR%" +"%PYINSTALLER%" "%SCRIPT_DIR%windowmsgbot.spec" --noconfirm --clean +if %errorlevel% neq 0 ( + echo 错误: PyInstaller打包失败! + pause + exit /b 1 +) +echo ✓ 打包完成 + +REM 4. 签名(可选) +if "%SKIP_SIGN%"=="0" ( + echo. + echo 4. 签名exe文件... + if exist "%SCRIPT_DIR%code_signing_cert.pfx" ( + call "%SCRIPT_DIR%sign.bat" + ) else ( + echo ⚠ 证书不存在,跳过签名 + ) +) else ( + echo. + echo 4. 跳过签名 +) + +REM 5. 输出结果 +echo. +echo 5. 构建结果... +if exist "%PROJECT_DIR%\dist\WindowMsgBot\WindowMsgBot.exe" ( + echo ✓ 可执行文件: %PROJECT_DIR%\dist\WindowMsgBot\WindowMsgBot.exe + for %%A in ("%PROJECT_DIR%\dist\WindowMsgBot\WindowMsgBot.exe") do echo 大小: %%~zA 字节 + echo ✓ 输出目录: %PROJECT_DIR%\dist\WindowMsgBot\ +) else ( + echo ✗ 可执行文件未生成 +) + +echo. +echo === 构建完成 === +echo 输出目录: %PROJECT_DIR%\dist +echo. +pause \ No newline at end of file diff --git a/build/generate_cert.ps1 b/build/generate_cert.ps1 new file mode 100644 index 0000000..1bdeca1 --- /dev/null +++ b/build/generate_cert.ps1 @@ -0,0 +1,105 @@ +# 生成自签名代码签名证书 - 降低360敏感度 +# 使用方法: 以管理员身份运行 PowerShell, 执行 .\generate_cert.ps1 + +param( + [string]$CertPassword = "WindowMsgBot2026", + [string]$CertSubject = "CN=WindowMsgBot, O=WindowMsgBot Development", + [int]$ValidYears = 5 +) + +Write-Host "=== 生成自签名代码签名证书 ===" -ForegroundColor Cyan + +# 1. 生成证书 +Write-Host "1. 生成自签名证书..." -ForegroundColor Yellow +$cert = New-SelfSignedCertificate ` + -Subject $CertSubject ` + -Type CodeSigningCert ` + -CertStoreLocation "Cert:\CurrentUser\My" ` + -NotAfter (Get-Date).AddYears($ValidYears) ` + -KeyAlgorithm RSA ` + -KeyLength 2048 ` + -HashAlgorithm SHA256 ` + -FriendlyName "WindowMsgBot Code Signing Certificate" + +if (-not $cert) { + Write-Error "证书生成失败!" + exit 1 +} + +Write-Host " 证书指纹: $($cert.Thumbprint)" -ForegroundColor Green + +# 2. 导出PFX文件 +Write-Host "2. 导出PFX文件..." -ForegroundColor Yellow +$pfxPath = Join-Path $PSScriptRoot "code_signing_cert.pfx" +$password = ConvertTo-SecureString -String $CertPassword -Force -AsPlainText + +Export-PfxCertificate ` + -Cert $cert ` + -FilePath $pfxPath ` + -Password $password + +Write-Host " PFX已保存: $pfxPath" -ForegroundColor Green + +# 3. 导出CER文件(公钥) +Write-Host "3. 导出CER公钥文件..." -ForegroundColor Yellow +$cerPath = Join-Path $PSScriptRoot "code_signing_cert.cer" +Export-Certificate ` + -Cert $cert ` + -FilePath $cerPath + +Write-Host " CER已保存: $cerPath" -ForegroundColor Green + +# 4. 安装到受信任的根证书颁发机构 +Write-Host "4. 安装到受信任的根证书颁发机构..." -ForegroundColor Yellow +$store = New-Object System.Security.Cryptography.X509Certificates.X509Store( + "Root", "LocalMachine") +$store.Open("ReadWrite") +$store.Add($cert) +$store.Close() + +Write-Host " 证书已安装到受信任的根证书存储" -ForegroundColor Green + +# 5. 验证安装 +Write-Host "5. 验证证书安装..." -ForegroundColor Yellow +$installed = Get-ChildItem -Path "Cert:\LocalMachine\Root" | + Where-Object { $_.Thumbprint -eq $cert.Thumbprint } + +if ($installed) { + Write-Host " ✓ 证书已成功安装到受信任存储" -ForegroundColor Green +} else { + Write-Host " ⚠ 证书可能未安装到受信任存储" -ForegroundColor Yellow +} + +# 6. 输出证书信息 +Write-Host "`n=== 证书信息 ===" -ForegroundColor Cyan +Write-Host "主题: $($cert.Subject)" +Write-Host "指纹: $($cert.Thumbprint)" +Write-Host "有效期: $($cert.NotBefore) 至 $($cert.NotAfter)" +Write-Host "用途: $($cert.EnhancedKeyUsageList.FriendlyName -join ', ')" +Write-Host "`nPFX密码: $CertPassword" -ForegroundColor Yellow +Write-Host "请妥善保管PFX文件和密码!" -ForegroundColor Red + +# 7. 创建证书信息文件 +Write-Host "7. 创建证书信息文件..." -ForegroundColor Yellow +$infoPath = Join-Path $PSScriptRoot "cert_info.txt" +@" +证书信息 +======== +主题: $($cert.Subject) +指纹: $($cert.Thumbprint) +有效期: $($cert.NotBefore) 至 $($cert.NotAfter) +PFX文件: code_signing_cert.pfx +CER文件: code_signing_cert.cer +PFX密码: $CertPassword + +使用说明 +======== +1. PFX文件用于signtool签名 +2. CER文件可分发给其他人验证签名 +3. 证书已安装到本机受信任存储 +"@ | Out-File -FilePath $infoPath -Encoding UTF8 + +Write-Host " 证书信息已保存: $infoPath" -ForegroundColor Green + +Write-Host "`n=== 完成 ===" -ForegroundColor Cyan +Write-Host "下一步: 使用 sign.bat 脚本签名exe文件" -ForegroundColor Yellow \ No newline at end of file diff --git a/build/hooks/hook-easyocr.py b/build/hooks/hook-easyocr.py new file mode 100644 index 0000000..437ae23 --- /dev/null +++ b/build/hooks/hook-easyocr.py @@ -0,0 +1,20 @@ +"""PyInstaller hook: 避免 easyocr 默认 hook 触发 collect_submodules('torch') 崩溃。 + +PyInstaller 默认 hook-easyocr.py 会调用 collect_submodules('easyocr'), +而 easyocr 内部 import torch,导致 isolated 子进程 access violation。 +本 hook 只列出 easyocr 实际需要的子模块,避开 torch 引用。 +""" +hiddenimports = [ + 'easyocr', + 'easyocr.easyocr', + 'easyocr.config', + 'easyocr.detection', + 'easyocr.recognition', + 'easyocr.utils', + 'easyocr.craft', + 'easyocr.craft_utils', + 'easyocr.imgproc', + 'easyocr.dataset', + 'easyocr.model', + 'easyocr.model.vgg_model', +] diff --git a/build/hooks/hook-torch.py b/build/hooks/hook-torch.py new file mode 100644 index 0000000..698c158 --- /dev/null +++ b/build/hooks/hook-torch.py @@ -0,0 +1,41 @@ +"""PyInstaller hook: 避免 collect_submodules('torch') 在 isolated 子进程中 access violation 崩溃。 + +PyInstaller 默认 hook-torch.py 调用 collect_submodules('torch') 遍历全部子模块, +在 torch 2.5+ 的 isolated 子进程中会触发 0xC0000005 access violation。 +本 hook 直接列出实际需要的 torch 子模块,完全避开 collect_submodules。 +""" +# 只列出 bot 实际引用的 torch 子模块(通过 import 分析确认) +hiddenimports = [ + 'torch', + 'torch.nn', + 'torch.nn.functional', + 'torch.nn.modules', + 'torch.nn.modules.activation', + 'torch.nn.modules.conv', + 'torch.nn.modules.linear', + 'torch.nn.modules.module', + 'torch.nn.modules.normalization', + 'torch.nn.modules.padding', + 'torch.nn.modules.pooling', + 'torch.nn.modules.rnn', + 'torch.nn.modules.sparse', + 'torch.nn.modules.dropout', + 'torch.nn.modules.batchnorm', + 'torch.nn.modules.loss', + 'torch.nn.modules.container', + 'torch.nn.modules.flatten', + 'torch.nn.modules.pixelshuffle', + 'torch.nn.modules.upsampling', + 'torch.nn.modules.channelshuffle', + 'torch.cuda', + 'torch.cuda.amp', + 'torch.tensor', + 'torch._C', + 'torch._utils', + 'torch.storage', + 'torch.serialization', + 'torch.types', + 'torch.classes', + 'torch.backends', + 'torch.backends.cudnn', +] diff --git a/build/sign.bat b/build/sign.bat new file mode 100644 index 0000000..bebb380 --- /dev/null +++ b/build/sign.bat @@ -0,0 +1,119 @@ +@echo off +setlocal enabledelayedexpansion + +REM === WindowMsgBot 签名脚本 === +REM 使用自签名证书签名exe文件,降低360敏感度 +REM 用法: sign.bat [exe路径] + +set "SCRIPT_DIR=%~dp0" +set "PROJECT_DIR=%SCRIPT_DIR%.." +set "CERT_PFX=%SCRIPT_DIR%code_signing_cert.pfx" +set "CERT_PASS=WindowMsgBot2026" +set "OUTPUT_DIR=%PROJECT_DIR%\dist" + +REM 如果没有指定exe文件,查找默认的exe(onedir模式在子目录中) +if "%~1"=="" ( + if exist "%OUTPUT_DIR%\WindowMsgBot\WindowMsgBot.exe" ( + set "EXE_PATH=%OUTPUT_DIR%\WindowMsgBot\WindowMsgBot.exe" + ) else ( + set "EXE_PATH=%OUTPUT_DIR%\WindowMsgBot.exe" + ) +) else ( + set "EXE_PATH=%~1" +) + +echo === WindowMsgBot 代码签名 === +echo. + +REM 检查exe文件是否存在 +if not exist "%EXE_PATH%" ( + echo 错误: exe文件不存在: %EXE_PATH% + echo 请先运行 PyInstaller 构建: pyinstaller build/windowmsgbot.spec + pause + exit /b 1 +) + +REM 检查证书文件是否存在 +if not exist "%CERT_PFX%" ( + echo 错误: 证书文件不存在: %CERT_PFX% + echo 请先运行证书生成脚本: powershell -ExecutionPolicy Bypass -File build\generate_cert.ps1 + pause + exit /b 1 +) + +REM 查找signtool.exe +set "SIGNTOOL=" +for %%i in ( + "%ProgramFiles%\Windows Kits\10\bin\x64\signtool.exe" + "%ProgramFiles(x86)%\Windows Kits\10\bin\x64\signtool.exe" + "%ProgramFiles%\Windows Kits\10\bin\x86\signtool.exe" + "%ProgramFiles(x86)%\Windows Kits\10\bin\x86\signtool.exe" + "%ProgramFiles%\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\signtool.exe" +) do ( + if exist %%i ( + set "SIGNTOOL=%%~i" + goto :found_sign + ) +) + +REM 尝试从PATH查找 +where signtool.exe >nul 2>&1 +if %errorlevel% equ 0 ( + set "SIGNTOOL=signtool.exe" + goto :found_sign +) + +echo 错误: 未找到 signtool.exe +echo 请安装 Windows SDK 或 Windows Kits +echo 下载地址: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/ +pause +exit /b 1 + +:found_sign +echo 使用signtool: %SIGNTOOL% +echo. + +REM 1. 签名exe +echo 1. 签名exe文件... +"%SIGNTOOL%" sign /f "%CERT_PFX%" /p "%CERT_PASS%" /tr http://timestamp.digicert.com /td sha256 /fd sha256 "%EXE_PATH%" +if %errorlevel% neq 0 ( + echo 错误: 签名失败! + pause + exit /b 1 +) +echo ✓ 签名成功 + +REM 2. 验证签名 +echo. +echo 2. 验证签名... +"%SIGNTOOL%" verify /pa "%EXE_PATH%" +if %errorlevel% equ 0 ( + echo ✓ 签名验证通过 +) else ( + echo ⚠ 签名验证失败,但签名可能已应用 +) + +REM 3. 显示签名信息 +echo. +echo 3. 签名信息... +"%SIGNTOOL%" verify /v "%EXE_PATH%" 2>nul | findstr /i "Signing" +echo. + +REM 4. 复制签名后的exe到项目根目录 +echo 4. 复制签名后的exe... +copy "%EXE_PATH%" "%PROJECT_DIR%\WindowMsgBot_signed.exe" >nul +if %errorlevel% equ 0 ( + echo ✓ 已复制到: %PROJECT_DIR%\WindowMsgBot_signed.exe +) else ( + echo ⚠ 复制失败 +) + +echo. +echo === 完成 === +echo 签名后的exe: %EXE_PATH% +echo 备份副本: %PROJECT_DIR%\WindowMsgBot_signed.exe +echo. +echo 注意: 自签名证书需要在目标机器上安装证书才能验证签名 +echo 证书文件: %CERT_PFX% +echo. +pause \ No newline at end of file diff --git a/build/windowmsgbot.spec b/build/windowmsgbot.spec new file mode 100644 index 0000000..41a9c59 --- /dev/null +++ b/build/windowmsgbot.spec @@ -0,0 +1,172 @@ +# -*- mode: python ; coding: utf-8 -*- +# WindowMsgBot PyInstaller spec文件 +# 用法: pyinstaller build/windowmsgbot.spec + +import os +import sys +from PyInstaller.utils.hooks import collect_submodules, collect_data_files + +# 项目根目录 +PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(SPEC))) + +# 收集所有需要的子模块 +hiddenimports = [ + 'uiautomation', + 'win32gui', + 'win32con', + 'win32api', + 'win32process', + 'psutil', + 'PIL', + 'PIL.Image', + 'PIL.ImageGrab', + 'PIL.ImageEnhance', + 'PIL.ImageFilter', + 'easyocr', + 'numpy', + 'torch', + 'torchvision', + 'sklearn', + 'sklearn.feature_extraction.text', + 'sklearn.metrics.pairwise', + 'jieba', + 'customtkinter', + 'comtypes', + 'comtypes.client', + 'win32com', + 'win32com.client', + 'pythoncom', + 'pywintypes', + 'ctypes', + 'ctypes.wintypes', + 'sqlite3', + 'json', + 'hashlib', + 'difflib', + 'unicodedata', + 'signal', + 'subprocess', + 'threading', + 'logging', + 'traceback', + 'faulthandler', + 're', + 'random', + 'datetime', + 'time', + 'sys', + 'os', + 'io', + 'math', + 'struct', + 'codecs', + 'encodings', + 'encodings.utf_8', + 'encodings.gbk', + 'encodings.gb2312', + 'encodings.gb18030', +] + +# 数据文件 +datas = [ + # 知识库目录 + (os.path.join(PROJECT_ROOT, '知识库'), '知识库'), + # 偏旁库 + (os.path.join(PROJECT_ROOT, 'unicode_radicals.md'), '.'), + # 配置文件 + (os.path.join(PROJECT_ROOT, 'config.json'), '.'), + # EasyOCR worker脚本(作为数据文件,运行时提取) + (os.path.join(PROJECT_ROOT, 'easyocr_worker.py'), '.'), + # 其他必要文件 + (os.path.join(PROJECT_ROOT, 'auto_reply.py'), '.'), + (os.path.join(PROJECT_ROOT, 'knowledge_base.py'), '.'), + (os.path.join(PROJECT_ROOT, 'config.py'), '.'), + (os.path.join(PROJECT_ROOT, 'gui.py'), '.'), +] + +# 收集easyocr数据文件 +easyocr_datas = collect_data_files('easyocr') +datas.extend(easyocr_datas) + +# 分析 +a = Analysis( + [os.path.join(PROJECT_ROOT, 'main.py')], + pathex=[PROJECT_ROOT], + binaries=[], + datas=datas, + hiddenimports=hiddenimports, + hookspath=[os.path.join(PROJECT_ROOT, 'build', 'hooks')], + hooksconfig={}, + runtime_hooks=[], + excludes=[ + 'tkinter.test', + 'unittest', + 'test', + 'distutils', + 'setuptools', + 'pip', + 'pkg_resources', + 'pydoc', + 'doctest', + 'torch.autograd', + 'torch.testing', + 'torch.distributed', + 'torch.fx', + 'torch.jit', + 'torch.onnx', + 'torch.profiler', + 'torch.sparse', + 'torch.utils.benchmark', + 'torch.utils.bottleneck', + 'torch.utils.checkpoint', + 'torch.utils.collect_env', + 'torch.utils.cpp_extension', + 'torch.utils.data', + 'torch.utils.deterministic', + 'torch.utils.dlpack', + 'torch.utils.file_baton', + 'torch.utils.hipify', + 'torch.utils.jit', + 'torch.utils.mobile_optimizer', + 'torch.utils.model_zoo', + 'torch.utils.show_pickle', + 'torch.utils.tensorboard', + 'torch.utils.throughput_benchmark', + ], + noarchive=False, +) + +# 过滤掉不必要的大文件 +a.datas = [d for d in a.datas if not d[0].endswith(('.pyc', '.pyo'))] + +# PYZ - Python字节码 +pyz = PYZ(a.pure) + +# EXE - 主程序(onedir模式:只放脚本+PYZ,不嵌入binaries/data) +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, # onedir关键:不嵌入二进制,交给COLLECT + name='WindowMsgBot', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=False, # UPX对大型DLL(torch/scipy)常出问题,关掉 + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) + +# COLLECT - 收集所有文件到输出目录 +coll = COLLECT( + exe, + a.binaries, + a.datas, + strip=False, + upx=False, + name='WindowMsgBot', +) \ No newline at end of file diff --git a/msg_history.db b/msg_history.db new file mode 100644 index 0000000..2bb7eeb Binary files /dev/null and b/msg_history.db differ