refactor: 删除 tkinter app_gui.py, 完全切到 Web UI 作为唯一入口

## 决策背景

用户反馈 "windows GUI 太难看了, 还是完全用 webUI 吧, 用户可以选择"。

tkinter app_gui.py (PR #107 引入, 929 行) 的问题:
- 中文字体下渲染糊 ("WeChat Decrypt 工具箱" 标题模糊)
- 90 年代 Windows 控件风格, 不暗色不现代
- 只能跑 Windows, 不跨平台
- 没法远程访问
- 跟 Web UI 维护两套, 重复

Web UI (monitor_web.py) 已经完全对齐功能:
- 8 个工具按钮 (3 tab 分组: 个人微信 / 企微 / 工具)
- 终止按钮 + 实时日志推送
- 导出筛选模态框 (会话搜索/复选/格式选择, close #112)
- 跟实时消息监听共享 SSE 通道
- Lucide SVG icon 统一风格 (无 emoji)
- 暗色主题 + design tokens + 玻璃质感顶栏

## 改动

### 删除
- \`app_gui.py\` (929 行 tkinter GUI)

### monitor_web.py
拆 \`main()\` → \`_start_monitor_if_ready()\` + 精简的 \`main()\`:

之前: keys 不存在 → \`sys.exit(1)\` 直接挂

现在: keys 不存在 → 跳过监听线程启动, 仅起 Web UI 服务。
用户从工具箱点 "① 提取密钥 + 解密数据库" 跑完后重启进程, 监听自动激活。
这样 exe 用户第一次双击时不会报错挂掉, 而是看到 Web UI 工具箱可以
直接用。

新启动流程: \`_start_monitor_if_ready\` 检查 keys 文件 / session.db
密钥 / session.db 路径都 OK 才启 monitor_thread, 任一不满足都给友好
提示但不退出。

### WeChatDecrypt.spec
- 入口 \`app_gui.py\` → \`monitor_web.py\`
- datas 清单补全 (加 export_all_chats / chat_export_helpers /
  batch_decrypt_images / transcribe_chat 等之前漏的)
- hiddenimports 显式列 Crypto / zstandard / pilk (避免 PyInstaller
  漏 detect)

### build.bat
- 删 30 行重复的 --add-data 清单 (跟 .spec 漂移风险)
- 改成 \`pyinstaller --noconfirm WeChatDecrypt.spec\`
- 单一 source of truth 是 .spec
- 完成提示从 "GUI 启动" 改成 "双击 → 浏览器开 Web UI"

### EXE_USAGE.md
完全重写为 Web UI 视角:
- 快速开始: 双击 exe → 浏览器自动打开
- 工具箱 3 tab 各自能力详解
- 导出筛选模态框使用说明
- 任务终止说明
- 输出目录布局
- 远程访问说明
- 结尾解释为什么去掉了 tkinter

### README.md
- 三平台 quick-start 里 "Windows GUI / EXE" → "Windows Web UI / EXE"
- 文件清单: \`app_gui.py\` 那行 → \`monitor_web.py\` (新身份: Web UI
  总入口)
- 技术细节里 "GUI 工具箱" 章节重写: 强调 Web UI 优势 (筛选模态框 /
  终止按钮 / 跨平台 / 远程访问 / 跟监听共存) + 末尾 1 句历史说明
  解释 tkinter 已删除

## 实测

- python monitor_web.py: keys 在 → 正常启动监听 + Web UI
- python monitor_web.py: 假装 keys 不在 → 跳过监听, Web UI 仍能开,
  用户从工具箱点 "① 提取密钥" 后重启即激活
- 测试 185/185 通过

## 没改 (留 follow-up)

- 实际打包 .spec 验证 (需要 Win 跑 pyinstaller, 没在 CI 跑)
- monitor_web 启动后**自动**检测 keys 文件 mtime 变化重启监听, 不需要
  用户手动重启进程 (现在的设计是"重启进程才激活")
This commit is contained in:
ylytdeng
2026-05-17 19:22:47 +08:00
parent 273fe65a07
commit e826b1a565
6 changed files with 213 additions and 1107 deletions

View File

@@ -2973,20 +2973,43 @@ class ThreadedServer(ThreadingMixIn, HTTPServer):
allow_reuse_address = True
def main():
print("=" * 60, flush=True)
print(" 微信实时监听 (WAL增量 + SSE推送)", flush=True)
print("=" * 60, flush=True)
def _start_monitor_if_ready():
"""如果 keys 已存在且能解出 session.db, 启动监听线程; 否则跳过。
with open(KEYS_FILE, encoding="utf-8") as f:
keys = strip_key_metadata(json.load(f))
用户在 Web UI 工具箱点 "① 提取密钥 + 解密数据库" 后, keys 文件就会
存在。届时刷新页面即可激活监听 (重启 monitor_web 或者下次访问 UI
时再尝试启动)。
返回 True = 监听已启动, False = 未启动 (UI 仍可用作工具箱)。
"""
if not os.path.exists(KEYS_FILE):
print(f"[!] 未找到 keys 文件: {KEYS_FILE}", flush=True)
print(" Web UI 仍可用作工具箱 (从右上角 🛠️ 工具 启动 '① 提取密钥')",
flush=True)
print(" 解密完成后重启本进程, 监听会自动启动\n", flush=True)
return False
try:
with open(KEYS_FILE, encoding="utf-8") as f:
keys = strip_key_metadata(json.load(f))
except Exception as e:
print(f"[!] 读取 keys 文件失败: {e}", flush=True)
print(" Web UI 仍可用作工具箱\n", flush=True)
return False
session_key_info = get_key_info(keys, os.path.join("session", "session.db"))
if not session_key_info:
print("[ERROR] 找不到 session.db 密钥", flush=True)
sys.exit(1)
print("[!] keys 文件里没有 session.db 密钥", flush=True)
print(" 可能 keys 是部分提取的, 工具箱 → '① 提取密钥' 重跑一次\n",
flush=True)
return False
enc_key = bytes.fromhex(session_key_info["enc_key"])
session_db = os.path.join(DB_DIR, "session", "session.db")
if not os.path.exists(session_db):
print(f"[!] session.db 不存在: {session_db}", flush=True)
print(" 检查 config.json 的 db_dir 是否对应当前微信账号\n", flush=True)
return False
print("加载联系人...", flush=True)
contact_names = load_contact_names()
@@ -3014,7 +3037,7 @@ def main():
db_cache = MonitorDBCache(keys, MONITOR_CACHE_DIR)
# 后台预热所有 message DB(图片/emoji 解密必需)
# 后台预热所有 message DB
def _warmup():
try:
t0 = time.perf_counter()
@@ -3032,16 +3055,26 @@ def main():
print(f"[warmup] {k} 失败: {e}", flush=True)
except Exception as e:
print(f"[warmup] 异常: {e}", flush=True)
# 构建 emoji 映射(独立解密,不走 cache
_build_emoji_lookup(keys)
print(f"[warmup] 全部完成 {(time.perf_counter()-t0)*1000:.0f}ms", flush=True)
threading.Thread(target=_warmup, daemon=True).start()
t = threading.Thread(target=monitor_thread, args=(enc_key, session_db, contact_names, db_cache, username_db_map), daemon=True)
t = threading.Thread(target=monitor_thread,
args=(enc_key, session_db, contact_names, db_cache, username_db_map),
daemon=True)
t.start()
return True
def main():
print("=" * 60, flush=True)
print(" WeChat Decrypt — Web UI + 实时监听", flush=True)
print("=" * 60, flush=True)
_start_monitor_if_ready()
server = ThreadedServer(('0.0.0.0', PORT), Handler)
print(f"\n=> http://localhost:{PORT}", flush=True)
print(f"=> http://localhost:{PORT}", flush=True)
print("Ctrl+C 停止\n", flush=True)
try: